Refactor code structure for improved readability and maintainability
This commit is contained in:
parent
389d72a136
commit
aa4c067ea8
1685 changed files with 393439 additions and 71932 deletions
1
.venv_codegen/Lib/site-packages/_black_version.py
Normal file
1
.venv_codegen/Lib/site-packages/_black_version.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
version = "26.3.1"
|
||||||
1
.venv_codegen/Lib/site-packages/_black_version.pyi
Normal file
1
.venv_codegen/Lib/site-packages/_black_version.pyi
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
version: str
|
||||||
128
.venv_codegen/Lib/site-packages/_distutils_hack/__init__.py
Normal file
128
.venv_codegen/Lib/site-packages/_distutils_hack/__init__.py
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import importlib
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
|
is_pypy = '__pypy__' in sys.builtin_module_names
|
||||||
|
|
||||||
|
|
||||||
|
warnings.filterwarnings('ignore',
|
||||||
|
r'.+ distutils\b.+ deprecated',
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
|
|
||||||
|
def warn_distutils_present():
|
||||||
|
if 'distutils' not in sys.modules:
|
||||||
|
return
|
||||||
|
if is_pypy and sys.version_info < (3, 7):
|
||||||
|
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
||||||
|
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
||||||
|
return
|
||||||
|
warnings.warn(
|
||||||
|
"Distutils was imported before Setuptools, but importing Setuptools "
|
||||||
|
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
||||||
|
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
||||||
|
"using distutils directly, ensure that setuptools is installed in the "
|
||||||
|
"traditional way (e.g. not an editable install), and/or make sure "
|
||||||
|
"that setuptools is always imported before distutils.")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_distutils():
|
||||||
|
if 'distutils' not in sys.modules:
|
||||||
|
return
|
||||||
|
warnings.warn("Setuptools is replacing distutils.")
|
||||||
|
mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
|
||||||
|
for name in mods:
|
||||||
|
del sys.modules[name]
|
||||||
|
|
||||||
|
|
||||||
|
def enabled():
|
||||||
|
"""
|
||||||
|
Allow selection of distutils by environment variable.
|
||||||
|
"""
|
||||||
|
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
|
||||||
|
return which == 'local'
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_local_distutils():
|
||||||
|
clear_distutils()
|
||||||
|
distutils = importlib.import_module('setuptools._distutils')
|
||||||
|
distutils.__name__ = 'distutils'
|
||||||
|
sys.modules['distutils'] = distutils
|
||||||
|
|
||||||
|
# sanity check that submodules load as expected
|
||||||
|
core = importlib.import_module('distutils.core')
|
||||||
|
assert '_distutils' in core.__file__, core.__file__
|
||||||
|
|
||||||
|
|
||||||
|
def do_override():
|
||||||
|
"""
|
||||||
|
Ensure that the local copy of distutils is preferred over stdlib.
|
||||||
|
|
||||||
|
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
||||||
|
for more motivation.
|
||||||
|
"""
|
||||||
|
if enabled():
|
||||||
|
warn_distutils_present()
|
||||||
|
ensure_local_distutils()
|
||||||
|
|
||||||
|
|
||||||
|
class DistutilsMetaFinder:
|
||||||
|
def find_spec(self, fullname, path, target=None):
|
||||||
|
if path is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
method_name = 'spec_for_{fullname}'.format(**locals())
|
||||||
|
method = getattr(self, method_name, lambda: None)
|
||||||
|
return method()
|
||||||
|
|
||||||
|
def spec_for_distutils(self):
|
||||||
|
import importlib.abc
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
class DistutilsLoader(importlib.abc.Loader):
|
||||||
|
|
||||||
|
def create_module(self, spec):
|
||||||
|
return importlib.import_module('setuptools._distutils')
|
||||||
|
|
||||||
|
def exec_module(self, module):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return importlib.util.spec_from_loader('distutils', DistutilsLoader())
|
||||||
|
|
||||||
|
def spec_for_pip(self):
|
||||||
|
"""
|
||||||
|
Ensure stdlib distutils when running under pip.
|
||||||
|
See pypa/pip#8761 for rationale.
|
||||||
|
"""
|
||||||
|
if self.pip_imported_during_build():
|
||||||
|
return
|
||||||
|
clear_distutils()
|
||||||
|
self.spec_for_distutils = lambda: None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pip_imported_during_build():
|
||||||
|
"""
|
||||||
|
Detect if pip is being imported in a build script. Ref #2355.
|
||||||
|
"""
|
||||||
|
import traceback
|
||||||
|
return any(
|
||||||
|
frame.f_globals['__file__'].endswith('setup.py')
|
||||||
|
for frame, line in traceback.walk_stack(None)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
DISTUTILS_FINDER = DistutilsMetaFinder()
|
||||||
|
|
||||||
|
|
||||||
|
def add_shim():
|
||||||
|
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_shim():
|
||||||
|
try:
|
||||||
|
sys.meta_path.remove(DISTUTILS_FINDER)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
__import__('_distutils_hack').do_override()
|
||||||
33
.venv_codegen/Lib/site-packages/_yaml/__init__.py
Normal file
33
.venv_codegen/Lib/site-packages/_yaml/__init__.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# This is a stub package designed to roughly emulate the _yaml
|
||||||
|
# extension module, which previously existed as a standalone module
|
||||||
|
# and has been moved into the `yaml` package namespace.
|
||||||
|
# It does not perfectly mimic its old counterpart, but should get
|
||||||
|
# close enough for anyone who's relying on it even when they shouldn't.
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
||||||
|
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
||||||
|
if not getattr(yaml, '__with_libyaml__', False):
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
||||||
|
raise exc("No module named '_yaml'")
|
||||||
|
else:
|
||||||
|
from yaml._yaml import *
|
||||||
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
'The _yaml extension module is now located at yaml._yaml'
|
||||||
|
' and its location is subject to change. To use the'
|
||||||
|
' LibYAML-based parser and emitter, import from `yaml`:'
|
||||||
|
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
del warnings
|
||||||
|
# Don't `del yaml` here because yaml is actually an existing
|
||||||
|
# namespace member of _yaml.
|
||||||
|
|
||||||
|
__name__ = '_yaml'
|
||||||
|
# If the module is top-level (i.e. not a part of any specific package)
|
||||||
|
# then the attribute should be set to ''.
|
||||||
|
# https://docs.python.org/3.8/library/types.html
|
||||||
|
__package__ = ''
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
Metadata-Version: 2.3
|
||||||
|
Name: annotated-types
|
||||||
|
Version: 0.7.0
|
||||||
|
Summary: Reusable constraint types to use with typing.Annotated
|
||||||
|
Project-URL: Homepage, https://github.com/annotated-types/annotated-types
|
||||||
|
Project-URL: Source, https://github.com/annotated-types/annotated-types
|
||||||
|
Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
|
||||||
|
Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin <s@muelcolvin.com>, Zac Hatfield-Dodds <zac@zhd.dev>
|
||||||
|
License-File: LICENSE
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: Environment :: Console
|
||||||
|
Classifier: Environment :: MacOS X
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: Information Technology
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: POSIX :: Linux
|
||||||
|
Classifier: Operating System :: Unix
|
||||||
|
Classifier: Programming Language :: Python :: 3 :: Only
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
|
# annotated-types
|
||||||
|
|
||||||
|
[](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
|
||||||
|
[](https://pypi.python.org/pypi/annotated-types)
|
||||||
|
[](https://github.com/annotated-types/annotated-types)
|
||||||
|
[](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
|
||||||
|
|
||||||
|
[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
|
||||||
|
adding context-specific metadata to existing types, and specifies that
|
||||||
|
`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
|
||||||
|
logic for `x`.
|
||||||
|
|
||||||
|
This package provides metadata objects which can be used to represent common
|
||||||
|
constraints such as upper and lower bounds on scalar values and collection sizes,
|
||||||
|
a `Predicate` marker for runtime checks, and
|
||||||
|
descriptions of how we intend these metadata to be interpreted. In some cases,
|
||||||
|
we also note alternative representations which do not require this package.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install annotated-types
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Annotated
|
||||||
|
from annotated_types import Gt, Len, Predicate
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
|
||||||
|
# Invalid: 17, 18, "19", 19.0, ...
|
||||||
|
factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
|
||||||
|
# Invalid: 4, 8, -2, 5.0, "prime", ...
|
||||||
|
|
||||||
|
my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
|
||||||
|
# Invalid: (1, 2), ["abc"], [0] * 20
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
_While `annotated-types` avoids runtime checks for performance, users should not
|
||||||
|
construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
|
||||||
|
Downstream implementors may choose to raise an error, emit a warning, silently ignore
|
||||||
|
a metadata item, etc., if the metadata objects described below are used with an
|
||||||
|
incompatible type - or for any other reason!_
|
||||||
|
|
||||||
|
### Gt, Ge, Lt, Le
|
||||||
|
|
||||||
|
Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
|
||||||
|
dates, times, strings, sets, etc. Note that the boundary value need not be of the
|
||||||
|
same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
|
||||||
|
is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
|
||||||
|
|
||||||
|
We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
|
||||||
|
as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
|
||||||
|
the `annotated-types` package.
|
||||||
|
|
||||||
|
To be explicit, these types have the following meanings:
|
||||||
|
|
||||||
|
* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
|
||||||
|
* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
|
||||||
|
* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
|
||||||
|
* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
|
||||||
|
|
||||||
|
### Interval
|
||||||
|
|
||||||
|
`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
|
||||||
|
metadata object. `None` attributes should be ignored, and non-`None` attributes
|
||||||
|
treated as per the single bounds above.
|
||||||
|
|
||||||
|
### MultipleOf
|
||||||
|
|
||||||
|
`MultipleOf(multiple_of=x)` might be interpreted in two ways:
|
||||||
|
|
||||||
|
1. Python semantics, implying `value % multiple_of == 0`, or
|
||||||
|
2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
|
||||||
|
where `int(value / multiple_of) == value / multiple_of`.
|
||||||
|
|
||||||
|
We encourage users to be aware of these two common interpretations and their
|
||||||
|
distinct behaviours, especially since very large or non-integer numbers make
|
||||||
|
it easy to cause silent data corruption due to floating-point imprecision.
|
||||||
|
|
||||||
|
We encourage libraries to carefully document which interpretation they implement.
|
||||||
|
|
||||||
|
### MinLen, MaxLen, Len
|
||||||
|
|
||||||
|
`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
|
||||||
|
|
||||||
|
As well as `Len()` which can optionally include upper and lower bounds, we also
|
||||||
|
provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
|
||||||
|
and `Len(max_length=y)` respectively.
|
||||||
|
|
||||||
|
`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
|
||||||
|
|
||||||
|
Examples of usage:
|
||||||
|
|
||||||
|
* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less
|
||||||
|
* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
|
||||||
|
* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more
|
||||||
|
* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
|
||||||
|
* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
|
||||||
|
|
||||||
|
#### Changed in v0.4.0
|
||||||
|
|
||||||
|
* `min_inclusive` has been renamed to `min_length`, no change in meaning
|
||||||
|
* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
|
||||||
|
* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
|
||||||
|
meaning of the upper bound in slices vs. `Len`
|
||||||
|
|
||||||
|
See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
|
||||||
|
|
||||||
|
### Timezone
|
||||||
|
|
||||||
|
`Timezone` can be used with a `datetime` or a `time` to express which timezones
|
||||||
|
are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
|
||||||
|
`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
|
||||||
|
expresses that any timezone-aware datetime is allowed. You may also pass a specific
|
||||||
|
timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
|
||||||
|
object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
|
||||||
|
allow a specific timezone, though we note that this is often a symptom of fragile design.
|
||||||
|
|
||||||
|
#### Changed in v0.x.x
|
||||||
|
|
||||||
|
* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
|
||||||
|
`timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
|
||||||
|
|
||||||
|
### Unit
|
||||||
|
|
||||||
|
`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
|
||||||
|
a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
|
||||||
|
would be a float representing a velocity in meters per second.
|
||||||
|
|
||||||
|
Please note that `annotated_types` itself makes no attempt to parse or validate
|
||||||
|
the unit string in any way. That is left entirely to downstream libraries,
|
||||||
|
such as [`pint`](https://pint.readthedocs.io) or
|
||||||
|
[`astropy.units`](https://docs.astropy.org/en/stable/units/).
|
||||||
|
|
||||||
|
An example of how a library might use this metadata:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from annotated_types import Unit
|
||||||
|
from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
|
||||||
|
|
||||||
|
# given a type annotated with a unit:
|
||||||
|
Meters = Annotated[float, Unit("m")]
|
||||||
|
|
||||||
|
|
||||||
|
# you can cast the annotation to a specific unit type with any
|
||||||
|
# callable that accepts a string and returns the desired type
|
||||||
|
T = TypeVar("T")
|
||||||
|
def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
|
||||||
|
if get_origin(tp) is Annotated:
|
||||||
|
for arg in get_args(tp):
|
||||||
|
if isinstance(arg, Unit):
|
||||||
|
return unit_cls(arg.unit)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# using `pint`
|
||||||
|
import pint
|
||||||
|
pint_unit = cast_unit(Meters, pint.Unit)
|
||||||
|
|
||||||
|
|
||||||
|
# using `astropy.units`
|
||||||
|
import astropy.units as u
|
||||||
|
astropy_unit = cast_unit(Meters, u.Unit)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Predicate
|
||||||
|
|
||||||
|
`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
|
||||||
|
Users should prefer the statically inspectable metadata above, but if you need
|
||||||
|
the full power and flexibility of arbitrary runtime predicates... here it is.
|
||||||
|
|
||||||
|
For some common constraints, we provide generic types:
|
||||||
|
|
||||||
|
* `IsLower = Annotated[T, Predicate(str.islower)]`
|
||||||
|
* `IsUpper = Annotated[T, Predicate(str.isupper)]`
|
||||||
|
* `IsDigit = Annotated[T, Predicate(str.isdigit)]`
|
||||||
|
* `IsFinite = Annotated[T, Predicate(math.isfinite)]`
|
||||||
|
* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
|
||||||
|
* `IsNan = Annotated[T, Predicate(math.isnan)]`
|
||||||
|
* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
|
||||||
|
* `IsInfinite = Annotated[T, Predicate(math.isinf)]`
|
||||||
|
* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
|
||||||
|
|
||||||
|
so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
|
||||||
|
(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
|
||||||
|
|
||||||
|
Some libraries might have special logic to handle known or understandable predicates,
|
||||||
|
for example by checking for `str.isdigit` and using its presence to both call custom
|
||||||
|
logic to enforce digit-only strings, and customise some generated external schema.
|
||||||
|
Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
|
||||||
|
favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
|
||||||
|
|
||||||
|
To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.
|
||||||
|
|
||||||
|
We do not specify what behaviour should be expected for predicates that raise
|
||||||
|
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||||
|
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||||
|
and then propagate or discard the resulting
|
||||||
|
`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
|
||||||
|
exception. We encourage libraries to document the behaviour they choose.
|
||||||
|
|
||||||
|
### Doc
|
||||||
|
|
||||||
|
`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used.
|
||||||
|
|
||||||
|
It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
|
||||||
|
|
||||||
|
It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
|
||||||
|
|
||||||
|
This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
|
||||||
|
|
||||||
|
### Integrating downstream types with `GroupedMetadata`
|
||||||
|
|
||||||
|
Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
|
||||||
|
This can help reduce verbosity and cognitive overhead for users.
|
||||||
|
For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Iterator
|
||||||
|
from annotated_types import GroupedMetadata, Ge
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Field(GroupedMetadata):
|
||||||
|
ge: int | None = None
|
||||||
|
description: str | None = None
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]:
|
||||||
|
# Iterating over a GroupedMetadata object should yield annotated-types
|
||||||
|
# constraint metadata objects which describe it as fully as possible,
|
||||||
|
# and may include other unknown objects too.
|
||||||
|
if self.ge is not None:
|
||||||
|
yield Ge(self.ge)
|
||||||
|
if self.description is not None:
|
||||||
|
yield Description(self.description)
|
||||||
|
```
|
||||||
|
|
||||||
|
Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently.
|
||||||
|
|
||||||
|
Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself.
|
||||||
|
|
||||||
|
Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`.
|
||||||
|
|
||||||
|
### Consuming metadata
|
||||||
|
|
||||||
|
We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103).
|
||||||
|
|
||||||
|
It is up to the implementer to determine how this metadata is used.
|
||||||
|
You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
|
||||||
|
|
||||||
|
## Design & History
|
||||||
|
|
||||||
|
This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
|
||||||
|
and Hypothesis, with the goal of making it as easy as possible for end-users to
|
||||||
|
provide more informative annotations for use by runtime libraries.
|
||||||
|
|
||||||
|
It is deliberately minimal, and following PEP-593 allows considerable downstream
|
||||||
|
discretion in what (if anything!) they choose to support. Nonetheless, we expect
|
||||||
|
that staying simple and covering _only_ the most common use-cases will give users
|
||||||
|
and maintainers the best experience we can. If you'd like more constraints for your
|
||||||
|
types - follow our lead, by defining them and documenting them downstream!
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046
|
||||||
|
annotated_types-0.7.0.dist-info/RECORD,,
|
||||||
|
annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
||||||
|
annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
|
||||||
|
annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819
|
||||||
|
annotated_types/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
annotated_types/__pycache__/test_cases.cpython-310.pyc,,
|
||||||
|
annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: hatchling 1.24.2
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2022 the contributors
|
||||||
|
|
||||||
|
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.
|
||||||
432
.venv_codegen/Lib/site-packages/annotated_types/__init__.py
Normal file
432
.venv_codegen/Lib/site-packages/annotated_types/__init__.py
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import tzinfo
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
from typing_extensions import Protocol, runtime_checkable
|
||||||
|
else:
|
||||||
|
from typing import Protocol, runtime_checkable
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
from typing_extensions import Annotated, Literal
|
||||||
|
else:
|
||||||
|
from typing import Annotated, Literal
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
EllipsisType = type(Ellipsis)
|
||||||
|
KW_ONLY = {}
|
||||||
|
SLOTS = {}
|
||||||
|
else:
|
||||||
|
from types import EllipsisType
|
||||||
|
|
||||||
|
KW_ONLY = {"kw_only": True}
|
||||||
|
SLOTS = {"slots": True}
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BaseMetadata',
|
||||||
|
'GroupedMetadata',
|
||||||
|
'Gt',
|
||||||
|
'Ge',
|
||||||
|
'Lt',
|
||||||
|
'Le',
|
||||||
|
'Interval',
|
||||||
|
'MultipleOf',
|
||||||
|
'MinLen',
|
||||||
|
'MaxLen',
|
||||||
|
'Len',
|
||||||
|
'Timezone',
|
||||||
|
'Predicate',
|
||||||
|
'LowerCase',
|
||||||
|
'UpperCase',
|
||||||
|
'IsDigits',
|
||||||
|
'IsFinite',
|
||||||
|
'IsNotFinite',
|
||||||
|
'IsNan',
|
||||||
|
'IsNotNan',
|
||||||
|
'IsInfinite',
|
||||||
|
'IsNotInfinite',
|
||||||
|
'doc',
|
||||||
|
'DocInfo',
|
||||||
|
'__version__',
|
||||||
|
)
|
||||||
|
|
||||||
|
__version__ = '0.7.0'
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
# arguments that start with __ are considered
|
||||||
|
# positional only
|
||||||
|
# see https://peps.python.org/pep-0484/#positional-only-arguments
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsGt(Protocol):
|
||||||
|
def __gt__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsGe(Protocol):
|
||||||
|
def __ge__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsLt(Protocol):
|
||||||
|
def __lt__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsLe(Protocol):
|
||||||
|
def __le__(self: T, __other: T) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsMod(Protocol):
|
||||||
|
def __mod__(self: T, __other: T) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SupportsDiv(Protocol):
|
||||||
|
def __div__(self: T, __other: T) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMetadata:
|
||||||
|
"""Base class for all metadata.
|
||||||
|
|
||||||
|
This exists mainly so that implementers
|
||||||
|
can do `isinstance(..., BaseMetadata)` while traversing field annotations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Gt(BaseMetadata):
|
||||||
|
"""Gt(gt=x) implies that the value must be greater than x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``>`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gt: SupportsGt
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Ge(BaseMetadata):
|
||||||
|
"""Ge(ge=x) implies that the value must be greater than or equal to x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``>=`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ge: SupportsGe
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Lt(BaseMetadata):
|
||||||
|
"""Lt(lt=x) implies that the value must be less than x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``<`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lt: SupportsLt
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Le(BaseMetadata):
|
||||||
|
"""Le(le=x) implies that the value must be less than or equal to x.
|
||||||
|
|
||||||
|
It can be used with any type that supports the ``<=`` operator,
|
||||||
|
including numbers, dates and times, strings, sets, and so on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
le: SupportsLe
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class GroupedMetadata(Protocol):
|
||||||
|
"""A grouping of multiple objects, like typing.Unpack.
|
||||||
|
|
||||||
|
`GroupedMetadata` on its own is not metadata and has no meaning.
|
||||||
|
All of the constraints and metadata should be fully expressable
|
||||||
|
in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
|
||||||
|
|
||||||
|
Concrete implementations should override `GroupedMetadata.__iter__()`
|
||||||
|
to add their own metadata.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> @dataclass
|
||||||
|
>>> class Field(GroupedMetadata):
|
||||||
|
>>> gt: float | None = None
|
||||||
|
>>> description: str | None = None
|
||||||
|
...
|
||||||
|
>>> def __iter__(self) -> Iterable[object]:
|
||||||
|
>>> if self.gt is not None:
|
||||||
|
>>> yield Gt(self.gt)
|
||||||
|
>>> if self.description is not None:
|
||||||
|
>>> yield Description(self.gt)
|
||||||
|
|
||||||
|
Also see the implementation of `Interval` below for an example.
|
||||||
|
|
||||||
|
Parsers should recognize this and unpack it so that it can be used
|
||||||
|
both with and without unpacking:
|
||||||
|
|
||||||
|
- `Annotated[int, Field(...)]` (parser must unpack Field)
|
||||||
|
- `Annotated[int, *Field(...)]` (PEP-646)
|
||||||
|
""" # noqa: trailing-whitespace
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]:
|
||||||
|
...
|
||||||
|
|
||||||
|
if not TYPE_CHECKING:
|
||||||
|
__slots__ = () # allow subclasses to use slots
|
||||||
|
|
||||||
|
def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
|
||||||
|
# Basic ABC like functionality without the complexity of an ABC
|
||||||
|
super().__init_subclass__(*args, **kwargs)
|
||||||
|
if cls.__iter__ is GroupedMetadata.__iter__:
|
||||||
|
raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]: # noqa: F811
|
||||||
|
raise NotImplementedError # more helpful than "None has no attribute..." type errors
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **KW_ONLY, **SLOTS)
|
||||||
|
class Interval(GroupedMetadata):
|
||||||
|
"""Interval can express inclusive or exclusive bounds with a single object.
|
||||||
|
|
||||||
|
It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
|
||||||
|
are interpreted the same way as the single-bound constraints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
gt: Union[SupportsGt, None] = None
|
||||||
|
ge: Union[SupportsGe, None] = None
|
||||||
|
lt: Union[SupportsLt, None] = None
|
||||||
|
le: Union[SupportsLe, None] = None
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||||
|
"""Unpack an Interval into zero or more single-bounds."""
|
||||||
|
if self.gt is not None:
|
||||||
|
yield Gt(self.gt)
|
||||||
|
if self.ge is not None:
|
||||||
|
yield Ge(self.ge)
|
||||||
|
if self.lt is not None:
|
||||||
|
yield Lt(self.lt)
|
||||||
|
if self.le is not None:
|
||||||
|
yield Le(self.le)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class MultipleOf(BaseMetadata):
|
||||||
|
"""MultipleOf(multiple_of=x) might be interpreted in two ways:
|
||||||
|
|
||||||
|
1. Python semantics, implying ``value % multiple_of == 0``, or
|
||||||
|
2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
|
||||||
|
|
||||||
|
We encourage users to be aware of these two common interpretations,
|
||||||
|
and libraries to carefully document which they implement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
multiple_of: Union[SupportsDiv, SupportsMod]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class MinLen(BaseMetadata):
|
||||||
|
"""
|
||||||
|
MinLen() implies minimum inclusive length,
|
||||||
|
e.g. ``len(value) >= min_length``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
min_length: Annotated[int, Ge(0)]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class MaxLen(BaseMetadata):
|
||||||
|
"""
|
||||||
|
MaxLen() implies maximum inclusive length,
|
||||||
|
e.g. ``len(value) <= max_length``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
max_length: Annotated[int, Ge(0)]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Len(GroupedMetadata):
|
||||||
|
"""
|
||||||
|
Len() implies that ``min_length <= len(value) <= max_length``.
|
||||||
|
|
||||||
|
Upper bound may be omitted or ``None`` to indicate no upper length bound.
|
||||||
|
"""
|
||||||
|
|
||||||
|
min_length: Annotated[int, Ge(0)] = 0
|
||||||
|
max_length: Optional[Annotated[int, Ge(0)]] = None
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||||
|
"""Unpack a Len into zone or more single-bounds."""
|
||||||
|
if self.min_length > 0:
|
||||||
|
yield MinLen(self.min_length)
|
||||||
|
if self.max_length is not None:
|
||||||
|
yield MaxLen(self.max_length)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Timezone(BaseMetadata):
|
||||||
|
"""Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
|
||||||
|
|
||||||
|
``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
|
||||||
|
``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
|
||||||
|
tz-aware but any timezone is allowed.
|
||||||
|
|
||||||
|
You may also pass a specific timezone string or tzinfo object such as
|
||||||
|
``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
|
||||||
|
you only allow a specific timezone, though we note that this is often
|
||||||
|
a symptom of poor design.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tz: Union[str, tzinfo, EllipsisType, None]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Unit(BaseMetadata):
|
||||||
|
"""Indicates that the value is a physical quantity with the specified unit.
|
||||||
|
|
||||||
|
It is intended for usage with numeric types, where the value represents the
|
||||||
|
magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
|
||||||
|
or ``speed: Annotated[float, Unit('m/s')]``.
|
||||||
|
|
||||||
|
Interpretation of the unit string is left to the discretion of the consumer.
|
||||||
|
It is suggested to follow conventions established by python libraries that work
|
||||||
|
with physical quantities, such as
|
||||||
|
|
||||||
|
- ``pint`` : <https://pint.readthedocs.io/en/stable/>
|
||||||
|
- ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
|
||||||
|
|
||||||
|
For indicating a quantity with a certain dimensionality but without a specific unit
|
||||||
|
it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
|
||||||
|
Note, however, ``annotated_types`` itself makes no use of the unit string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unit: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class Predicate(BaseMetadata):
|
||||||
|
"""``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
|
||||||
|
|
||||||
|
Users should prefer statically inspectable metadata, but if you need the full
|
||||||
|
power and flexibility of arbitrary runtime predicates... here it is.
|
||||||
|
|
||||||
|
We provide a few predefined predicates for common string constraints:
|
||||||
|
``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
|
||||||
|
``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
|
||||||
|
can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
|
||||||
|
|
||||||
|
Some libraries might have special logic to handle certain predicates, e.g. by
|
||||||
|
checking for `str.isdigit` and using its presence to both call custom logic to
|
||||||
|
enforce digit-only strings, and customise some generated external schema.
|
||||||
|
|
||||||
|
We do not specify what behaviour should be expected for predicates that raise
|
||||||
|
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||||
|
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||||
|
and then propagate or discard the resulting exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
func: Callable[[Any], bool]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
|
||||||
|
return f"{self.__class__.__name__}({self.func!r})"
|
||||||
|
if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
|
||||||
|
namespace := getattr(self.func.__self__, "__name__", None)
|
||||||
|
):
|
||||||
|
return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
|
||||||
|
if isinstance(self.func, type(str.isascii)): # method descriptor
|
||||||
|
return f"{self.__class__.__name__}({self.func.__qualname__})"
|
||||||
|
return f"{self.__class__.__name__}({self.func.__name__})"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Not:
|
||||||
|
func: Callable[[Any], bool]
|
||||||
|
|
||||||
|
def __call__(self, __v: Any) -> bool:
|
||||||
|
return not self.func(__v)
|
||||||
|
|
||||||
|
|
||||||
|
_StrType = TypeVar("_StrType", bound=str)
|
||||||
|
|
||||||
|
LowerCase = Annotated[_StrType, Predicate(str.islower)]
|
||||||
|
"""
|
||||||
|
Return True if the string is a lowercase string, False otherwise.
|
||||||
|
|
||||||
|
A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
|
||||||
|
""" # noqa: E501
|
||||||
|
UpperCase = Annotated[_StrType, Predicate(str.isupper)]
|
||||||
|
"""
|
||||||
|
Return True if the string is an uppercase string, False otherwise.
|
||||||
|
|
||||||
|
A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
|
||||||
|
""" # noqa: E501
|
||||||
|
IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
|
||||||
|
IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
|
||||||
|
"""
|
||||||
|
Return True if the string is a digit string, False otherwise.
|
||||||
|
|
||||||
|
A string is a digit string if all characters in the string are digits and there is at least one character in the string.
|
||||||
|
""" # noqa: E501
|
||||||
|
IsAscii = Annotated[_StrType, Predicate(str.isascii)]
|
||||||
|
"""
|
||||||
|
Return True if all characters in the string are ASCII, False otherwise.
|
||||||
|
|
||||||
|
ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
|
||||||
|
IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
|
||||||
|
"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
|
||||||
|
IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
|
||||||
|
"""Return True if x is one of infinity or NaN, and False otherwise"""
|
||||||
|
IsNan = Annotated[_NumericType, Predicate(math.isnan)]
|
||||||
|
"""Return True if x is a NaN (not a number), and False otherwise."""
|
||||||
|
IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
|
||||||
|
"""Return True if x is anything but NaN (not a number), and False otherwise."""
|
||||||
|
IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
|
||||||
|
"""Return True if x is a positive or negative infinity, and False otherwise."""
|
||||||
|
IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
|
||||||
|
"""Return True if x is neither a positive or negative infinity, and False otherwise."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing_extensions import DocInfo, doc # type: ignore [attr-defined]
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
@dataclass(frozen=True, **SLOTS)
|
||||||
|
class DocInfo: # type: ignore [no-redef]
|
||||||
|
""" "
|
||||||
|
The return value of doc(), mainly to be used by tools that want to extract the
|
||||||
|
Annotated documentation at runtime.
|
||||||
|
"""
|
||||||
|
|
||||||
|
documentation: str
|
||||||
|
"""The documentation string passed to doc()."""
|
||||||
|
|
||||||
|
def doc(
|
||||||
|
documentation: str,
|
||||||
|
) -> DocInfo:
|
||||||
|
"""
|
||||||
|
Add documentation to a type annotation inside of Annotated.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
|
||||||
|
"""
|
||||||
|
return DocInfo(documentation)
|
||||||
0
.venv_codegen/Lib/site-packages/annotated_types/py.typed
Normal file
0
.venv_codegen/Lib/site-packages/annotated_types/py.typed
Normal file
151
.venv_codegen/Lib/site-packages/annotated_types/test_cases.py
Normal file
151
.venv_codegen/Lib/site-packages/annotated_types/test_cases.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from datetime import date, datetime, timedelta, timezone
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
else:
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import annotated_types as at
|
||||||
|
|
||||||
|
|
||||||
|
class Case(NamedTuple):
|
||||||
|
"""
|
||||||
|
A test case for `annotated_types`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
annotation: Any
|
||||||
|
valid_cases: Iterable[Any]
|
||||||
|
invalid_cases: Iterable[Any]
|
||||||
|
|
||||||
|
|
||||||
|
def cases() -> Iterable[Case]:
|
||||||
|
# Gt, Ge, Lt, Le
|
||||||
|
yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
|
||||||
|
yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Gt(date(2000, 1, 1))],
|
||||||
|
[date(2000, 1, 2), date(2000, 1, 3)],
|
||||||
|
[date(2000, 1, 1), date(1999, 12, 31)],
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Gt(Decimal('1.123'))],
|
||||||
|
[Decimal('1.1231'), Decimal('123')],
|
||||||
|
[Decimal('1.123'), Decimal('0')],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
|
||||||
|
yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
[datetime(1998, 1, 1), datetime(1999, 12, 31)],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
|
||||||
|
yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
|
||||||
|
[datetime(1999, 12, 31), datetime(1999, 12, 31)],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
|
||||||
|
yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Le(datetime(2000, 1, 1))],
|
||||||
|
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Interval
|
||||||
|
yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
|
||||||
|
yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
|
||||||
|
yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
|
||||||
|
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||||
|
[datetime(2000, 1, 1), datetime(2000, 1, 4)],
|
||||||
|
)
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
|
||||||
|
yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
|
||||||
|
|
||||||
|
# lengths
|
||||||
|
|
||||||
|
yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||||
|
yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||||
|
yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||||
|
yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||||
|
|
||||||
|
yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
|
||||||
|
yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
|
||||||
|
yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||||
|
yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||||
|
|
||||||
|
yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
|
||||||
|
yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
|
||||||
|
|
||||||
|
yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
|
||||||
|
yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
|
||||||
|
yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
|
||||||
|
|
||||||
|
# Timezone
|
||||||
|
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone(timezone.utc)],
|
||||||
|
[datetime(2000, 1, 1, tzinfo=timezone.utc)],
|
||||||
|
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||||
|
)
|
||||||
|
yield Case(
|
||||||
|
Annotated[datetime, at.Timezone('Europe/London')],
|
||||||
|
[datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
|
||||||
|
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Quantity
|
||||||
|
|
||||||
|
yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
|
||||||
|
|
||||||
|
# predicate types
|
||||||
|
|
||||||
|
yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
|
||||||
|
yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
|
||||||
|
yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
|
||||||
|
yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
|
||||||
|
|
||||||
|
yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
|
||||||
|
|
||||||
|
yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
|
||||||
|
yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
|
||||||
|
yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
|
||||||
|
yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
|
||||||
|
yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
|
||||||
|
yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
|
||||||
|
|
||||||
|
# check stacked predicates
|
||||||
|
yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
|
||||||
|
|
||||||
|
# doc
|
||||||
|
yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
|
||||||
|
|
||||||
|
# custom GroupedMetadata
|
||||||
|
class MyCustomGroupedMetadata(at.GroupedMetadata):
|
||||||
|
def __iter__(self) -> Iterator[at.Predicate]:
|
||||||
|
yield at.Predicate(lambda x: float(x).is_integer())
|
||||||
|
|
||||||
|
yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,354 @@
|
||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: argcomplete
|
||||||
|
Version: 3.6.3
|
||||||
|
Summary: Bash tab completion for argparse
|
||||||
|
Project-URL: Homepage, https://github.com/kislyuk/argcomplete
|
||||||
|
Project-URL: Documentation, https://kislyuk.github.io/argcomplete
|
||||||
|
Project-URL: Source Code, https://github.com/kislyuk/argcomplete
|
||||||
|
Project-URL: Issue Tracker, https://github.com/kislyuk/argcomplete/issues
|
||||||
|
Project-URL: Change Log, https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst
|
||||||
|
Author: Andrey Kislyuk
|
||||||
|
Author-email: kislyuk@gmail.com
|
||||||
|
Maintainer: Andrey Kislyuk
|
||||||
|
Maintainer-email: kislyuk@gmail.com
|
||||||
|
License: Apache Software License
|
||||||
|
License-File: LICENSE.rst
|
||||||
|
License-File: NOTICE
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Environment :: Console
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: Apache Software License
|
||||||
|
Classifier: Operating System :: MacOS :: MacOS X
|
||||||
|
Classifier: Operating System :: POSIX
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Classifier: Topic :: Software Development
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
Classifier: Topic :: System :: Shells
|
||||||
|
Classifier: Topic :: Terminals
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Provides-Extra: test
|
||||||
|
Requires-Dist: coverage; extra == 'test'
|
||||||
|
Requires-Dist: mypy; extra == 'test'
|
||||||
|
Requires-Dist: pexpect; extra == 'test'
|
||||||
|
Requires-Dist: ruff; extra == 'test'
|
||||||
|
Requires-Dist: wheel; extra == 'test'
|
||||||
|
Description-Content-Type: text/x-rst
|
||||||
|
|
||||||
|
argcomplete - Bash/zsh tab completion for argparse
|
||||||
|
==================================================
|
||||||
|
*Tab complete all the things!*
|
||||||
|
|
||||||
|
Argcomplete provides easy, extensible command line tab completion of arguments for your Python application.
|
||||||
|
|
||||||
|
It makes two assumptions:
|
||||||
|
|
||||||
|
* You're using bash or zsh as your shell (limited support exists for other shells - see below)
|
||||||
|
* You're using `argparse <http://docs.python.org/3/library/argparse.html>`_ to manage your command line arguments/options
|
||||||
|
|
||||||
|
Argcomplete is particularly useful if your program has lots of options or subparsers, and if your program can
|
||||||
|
dynamically suggest completions for your argument/option values (for example, if the user is browsing resources over
|
||||||
|
the network).
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
::
|
||||||
|
|
||||||
|
pip install argcomplete
|
||||||
|
activate-global-python-argcomplete
|
||||||
|
|
||||||
|
See `Activating global completion`_ below for details about the second step.
|
||||||
|
|
||||||
|
Refresh your shell environment (start a new shell).
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
Add the ``PYTHON_ARGCOMPLETE_OK`` marker and a call to ``argcomplete.autocomplete()`` to your Python application as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
|
import argcomplete, argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
...
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
...
|
||||||
|
|
||||||
|
If using ``pyproject.toml`` ``[project.scripts]`` entry points, the ``PYTHON_ARGCOMPLETE_OK`` marker should appear
|
||||||
|
at the beginning of the file that contains the entry point.
|
||||||
|
|
||||||
|
Register your Python application with your shell's completion framework by running ``register-python-argcomplete``::
|
||||||
|
|
||||||
|
eval "$(register-python-argcomplete my-python-app)"
|
||||||
|
|
||||||
|
Quotes are significant; the registration will fail without them. See `Global completion`_ below for a way to enable
|
||||||
|
argcomplete generally without registering each application individually.
|
||||||
|
|
||||||
|
argcomplete.autocomplete(*parser*)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
This method is the entry point to the module. It must be called **after** ArgumentParser construction is complete, but
|
||||||
|
**before** the ``ArgumentParser.parse_args()`` method is called. The method looks for an environment variable that the
|
||||||
|
completion hook shellcode sets, and if it's there, collects completions, prints them to the output stream (fd 8 by
|
||||||
|
default), and exits. Otherwise, it returns to the caller immediately.
|
||||||
|
|
||||||
|
.. admonition:: Side effects
|
||||||
|
|
||||||
|
Argcomplete gets completions by running your program. It intercepts the execution flow at the moment
|
||||||
|
``argcomplete.autocomplete()`` is called. After sending completions, it exits using ``exit_method`` (``os._exit``
|
||||||
|
by default). This means if your program has any side effects that happen before ``argcomplete`` is called, those
|
||||||
|
side effects will happen every time the user presses ``<TAB>`` (although anything your program prints to stdout or
|
||||||
|
stderr will be suppressed). For this reason it's best to construct the argument parser and call
|
||||||
|
``argcomplete.autocomplete()`` as early as possible in your execution flow.
|
||||||
|
|
||||||
|
.. admonition:: Performance
|
||||||
|
|
||||||
|
If the program takes a long time to get to the point where ``argcomplete.autocomplete()`` is called, the tab completion
|
||||||
|
process will feel sluggish, and the user may lose confidence in it. So it's also important to minimize the startup time
|
||||||
|
of the program up to that point (for example, by deferring initialization or importing of large modules until after
|
||||||
|
parsing options).
|
||||||
|
|
||||||
|
Specifying completers
|
||||||
|
---------------------
|
||||||
|
You can specify custom completion functions for your options and arguments. Two styles are supported: callable and
|
||||||
|
readline-style. Callable completers are simpler. They are called with the following keyword arguments:
|
||||||
|
|
||||||
|
* ``prefix``: The prefix text of the last word before the cursor on the command line.
|
||||||
|
For dynamic completers, this can be used to reduce the work required to generate possible completions.
|
||||||
|
* ``action``: The ``argparse.Action`` instance that this completer was called for.
|
||||||
|
* ``parser``: The ``argparse.ArgumentParser`` instance that the action was taken by.
|
||||||
|
* ``parsed_args``: The result of argument parsing so far (the ``argparse.Namespace`` args object normally returned by
|
||||||
|
``ArgumentParser.parse_args()``).
|
||||||
|
|
||||||
|
Completers can return their completions as an iterable of strings or a mapping (dict) of strings to their
|
||||||
|
descriptions (zsh will display the descriptions as context help alongside completions). An example completer for names
|
||||||
|
of environment variables might look like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def EnvironCompleter(**kwargs):
|
||||||
|
return os.environ
|
||||||
|
|
||||||
|
To specify a completer for an argument or option, set the ``completer`` attribute of its associated action. An easy
|
||||||
|
way to do this at definition time is:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from argcomplete.completers import EnvironCompleter
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--env-var1").completer = EnvironCompleter
|
||||||
|
parser.add_argument("--env-var2").completer = EnvironCompleter
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
|
If you specify the ``choices`` keyword for an argparse option or argument (and don't specify a completer), it will be
|
||||||
|
used for completions.
|
||||||
|
|
||||||
|
A completer that is initialized with a set of all possible choices of values for its action might look like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class ChoicesCompleter(object):
|
||||||
|
def __init__(self, choices):
|
||||||
|
self.choices = choices
|
||||||
|
|
||||||
|
def __call__(self, **kwargs):
|
||||||
|
return self.choices
|
||||||
|
|
||||||
|
The following two ways to specify a static set of choices are equivalent for completion purposes:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from argcomplete.completers import ChoicesCompleter
|
||||||
|
|
||||||
|
parser.add_argument("--protocol", choices=('http', 'https', 'ssh', 'rsync', 'wss'))
|
||||||
|
parser.add_argument("--proto").completer=ChoicesCompleter(('http', 'https', 'ssh', 'rsync', 'wss'))
|
||||||
|
|
||||||
|
Note that if you use the ``choices=<completions>`` option, argparse will show
|
||||||
|
all these choices in the ``--help`` output by default. To prevent this, set
|
||||||
|
``metavar`` (like ``parser.add_argument("--protocol", metavar="PROTOCOL",
|
||||||
|
choices=('http', 'https', 'ssh', 'rsync', 'wss'))``).
|
||||||
|
|
||||||
|
The following `script <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ uses
|
||||||
|
``parsed_args`` and `Requests <http://python-requests.org/>`_ to query GitHub for publicly known members of an
|
||||||
|
organization and complete their names, then prints the member description:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
|
import argcomplete, argparse, requests, pprint
|
||||||
|
|
||||||
|
def github_org_members(prefix, parsed_args, **kwargs):
|
||||||
|
resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization)
|
||||||
|
return (member['login'] for member in requests.get(resource).json() if member['login'].startswith(prefix))
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--organization", help="GitHub organization")
|
||||||
|
parser.add_argument("--member", help="GitHub member").completer = github_org_members
|
||||||
|
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json())
|
||||||
|
|
||||||
|
`Try it <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ like this::
|
||||||
|
|
||||||
|
./describe_github_user.py --organization heroku --member <TAB>
|
||||||
|
|
||||||
|
If you have a useful completer to add to the `completer library
|
||||||
|
<https://github.com/kislyuk/argcomplete/blob/master/argcomplete/completers.py>`_, send a pull request!
|
||||||
|
|
||||||
|
Readline-style completers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
The readline_ module defines a completer protocol in rlcompleter_. Readline-style completers are also supported by
|
||||||
|
argcomplete, so you can use the same completer object both in an interactive readline-powered shell and on the command
|
||||||
|
line. For example, you can use the readline-style completer provided by IPython_ to get introspective completions like
|
||||||
|
you would get in the IPython shell:
|
||||||
|
|
||||||
|
.. _readline: http://docs.python.org/3/library/readline.html
|
||||||
|
.. _rlcompleter: http://docs.python.org/3/library/rlcompleter.html#completer-objects
|
||||||
|
.. _IPython: http://ipython.org/
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import IPython
|
||||||
|
parser.add_argument("--python-name").completer = IPython.core.completer.Completer()
|
||||||
|
|
||||||
|
``argcomplete.CompletionFinder.rl_complete`` can also be used to plug in an argparse parser as a readline completer.
|
||||||
|
|
||||||
|
Printing warnings in completers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Normal stdout/stderr output is suspended when argcomplete runs. Sometimes, though, when the user presses ``<TAB>``, it's
|
||||||
|
appropriate to print information about why completions generation failed. To do this, use ``warn``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from argcomplete import warn
|
||||||
|
|
||||||
|
def AwesomeWebServiceCompleter(prefix, **kwargs):
|
||||||
|
if login_failed:
|
||||||
|
warn("Please log in to Awesome Web Service to use autocompletion")
|
||||||
|
return completions
|
||||||
|
|
||||||
|
Using a custom completion validator
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
By default, argcomplete validates your completions by checking if they start with the prefix given to the completer. You
|
||||||
|
can override this validation check by supplying the ``validator`` keyword to ``argcomplete.autocomplete()``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def my_validator(completion_candidate, current_input):
|
||||||
|
"""Complete non-prefix substring matches."""
|
||||||
|
return current_input in completion_candidate
|
||||||
|
|
||||||
|
argcomplete.autocomplete(parser, validator=my_validator)
|
||||||
|
|
||||||
|
Global completion
|
||||||
|
-----------------
|
||||||
|
In global completion mode, you don't have to register each argcomplete-capable executable separately. Instead, the shell
|
||||||
|
will look for the string **PYTHON_ARGCOMPLETE_OK** in the first 1024 bytes of any executable that it's running
|
||||||
|
completion for, and if it's found, follow the rest of the argcomplete protocol as described above.
|
||||||
|
|
||||||
|
Additionally, completion is activated for scripts run as ``python <script>`` and ``python -m <module>``. If you're using
|
||||||
|
multiple Python versions on the same system, the version being used to run the script must have argcomplete installed.
|
||||||
|
|
||||||
|
.. admonition:: Bash version compatibility
|
||||||
|
|
||||||
|
When using bash, global completion requires bash support for ``complete -D``, which was introduced in bash 4.2. Since
|
||||||
|
Mac OS ships with an outdated version of Bash (3.2), you can either use zsh or install a newer version of bash using
|
||||||
|
`Homebrew <http://brew.sh/>`_ (``brew install bash`` - you will also need to add ``/opt/homebrew/bin/bash`` to
|
||||||
|
``/etc/shells``, and run ``chsh`` to change your shell). You can check the version of the running copy of bash with
|
||||||
|
``echo $BASH_VERSION``.
|
||||||
|
|
||||||
|
.. note:: If you use ``project.scripts`` directives to provide command line entry points to your package,
|
||||||
|
argcomplete will follow the wrapper scripts to their destination and look for ``PYTHON_ARGCOMPLETE_OK`` in the
|
||||||
|
first kilobyte of the file containing the destination code.
|
||||||
|
|
||||||
|
If you choose not to use global completion, or ship a completion module that depends on argcomplete, you must register
|
||||||
|
your script explicitly using ``eval "$(register-python-argcomplete my-python-app)"``. Standard completion module
|
||||||
|
registration rules apply: namely, the script name is passed directly to ``complete``, meaning it is only tab completed
|
||||||
|
when invoked exactly as it was registered. In the above example, ``my-python-app`` must be on the path, and the user
|
||||||
|
must be attempting to complete it by that name. The above line alone would **not** allow you to complete
|
||||||
|
``./my-python-app``, or ``/path/to/my-python-app``.
|
||||||
|
|
||||||
|
Activating global completion
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
The script ``activate-global-python-argcomplete`` installs the global completion script
|
||||||
|
`bash_completion.d/_python-argcomplete <https://github.com/kislyuk/argcomplete/blob/master/argcomplete/bash_completion.d/_python-argcomplete>`_
|
||||||
|
into an appropriate location on your system for both bash and zsh. The specific location depends on your platform and
|
||||||
|
whether you installed argcomplete system-wide using ``sudo`` or locally (into your user's home directory).
|
||||||
|
|
||||||
|
Zsh Support
|
||||||
|
-----------
|
||||||
|
Argcomplete supports zsh. On top of plain completions like in bash, zsh allows you to see argparse help strings as
|
||||||
|
completion descriptions. All shellcode included with argcomplete is compatible with both bash and zsh, so the same
|
||||||
|
completer commands ``activate-global-python-argcomplete`` and ``eval "$(register-python-argcomplete my-python-app)"``
|
||||||
|
work for zsh as well.
|
||||||
|
|
||||||
|
Python Support
|
||||||
|
--------------
|
||||||
|
Argcomplete requires Python 3.9+.
|
||||||
|
|
||||||
|
Support for other shells
|
||||||
|
------------------------
|
||||||
|
Argcomplete maintainers provide support only for the bash and zsh shells on Linux and MacOS. For resources related to
|
||||||
|
other shells and platforms, including fish, tcsh, xonsh, powershell, and Windows, please see the
|
||||||
|
`contrib <https://github.com/kislyuk/argcomplete/tree/develop/contrib>`_ directory.
|
||||||
|
|
||||||
|
Common Problems
|
||||||
|
---------------
|
||||||
|
If global completion is not completing your script, bash may have registered a default completion function::
|
||||||
|
|
||||||
|
$ complete | grep my-python-app
|
||||||
|
complete -F _minimal my-python-app
|
||||||
|
|
||||||
|
You can fix this by restarting your shell, or by running ``complete -r my-python-app``.
|
||||||
|
|
||||||
|
Debugging
|
||||||
|
---------
|
||||||
|
Set the ``_ARC_DEBUG`` variable in your shell to enable verbose debug output every time argcomplete runs. This will
|
||||||
|
disrupt the command line composition state of your terminal, but make it possible to see the internal state of the
|
||||||
|
completer if it encounters problems.
|
||||||
|
|
||||||
|
Acknowledgments
|
||||||
|
---------------
|
||||||
|
Inspired and informed by the optcomplete_ module by Martin Blais.
|
||||||
|
|
||||||
|
.. _optcomplete: http://pypi.python.org/pypi/optcomplete
|
||||||
|
|
||||||
|
Links
|
||||||
|
-----
|
||||||
|
* `Project home page (GitHub) <https://github.com/kislyuk/argcomplete>`_
|
||||||
|
* `Documentation <https://kislyuk.github.io/argcomplete/>`_
|
||||||
|
* `Package distribution (PyPI) <https://pypi.python.org/pypi/argcomplete>`_
|
||||||
|
* `Change log <https://github.com/kislyuk/argcomplete/blob/master/Changes.rst>`_
|
||||||
|
|
||||||
|
Bugs
|
||||||
|
~~~~
|
||||||
|
Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/kislyuk/argcomplete/issues>`_.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
|
||||||
|
`Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
|
||||||
|
files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
|
||||||
|
|
||||||
|
.. image:: https://github.com/kislyuk/argcomplete/workflows/Python%20package/badge.svg
|
||||||
|
:target: https://github.com/kislyuk/argcomplete/actions
|
||||||
|
.. image:: https://codecov.io/github/kislyuk/argcomplete/coverage.svg?branch=master
|
||||||
|
:target: https://codecov.io/github/kislyuk/argcomplete?branch=master
|
||||||
|
.. image:: https://img.shields.io/pypi/v/argcomplete.svg
|
||||||
|
:target: https://pypi.python.org/pypi/argcomplete
|
||||||
|
.. image:: https://img.shields.io/pypi/l/argcomplete.svg
|
||||||
|
:target: https://pypi.python.org/pypi/argcomplete
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
../../Scripts/activate-global-python-argcomplete.exe,sha256=sKTtEcxvniDHRL7TCczFj2bZGEoUZeP72CVONpB7PUk,106438
|
||||||
|
../../Scripts/python-argcomplete-check-easy-install-script.exe,sha256=OSI2DrC05tXplss6QmCac3ktWpVA-3hX0SRRM9XftSI,106448
|
||||||
|
../../Scripts/register-python-argcomplete.exe,sha256=nR87Gcqu9tSQUWELXcNsu9-7HFeZl7p2NMoUV1qs568,106431
|
||||||
|
argcomplete-3.6.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
argcomplete-3.6.3.dist-info/METADATA,sha256=pgeGA7r-pv0MtwGW3gjQCI7P7959Re96W9ppfBxkCMc,16891
|
||||||
|
argcomplete-3.6.3.dist-info/RECORD,,
|
||||||
|
argcomplete-3.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
||||||
|
argcomplete-3.6.3.dist-info/entry_points.txt,sha256=b5M9f6w7ZYVDGLGhZOghcXTwAlVJJIDnnuaJsafvaIo,315
|
||||||
|
argcomplete-3.6.3.dist-info/licenses/LICENSE.rst,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
||||||
|
argcomplete-3.6.3.dist-info/licenses/NOTICE,sha256=LIicch7Irm12ZGgK-u-7THYgl29DS1elBuzZLwZJtqA,387
|
||||||
|
argcomplete/__init__.py,sha256=yF6xrvfK0WpO8rlqQlilfScgXTJH1RSXcAMi5s4iZ6Q,693
|
||||||
|
argcomplete/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/_check_console_script.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/_check_module.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/completers.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/exceptions.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/finders.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/io.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/lexers.cpython-310.pyc,,
|
||||||
|
argcomplete/__pycache__/shell_integration.cpython-310.pyc,,
|
||||||
|
argcomplete/_check_console_script.py,sha256=pL8QMk-0RB2_dakNTAy5aGA-2sB2fY2E7Ps3LSLi2sQ,2413
|
||||||
|
argcomplete/_check_module.py,sha256=vBHBICA_sRcduWArcrupNimkum-9DApa7zh8AauFSUo,2178
|
||||||
|
argcomplete/bash_completion.d/_python-argcomplete,sha256=Bq8DgQlSyKVYrnp1aqG0YVEyQbKk8vB9DIcazd-dJqU,10047
|
||||||
|
argcomplete/completers.py,sha256=_AroOMLJirJe786_7pf4hA83l6IPIlzaES-uKJcz150,4657
|
||||||
|
argcomplete/exceptions.py,sha256=tojpetbrtcmabLRVC_YqiFGv0x-QzHPOIA2fEG6l9aA,112
|
||||||
|
argcomplete/finders.py,sha256=yxYDCePC9bIfe64wcw1YsmAvrE3spKQ0iO1MHDnFUuY,28664
|
||||||
|
argcomplete/io.py,sha256=PCepOaNi714v6aEzQ7AAltWDYo-aP5Xmbm6-fJ4ta5U,866
|
||||||
|
argcomplete/lexers.py,sha256=se52bPTUK97m8oGDqP0mrwxQEUYMnOxvGQ2FdEo8v1U,2131
|
||||||
|
argcomplete/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
argcomplete/packages/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
argcomplete/packages/__pycache__/_argparse.cpython-310.pyc,,
|
||||||
|
argcomplete/packages/__pycache__/_shlex.cpython-310.pyc,,
|
||||||
|
argcomplete/packages/_argparse.py,sha256=BdSoZUpmc-LlT8pAjLmQE6eh98CdKU78tJDRy5HmWEE,16151
|
||||||
|
argcomplete/packages/_shlex.py,sha256=hQ9e0n_oKsbKzuIQk0kcABK7cZO1bRGg-tpFGCTpO_4,12963
|
||||||
|
argcomplete/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
argcomplete/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
argcomplete/scripts/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
argcomplete/scripts/__pycache__/activate_global_python_argcomplete.cpython-310.pyc,,
|
||||||
|
argcomplete/scripts/__pycache__/python_argcomplete_check_easy_install_script.cpython-310.pyc,,
|
||||||
|
argcomplete/scripts/__pycache__/register_python_argcomplete.cpython-310.pyc,,
|
||||||
|
argcomplete/scripts/activate_global_python_argcomplete.py,sha256=DqNPgvT4XVz2AjcfRePio8kSHJ3_SU0jIE1-rmqk-PA,6454
|
||||||
|
argcomplete/scripts/python_argcomplete_check_easy_install_script.py,sha256=4FMHlpfFK2mdez-aH_sEKiu6kcUkbdjdD2AtBr6cpuw,3478
|
||||||
|
argcomplete/scripts/register_python_argcomplete.py,sha256=oDspOBZPfXhck5z9KPMH0Wls9VVe2Kw7n9ARS5NczfQ,2242
|
||||||
|
argcomplete/shell_integration.py,sha256=rlHtjyjobQSIE6AD-_KFyBFzCCRpApgADfaPOR9dumY,7915
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: hatchling 1.27.0
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
[console_scripts]
|
||||||
|
activate-global-python-argcomplete = argcomplete.scripts.activate_global_python_argcomplete:main
|
||||||
|
python-argcomplete-check-easy-install-script = argcomplete.scripts.python_argcomplete_check_easy_install_script:main
|
||||||
|
register-python-argcomplete = argcomplete.scripts.register_python_argcomplete:main
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
argcomplete is a free open source library that integrates Python applications with Bash and Zsh shell completion.
|
||||||
|
The argcomplete project is staffed by volunteers. If you are using this library in a for-profit project, please
|
||||||
|
contribute to argcomplete development and maintenance using the "Sponsor" button on the argcomplete GitHub project page,
|
||||||
|
https://github.com/kislyuk/argcomplete.
|
||||||
13
.venv_codegen/Lib/site-packages/argcomplete/__init__.py
Normal file
13
.venv_codegen/Lib/site-packages/argcomplete/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
|
||||||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
from . import completers
|
||||||
|
from .completers import ChoicesCompleter, DirectoriesCompleter, EnvironCompleter, FilesCompleter, SuppressCompleter
|
||||||
|
from .exceptions import ArgcompleteException
|
||||||
|
from .finders import CompletionFinder, ExclusiveCompletionFinder, safe_actions
|
||||||
|
from .io import debug, mute_stderr, warn
|
||||||
|
from .lexers import split_line
|
||||||
|
from .shell_integration import shellcode
|
||||||
|
|
||||||
|
autocomplete = CompletionFinder()
|
||||||
|
autocomplete.__doc__ = """ Use this to access argcomplete. See :meth:`argcomplete.CompletionFinder.__call__()`. """
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""
|
||||||
|
Utility for locating the module (or package's __init__.py)
|
||||||
|
associated with a given console_script name
|
||||||
|
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.
|
||||||
|
|
||||||
|
Such scripts are automatically generated and cannot contain
|
||||||
|
the marker themselves, so we defer to the containing module or package.
|
||||||
|
|
||||||
|
For more information on setuptools console_scripts, see
|
||||||
|
https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation
|
||||||
|
|
||||||
|
Intended to be invoked by argcomplete's global completion function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from importlib.metadata import EntryPoint
|
||||||
|
from importlib.metadata import entry_points as importlib_entry_points
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from ._check_module import ArgcompleteMarkerNotFound, find
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Argument is the full path to the console script.
|
||||||
|
script_path = sys.argv[1]
|
||||||
|
|
||||||
|
# Find the module and function names that correspond to this
|
||||||
|
# assuming it is actually a console script.
|
||||||
|
name = os.path.basename(script_path)
|
||||||
|
|
||||||
|
entry_points: Iterable[EntryPoint] = importlib_entry_points() # type:ignore
|
||||||
|
|
||||||
|
# Python 3.12+ returns a tuple of entry point objects
|
||||||
|
# whereas <=3.11 returns a SelectableGroups object
|
||||||
|
if sys.version_info < (3, 12):
|
||||||
|
entry_points = entry_points["console_scripts"] # type:ignore
|
||||||
|
|
||||||
|
entry_points = [ep for ep in entry_points if ep.name == name and ep.group == "console_scripts"] # type:ignore
|
||||||
|
|
||||||
|
if not entry_points:
|
||||||
|
raise ArgcompleteMarkerNotFound("no entry point found matching script")
|
||||||
|
entry_point = entry_points[0]
|
||||||
|
module_name, function_name = entry_point.value.split(":", 1)
|
||||||
|
|
||||||
|
# Check this looks like the script we really expected.
|
||||||
|
with open(script_path) as f:
|
||||||
|
script = f.read()
|
||||||
|
if "from {} import {}".format(module_name, function_name) not in script:
|
||||||
|
raise ArgcompleteMarkerNotFound("does not appear to be a console script")
|
||||||
|
if "sys.exit({}())".format(function_name) not in script:
|
||||||
|
raise ArgcompleteMarkerNotFound("does not appear to be a console script")
|
||||||
|
|
||||||
|
# Look for the argcomplete marker in the script it imports.
|
||||||
|
with open(find(module_name, return_package=True)) as f:
|
||||||
|
head = f.read(1024)
|
||||||
|
if "PYTHON_ARGCOMPLETE_OK" not in head:
|
||||||
|
raise ArgcompleteMarkerNotFound("marker not found")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except ArgcompleteMarkerNotFound as e:
|
||||||
|
sys.exit(str(e))
|
||||||
72
.venv_codegen/Lib/site-packages/argcomplete/_check_module.py
Normal file
72
.venv_codegen/Lib/site-packages/argcomplete/_check_module.py
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
"""
|
||||||
|
Utility for locating a module (or package's __main__.py) with a given name
|
||||||
|
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.
|
||||||
|
|
||||||
|
The module name should be specified in a form usable with `python -m`.
|
||||||
|
|
||||||
|
Intended to be invoked by argcomplete's global completion function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tokenize
|
||||||
|
from importlib.util import find_spec
|
||||||
|
|
||||||
|
|
||||||
|
class ArgcompleteMarkerNotFound(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def find(name, return_package=False):
|
||||||
|
names = name.split(".")
|
||||||
|
# Look for the first importlib ModuleSpec that has `origin` set, indicating it's not a namespace package.
|
||||||
|
for package_name_boundary in range(len(names)):
|
||||||
|
spec = find_spec(".".join(names[: package_name_boundary + 1]))
|
||||||
|
if spec is not None and spec.origin is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if spec is None:
|
||||||
|
raise ArgcompleteMarkerNotFound('no module named "{}"'.format(names[0]))
|
||||||
|
if not spec.has_location:
|
||||||
|
raise ArgcompleteMarkerNotFound("cannot locate file")
|
||||||
|
if spec.submodule_search_locations is None:
|
||||||
|
if len(names) != 1:
|
||||||
|
raise ArgcompleteMarkerNotFound("{} is not a package".format(names[0]))
|
||||||
|
return spec.origin
|
||||||
|
if len(spec.submodule_search_locations) != 1:
|
||||||
|
raise ArgcompleteMarkerNotFound("expecting one search location")
|
||||||
|
path = os.path.join(spec.submodule_search_locations[0], *names[package_name_boundary + 1 :])
|
||||||
|
if os.path.isdir(path):
|
||||||
|
filename = "__main__.py"
|
||||||
|
if return_package:
|
||||||
|
filename = "__init__.py"
|
||||||
|
return os.path.join(path, filename)
|
||||||
|
else:
|
||||||
|
return path + ".py"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
name = sys.argv[1]
|
||||||
|
except IndexError:
|
||||||
|
raise ArgcompleteMarkerNotFound("missing argument on the command line")
|
||||||
|
|
||||||
|
filename = find(name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fp = tokenize.open(filename)
|
||||||
|
except OSError:
|
||||||
|
raise ArgcompleteMarkerNotFound("cannot open file")
|
||||||
|
|
||||||
|
with fp:
|
||||||
|
head = fp.read(1024)
|
||||||
|
|
||||||
|
if "PYTHON_ARGCOMPLETE_OK" not in head:
|
||||||
|
raise ArgcompleteMarkerNotFound("marker not found")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except ArgcompleteMarkerNotFound as e:
|
||||||
|
sys.exit(str(e))
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
#compdef -default-
|
||||||
|
|
||||||
|
# argcomplete global completion loader for zsh and bash
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
|
||||||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
# Note: both the leading underscore in the name of this file and the first line (compdef) are required by zsh
|
||||||
|
|
||||||
|
# In zsh, this file is autoloaded and used as the default completer (_default).
|
||||||
|
# There are many other special contexts we don't want to override
|
||||||
|
# (as would be the case with `#compdef -P *`).
|
||||||
|
# https://zsh.sourceforge.io/Doc/Release/Completion-System.html
|
||||||
|
|
||||||
|
# Copy of __expand_tilde_by_ref from bash-completion
|
||||||
|
# ZSH implementation added
|
||||||
|
__python_argcomplete_expand_tilde_by_ref () {
|
||||||
|
if [ -n "${ZSH_VERSION-}" ]; then
|
||||||
|
if [ "${(P)1[1]}" = "~" ]; then
|
||||||
|
eval $1="${(P)1/#\~/$HOME}";
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "${!1:0:1}" = "~" ]; then
|
||||||
|
if [ "${!1}" != "${!1//\/}" ]; then
|
||||||
|
eval $1="${!1/%\/*}"/'${!1#*/}';
|
||||||
|
else
|
||||||
|
eval $1="${!1}";
|
||||||
|
fi;
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run something, muting output or redirecting it to the debug stream
|
||||||
|
# depending on the value of _ARC_DEBUG.
|
||||||
|
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
|
||||||
|
__python_argcomplete_run() {
|
||||||
|
if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
|
||||||
|
__python_argcomplete_run_inner "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local tmpfile="$(mktemp)"
|
||||||
|
_ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
|
||||||
|
local code=$?
|
||||||
|
cat "$tmpfile"
|
||||||
|
rm "$tmpfile"
|
||||||
|
return $code
|
||||||
|
}
|
||||||
|
|
||||||
|
__python_argcomplete_run_inner() {
|
||||||
|
if [[ -z "${_ARC_DEBUG-}" ]]; then
|
||||||
|
"$@" 8>&1 9>&2 1>/dev/null 2>&1 </dev/null
|
||||||
|
else
|
||||||
|
"$@" 8>&1 9>&2 1>&9 2>&1 </dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__python_argcomplete_upshift_bash_rematch() {
|
||||||
|
if [[ -z "${ZSH_VERSION-}" ]]; then
|
||||||
|
_BASH_REMATCH=( "" "${BASH_REMATCH[@]}" )
|
||||||
|
else
|
||||||
|
_BASH_REMATCH=( "${BASH_REMATCH[@]}" )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function scans the beginning of an executable file provided as the first
|
||||||
|
# argument ($1) for certain indicators, specified by the second argument ($2),
|
||||||
|
# or the "target". There are three possible targets: "interpreter",
|
||||||
|
# "magic_string", and "easy_install". If the target is "interpreter", the
|
||||||
|
# function matches the interpreter line, alongside any optional interpreter
|
||||||
|
# arguments. If the target is "magic_string", a match is attempted for the
|
||||||
|
# "PYTHON_ARGCOMPLETE_OK" magic string, indicating that the file should be run
|
||||||
|
# to get completions. If the target is "easy_install", the function matches either
|
||||||
|
# "PBR Generated" or any of the "EASY-INSTALL" scripts (either SCRIPT,
|
||||||
|
# ENTRY-SCRIPT, or DEV-SCRIPT). In all cases, only the first kilobyte of
|
||||||
|
# the file is searched. The regex matches are returned in BASH_REMATCH,
|
||||||
|
# indexed starting at 1, regardless of the shell in use.
|
||||||
|
__python_argcomplete_scan_head() {
|
||||||
|
local file="$1"
|
||||||
|
local target="$2"
|
||||||
|
|
||||||
|
local REPLY
|
||||||
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
read -r -k 1024 -u 0 < "$file";
|
||||||
|
else
|
||||||
|
read -r -N 1024 < "$file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Since ZSH does not support a -n option, we
|
||||||
|
# trim all characters after the first line in both shells
|
||||||
|
if [[ "$target" = "interpreter" ]]; then
|
||||||
|
read -r <<< "$REPLY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local regex
|
||||||
|
|
||||||
|
case "$target" in
|
||||||
|
magic_string) regex='PYTHON_ARGCOMPLETE_OK' ;;
|
||||||
|
easy_install) regex="(PBR Generated)|(EASY-INSTALL-(SCRIPT|ENTRY-SCRIPT|DEV-SCRIPT))" ;;
|
||||||
|
asdf) regex="asdf exec " ;;
|
||||||
|
interpreter) regex='^#!(.*)$' ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local ret=""
|
||||||
|
if [[ "$REPLY" =~ $regex ]]; then
|
||||||
|
ret=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
__python_argcomplete_upshift_bash_rematch
|
||||||
|
|
||||||
|
[[ -n $ret ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
__python_argcomplete_scan_head_noerr() {
|
||||||
|
__python_argcomplete_scan_head "$@" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
__python_argcomplete_which() {
|
||||||
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
whence -p "$@"
|
||||||
|
else
|
||||||
|
type -P "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_python_argcomplete_global() {
|
||||||
|
|
||||||
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
# Store result of a regex match in the
|
||||||
|
# BASH_REMATCH variable rather than MATCH
|
||||||
|
setopt local_options BASH_REMATCH
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1-based version of BASH_REMATCH. Modifying BASH_REMATCH
|
||||||
|
# directly causes older versions of Bash to exit
|
||||||
|
local _BASH_REMATCH="";
|
||||||
|
|
||||||
|
local executable=""
|
||||||
|
|
||||||
|
# req_argv contains the arguments to the completion
|
||||||
|
# indexed from 1 (regardless of the shell.) In Bash,
|
||||||
|
# the zeroth index is empty
|
||||||
|
local req_argv=()
|
||||||
|
|
||||||
|
if [[ -z "${ZSH_VERSION-}" ]]; then
|
||||||
|
executable=$1
|
||||||
|
req_argv=( "" "${COMP_WORDS[@]:1}" )
|
||||||
|
__python_argcomplete_expand_tilde_by_ref executable
|
||||||
|
else
|
||||||
|
executable="${words[1]}"
|
||||||
|
__python_argcomplete_expand_tilde_by_ref executable
|
||||||
|
req_argv=( "${words[@]:1}" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
local ARGCOMPLETE=0
|
||||||
|
if [[ "$executable" == python* ]] || [[ "$executable" == pypy* ]]; then
|
||||||
|
if [[ "${req_argv[1]}" == -m ]]; then
|
||||||
|
if __python_argcomplete_run "$executable" -m argcomplete._check_module "${req_argv[2]}"; then
|
||||||
|
ARGCOMPLETE=3
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ $ARGCOMPLETE == 0 ]]; then
|
||||||
|
local potential_path="${req_argv[1]}"
|
||||||
|
__python_argcomplete_expand_tilde_by_ref potential_path
|
||||||
|
if [[ -f "$potential_path" ]] && __python_argcomplete_scan_head_noerr "$potential_path" magic_string; then
|
||||||
|
req_argv[1]="$potential_path" # not expanded in __python_argcomplete_run
|
||||||
|
ARGCOMPLETE=2
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif __python_argcomplete_which "$executable" >/dev/null 2>&1; then
|
||||||
|
local SCRIPT_NAME=$(__python_argcomplete_which "$executable")
|
||||||
|
__python_argcomplete_scan_head_noerr "$SCRIPT_NAME" interpreter
|
||||||
|
if (__python_argcomplete_which pyenv && [[ "$SCRIPT_NAME" = $(pyenv root)/shims/* ]]) >/dev/null 2>&1; then
|
||||||
|
local SCRIPT_NAME=$(pyenv which "$executable")
|
||||||
|
fi
|
||||||
|
if (__python_argcomplete_which asdf && __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" asdf) >/dev/null 2>&1; then
|
||||||
|
local SCRIPT_NAME=$(asdf which "$executable")
|
||||||
|
fi
|
||||||
|
if __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" magic_string; then
|
||||||
|
ARGCOMPLETE=1
|
||||||
|
elif __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" interpreter; then
|
||||||
|
__python_argcomplete_upshift_bash_rematch
|
||||||
|
local interpreter="${_BASH_REMATCH[2]}"
|
||||||
|
|
||||||
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
interpreter=($=interpreter)
|
||||||
|
else
|
||||||
|
interpreter=($interpreter)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (__python_argcomplete_scan_head_noerr "$SCRIPT_NAME" easy_install \
|
||||||
|
&& "${interpreter[@]}" "$(__python_argcomplete_which python-argcomplete-check-easy-install-script)" "$SCRIPT_NAME") >/dev/null 2>&1; then
|
||||||
|
ARGCOMPLETE=1
|
||||||
|
elif ([[ "${interpreter[@]}" == *python* ]] || [[ "${interpreter[@]}" == *pypy* ]])\
|
||||||
|
&& __python_argcomplete_run "${interpreter[@]}" -m argcomplete._check_console_script "$SCRIPT_NAME"; then
|
||||||
|
ARGCOMPLETE=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $ARGCOMPLETE != 0 ]]; then
|
||||||
|
local IFS=$'\013'
|
||||||
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
local completions
|
||||||
|
completions=($(IFS="$IFS" \
|
||||||
|
COMP_LINE="$BUFFER" \
|
||||||
|
COMP_POINT="$CURSOR" \
|
||||||
|
_ARGCOMPLETE=$ARGCOMPLETE \
|
||||||
|
_ARGCOMPLETE_SHELL="zsh" \
|
||||||
|
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
|
||||||
|
__python_argcomplete_run "$executable" "${(@)req_argv[1, ${ARGCOMPLETE}-1]}"))
|
||||||
|
local nosort=()
|
||||||
|
local nospace=()
|
||||||
|
if is-at-least 5.8; then
|
||||||
|
nosort=(-o nosort)
|
||||||
|
fi
|
||||||
|
if [[ "${completions-}" =~ ([^\\\\]): && "${BASH_REMATCH[2]}" =~ [=/:] ]]; then
|
||||||
|
nospace=(-S '')
|
||||||
|
fi
|
||||||
|
_describe "$executable" completions "${nosort[@]}" "${nospace[@]}"
|
||||||
|
else
|
||||||
|
COMPREPLY=($(IFS="$IFS" \
|
||||||
|
COMP_LINE="$COMP_LINE" \
|
||||||
|
COMP_POINT="$COMP_POINT" \
|
||||||
|
COMP_TYPE="$COMP_TYPE" \
|
||||||
|
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
|
||||||
|
_ARGCOMPLETE=$ARGCOMPLETE \
|
||||||
|
_ARGCOMPLETE_SHELL="bash" \
|
||||||
|
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
|
||||||
|
__python_argcomplete_run "$executable" "${req_argv[@]:1:${ARGCOMPLETE}-1}"))
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
unset COMPREPLY
|
||||||
|
elif [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
_default
|
||||||
|
else
|
||||||
|
type -t _completion_loader | grep -q 'function' && _completion_loader "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
if [[ -z "${ZSH_VERSION-}" ]]; then
|
||||||
|
complete -o default -o bashdefault -D -F _python_argcomplete_global
|
||||||
|
else
|
||||||
|
# -Uz is recommended for the use of functions supplied with the zsh distribution.
|
||||||
|
# https://unix.stackexchange.com/a/214306
|
||||||
|
autoload -Uz is-at-least
|
||||||
|
# If this is being implicitly loaded because we placed it on fpath,
|
||||||
|
# the comment at the top of this file causes zsh to invoke this script directly,
|
||||||
|
# so we must explicitly call the global completion function.
|
||||||
|
# Note $service should only ever be -default- because the comment at the top
|
||||||
|
# registers this script as the default completer (#compdef -default-).
|
||||||
|
if [[ $service == -default- ]]; then
|
||||||
|
_python_argcomplete_global
|
||||||
|
fi
|
||||||
|
# If this has been executed directly (e.g. `eval "$(activate-global-python-argcomplete --dest=-)"`)
|
||||||
|
# we need to explicitly call compdef to register the completion function.
|
||||||
|
# If we have been implicitly loaded, we still call compdef as a slight optimisation
|
||||||
|
# (there is no need to execute any top-level code more than once).
|
||||||
|
compdef _python_argcomplete_global -default-
|
||||||
|
fi
|
||||||
137
.venv_codegen/Lib/site-packages/argcomplete/completers.py
Normal file
137
.venv_codegen/Lib/site-packages/argcomplete/completers.py
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
|
||||||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def _call(*args, **kwargs):
|
||||||
|
# TODO: replace "universal_newlines" with "text" once 3.6 support is dropped
|
||||||
|
kwargs["universal_newlines"] = True
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(*args, **kwargs).splitlines()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCompleter:
|
||||||
|
"""
|
||||||
|
This is the base class that all argcomplete completers should subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace
|
||||||
|
) -> None:
|
||||||
|
raise NotImplementedError("This method should be implemented by a subclass.")
|
||||||
|
|
||||||
|
|
||||||
|
class ChoicesCompleter(BaseCompleter):
|
||||||
|
def __init__(self, choices):
|
||||||
|
self.choices = choices
|
||||||
|
|
||||||
|
def _convert(self, choice):
|
||||||
|
if not isinstance(choice, str):
|
||||||
|
choice = str(choice)
|
||||||
|
return choice
|
||||||
|
|
||||||
|
def __call__(self, **kwargs):
|
||||||
|
return (self._convert(c) for c in self.choices)
|
||||||
|
|
||||||
|
|
||||||
|
EnvironCompleter = ChoicesCompleter(os.environ)
|
||||||
|
|
||||||
|
|
||||||
|
class FilesCompleter(BaseCompleter):
|
||||||
|
"""
|
||||||
|
File completer class, optionally takes a list of allowed extensions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, allowednames=(), directories=True):
|
||||||
|
# Fix if someone passes in a string instead of a list
|
||||||
|
if isinstance(allowednames, (str, bytes)):
|
||||||
|
allowednames = [allowednames]
|
||||||
|
|
||||||
|
self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames]
|
||||||
|
self.directories = directories
|
||||||
|
|
||||||
|
def __call__(self, prefix, **kwargs):
|
||||||
|
completion = []
|
||||||
|
if self.allowednames:
|
||||||
|
if self.directories:
|
||||||
|
# Using 'bind' in this and the following commands is a workaround to a bug in bash
|
||||||
|
# that was fixed in bash 5.3 but affects older versions. Environment variables are not treated
|
||||||
|
# correctly in older versions and calling bind makes them available. For details, see
|
||||||
|
# https://savannah.gnu.org/support/index.php?111125
|
||||||
|
files = _call(
|
||||||
|
["bash", "-c", "bind; compgen -A directory -- '{p}'".format(p=prefix)], stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
completion += [f + "/" for f in files]
|
||||||
|
for x in self.allowednames:
|
||||||
|
completion += _call(
|
||||||
|
["bash", "-c", "bind; compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix)],
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
completion += _call(
|
||||||
|
["bash", "-c", "bind; compgen -A file -- '{p}'".format(p=prefix)], stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
anticomp = _call(
|
||||||
|
["bash", "-c", "bind; compgen -A directory -- '{p}'".format(p=prefix)], stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
completion = list(set(completion) - set(anticomp))
|
||||||
|
|
||||||
|
if self.directories:
|
||||||
|
completion += [f + "/" for f in anticomp]
|
||||||
|
return completion
|
||||||
|
|
||||||
|
|
||||||
|
class _FilteredFilesCompleter(BaseCompleter):
|
||||||
|
def __init__(self, predicate):
|
||||||
|
"""
|
||||||
|
Create the completer
|
||||||
|
|
||||||
|
A predicate accepts as its only argument a candidate path and either
|
||||||
|
accepts it or rejects it.
|
||||||
|
"""
|
||||||
|
assert predicate, "Expected a callable predicate"
|
||||||
|
self.predicate = predicate
|
||||||
|
|
||||||
|
def __call__(self, prefix, **kwargs):
|
||||||
|
"""
|
||||||
|
Provide completions on prefix
|
||||||
|
"""
|
||||||
|
target_dir = os.path.dirname(prefix)
|
||||||
|
try:
|
||||||
|
names = os.listdir(target_dir or ".")
|
||||||
|
except Exception:
|
||||||
|
return # empty iterator
|
||||||
|
incomplete_part = os.path.basename(prefix)
|
||||||
|
# Iterate on target_dir entries and filter on given predicate
|
||||||
|
for name in names:
|
||||||
|
if not name.startswith(incomplete_part):
|
||||||
|
continue
|
||||||
|
candidate = os.path.join(target_dir, name)
|
||||||
|
if not self.predicate(candidate):
|
||||||
|
continue
|
||||||
|
yield candidate + "/" if os.path.isdir(candidate) else candidate
|
||||||
|
|
||||||
|
|
||||||
|
class DirectoriesCompleter(_FilteredFilesCompleter):
|
||||||
|
def __init__(self):
|
||||||
|
_FilteredFilesCompleter.__init__(self, predicate=os.path.isdir)
|
||||||
|
|
||||||
|
|
||||||
|
class SuppressCompleter(BaseCompleter):
|
||||||
|
"""
|
||||||
|
A completer used to suppress the completion of specific arguments
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def suppress(self):
|
||||||
|
"""
|
||||||
|
Decide if the completion should be suppressed
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
class ArgcompleteException(Exception):
|
||||||
|
"Exception raised when the shell argument completion process fails."
|
||||||
621
.venv_codegen/Lib/site-packages/argcomplete/finders.py
Normal file
621
.venv_codegen/Lib/site-packages/argcomplete/finders.py
Normal file
|
|
@ -0,0 +1,621 @@
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
|
||||||
|
# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
|
||||||
|
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
|
||||||
|
# See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Callable, Dict, List, Optional, Sequence, TextIO, Union
|
||||||
|
|
||||||
|
from . import io as _io
|
||||||
|
from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter
|
||||||
|
from .io import debug, mute_stderr
|
||||||
|
from .lexers import split_line
|
||||||
|
from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
|
||||||
|
|
||||||
|
safe_actions = {
|
||||||
|
argparse._StoreAction,
|
||||||
|
argparse._StoreConstAction,
|
||||||
|
argparse._StoreTrueAction,
|
||||||
|
argparse._StoreFalseAction,
|
||||||
|
argparse._AppendAction,
|
||||||
|
argparse._AppendConstAction,
|
||||||
|
argparse._CountAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def default_validator(completion, prefix):
|
||||||
|
return completion.startswith(prefix)
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionFinder(object):
|
||||||
|
"""
|
||||||
|
Inherit from this class if you wish to override any of the stages below. Otherwise, use
|
||||||
|
``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as
|
||||||
|
:meth:`CompletionFinder.__call__()`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
argument_parser=None,
|
||||||
|
always_complete_options=True,
|
||||||
|
exclude=None,
|
||||||
|
validator=None,
|
||||||
|
print_suppressed=False,
|
||||||
|
default_completer=FilesCompleter(),
|
||||||
|
append_space=None,
|
||||||
|
):
|
||||||
|
self._parser = argument_parser
|
||||||
|
self._formatter = None
|
||||||
|
self.always_complete_options = always_complete_options
|
||||||
|
self.exclude = exclude
|
||||||
|
if validator is None:
|
||||||
|
validator = default_validator
|
||||||
|
self.validator = validator
|
||||||
|
self.print_suppressed = print_suppressed
|
||||||
|
self.completing = False
|
||||||
|
self._display_completions: Dict[str, str] = {}
|
||||||
|
self.default_completer = default_completer
|
||||||
|
if append_space is None:
|
||||||
|
append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1"
|
||||||
|
self.append_space = append_space
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
argument_parser: argparse.ArgumentParser,
|
||||||
|
always_complete_options: Union[bool, str] = True,
|
||||||
|
exit_method: Callable = os._exit,
|
||||||
|
output_stream: Optional[TextIO] = None,
|
||||||
|
exclude: Optional[Sequence[str]] = None,
|
||||||
|
validator: Optional[Callable] = None,
|
||||||
|
print_suppressed: bool = False,
|
||||||
|
append_space: Optional[bool] = None,
|
||||||
|
default_completer: BaseCompleter = FilesCompleter(),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
:param argument_parser: The argument parser to autocomplete on
|
||||||
|
:param always_complete_options:
|
||||||
|
Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
|
||||||
|
been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
|
||||||
|
suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
|
||||||
|
with no long variant will be suggested. If ``short``, short options and long options with no short variant
|
||||||
|
will be suggested.
|
||||||
|
:param exit_method:
|
||||||
|
Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
|
||||||
|
perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
|
||||||
|
:param exclude: List of strings representing options to be omitted from autocompletion
|
||||||
|
:param validator:
|
||||||
|
Function to filter all completions through before returning (called with two string arguments, completion
|
||||||
|
and prefix; return value is evaluated as a boolean)
|
||||||
|
:param print_suppressed:
|
||||||
|
Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
|
||||||
|
:param append_space:
|
||||||
|
Whether to append a space to unique matches. The default is ``True``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you are not subclassing CompletionFinder to override its behaviors,
|
||||||
|
use :meth:`argcomplete.autocomplete()` directly. It has the same signature as this method.
|
||||||
|
|
||||||
|
Produces tab completions for ``argument_parser``. See module docs for more info.
|
||||||
|
|
||||||
|
Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
|
||||||
|
added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
|
||||||
|
their execution is otherwise desirable.
|
||||||
|
"""
|
||||||
|
self.__init__( # type: ignore
|
||||||
|
argument_parser,
|
||||||
|
always_complete_options=always_complete_options,
|
||||||
|
exclude=exclude,
|
||||||
|
validator=validator,
|
||||||
|
print_suppressed=print_suppressed,
|
||||||
|
append_space=append_space,
|
||||||
|
default_completer=default_completer,
|
||||||
|
)
|
||||||
|
|
||||||
|
if "_ARGCOMPLETE" not in os.environ:
|
||||||
|
# not an argument completion invocation
|
||||||
|
return
|
||||||
|
|
||||||
|
self._init_debug_stream()
|
||||||
|
|
||||||
|
if output_stream is None:
|
||||||
|
filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME")
|
||||||
|
if filename is not None:
|
||||||
|
debug("Using output file {}".format(filename))
|
||||||
|
output_stream = open(filename, "w")
|
||||||
|
|
||||||
|
if output_stream is None:
|
||||||
|
try:
|
||||||
|
output_stream = os.fdopen(8, "w")
|
||||||
|
except Exception:
|
||||||
|
debug("Unable to open fd 8 for writing, quitting")
|
||||||
|
exit_method(1)
|
||||||
|
|
||||||
|
assert output_stream is not None
|
||||||
|
|
||||||
|
ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
|
||||||
|
if len(ifs) != 1:
|
||||||
|
debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
|
||||||
|
exit_method(1)
|
||||||
|
|
||||||
|
dfs = os.environ.get("_ARGCOMPLETE_DFS")
|
||||||
|
if dfs and len(dfs) != 1:
|
||||||
|
debug("Invalid value for DFS, quitting [{v}]".format(v=dfs))
|
||||||
|
exit_method(1)
|
||||||
|
|
||||||
|
comp_line = os.environ["COMP_LINE"]
|
||||||
|
comp_point = int(os.environ["COMP_POINT"])
|
||||||
|
|
||||||
|
cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)
|
||||||
|
|
||||||
|
# _ARGCOMPLETE is set by the shell script to tell us where comp_words
|
||||||
|
# should start, based on what we're completing.
|
||||||
|
# 1: <script> [args]
|
||||||
|
# 2: python <script> [args]
|
||||||
|
# 3: python -m <module> [args]
|
||||||
|
start = int(os.environ["_ARGCOMPLETE"]) - 1
|
||||||
|
comp_words = comp_words[start:]
|
||||||
|
|
||||||
|
if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix:
|
||||||
|
# Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser.
|
||||||
|
comp_words.append(cword_prefix.split("=", 1)[0])
|
||||||
|
|
||||||
|
debug(
|
||||||
|
"\nLINE: {!r}".format(comp_line),
|
||||||
|
"\nPOINT: {!r}".format(comp_point),
|
||||||
|
"\nPREQUOTE: {!r}".format(cword_prequote),
|
||||||
|
"\nPREFIX: {!r}".format(cword_prefix),
|
||||||
|
"\nSUFFIX: {!r}".format(cword_suffix),
|
||||||
|
"\nWORDS:",
|
||||||
|
comp_words,
|
||||||
|
)
|
||||||
|
|
||||||
|
completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)
|
||||||
|
|
||||||
|
if dfs:
|
||||||
|
display_completions = {
|
||||||
|
key: value.replace(ifs, " ") if value else "" for key, value in self._display_completions.items()
|
||||||
|
}
|
||||||
|
completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]
|
||||||
|
|
||||||
|
if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
|
||||||
|
completions = [f"{c}:{self._display_completions.get(c)}" for c in completions]
|
||||||
|
|
||||||
|
debug("\nReturning completions:", completions)
|
||||||
|
output_stream.write(ifs.join(completions))
|
||||||
|
output_stream.flush()
|
||||||
|
_io.debug_stream.flush()
|
||||||
|
exit_method(0)
|
||||||
|
|
||||||
|
def _init_debug_stream(self):
|
||||||
|
"""Initialize debug output stream
|
||||||
|
|
||||||
|
By default, writes to file descriptor 9, or stderr if that fails.
|
||||||
|
This can be overridden by derived classes, for example to avoid
|
||||||
|
clashes with file descriptors being used elsewhere (such as in pytest).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_io.debug_stream = os.fdopen(9, "w")
|
||||||
|
except Exception:
|
||||||
|
_io.debug_stream = sys.stderr
|
||||||
|
debug()
|
||||||
|
|
||||||
|
def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos):
|
||||||
|
active_parsers = self._patch_argument_parser()
|
||||||
|
|
||||||
|
parsed_args = argparse.Namespace()
|
||||||
|
self.completing = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
debug("invoking parser with", comp_words[1:])
|
||||||
|
with mute_stderr():
|
||||||
|
a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args)
|
||||||
|
debug("parsed args:", a)
|
||||||
|
except BaseException as e:
|
||||||
|
debug("\nexception", type(e), str(e), "while parsing args")
|
||||||
|
|
||||||
|
self.completing = False
|
||||||
|
|
||||||
|
if "--" in comp_words:
|
||||||
|
self.always_complete_options = False
|
||||||
|
|
||||||
|
completions = self.collect_completions(active_parsers, parsed_args, cword_prefix)
|
||||||
|
completions = self.filter_completions(completions)
|
||||||
|
completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos)
|
||||||
|
return completions
|
||||||
|
|
||||||
|
def _patch_argument_parser(self):
|
||||||
|
"""
|
||||||
|
Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and
|
||||||
|
all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser
|
||||||
|
figure out which subparsers need to be activated (then recursively monkey-patch those).
|
||||||
|
We save all active ArgumentParsers to extract all their possible option names later.
|
||||||
|
"""
|
||||||
|
self.active_parsers: List[argparse.ArgumentParser] = []
|
||||||
|
self.visited_positionals: List[argparse.Action] = []
|
||||||
|
|
||||||
|
completer = self
|
||||||
|
|
||||||
|
def patch(parser):
|
||||||
|
completer.visited_positionals.append(parser)
|
||||||
|
completer.active_parsers.append(parser)
|
||||||
|
|
||||||
|
if isinstance(parser, IntrospectiveArgumentParser):
|
||||||
|
return
|
||||||
|
|
||||||
|
classname = "MonkeyPatchedIntrospectiveArgumentParser"
|
||||||
|
|
||||||
|
parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {})
|
||||||
|
|
||||||
|
for action in parser._actions:
|
||||||
|
if hasattr(action, "_orig_class"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# TODO: accomplish this with super
|
||||||
|
class IntrospectAction(action.__class__): # type: ignore
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
debug("Action stub called on", self)
|
||||||
|
debug("\targs:", parser, namespace, values, option_string)
|
||||||
|
debug("\torig class:", self._orig_class)
|
||||||
|
debug("\torig callable:", self._orig_callable)
|
||||||
|
|
||||||
|
if not completer.completing:
|
||||||
|
self._orig_callable(parser, namespace, values, option_string=option_string)
|
||||||
|
elif issubclass(self._orig_class, argparse._SubParsersAction):
|
||||||
|
debug("orig class is a subparsers action: patching and running it")
|
||||||
|
patch(self._name_parser_map[values[0]])
|
||||||
|
self._orig_callable(parser, namespace, values, option_string=option_string)
|
||||||
|
elif self._orig_class in safe_actions:
|
||||||
|
if not self.option_strings:
|
||||||
|
completer.visited_positionals.append(self)
|
||||||
|
|
||||||
|
self._orig_callable(parser, namespace, values, option_string=option_string)
|
||||||
|
|
||||||
|
action._orig_class = action.__class__
|
||||||
|
action._orig_callable = action.__call__
|
||||||
|
action.__class__ = IntrospectAction
|
||||||
|
|
||||||
|
patch(self._parser)
|
||||||
|
|
||||||
|
debug("Active parsers:", self.active_parsers)
|
||||||
|
debug("Visited positionals:", self.visited_positionals)
|
||||||
|
|
||||||
|
return self.active_parsers
|
||||||
|
|
||||||
|
def _get_action_help(self, action):
|
||||||
|
if action.help is None:
|
||||||
|
return ""
|
||||||
|
if "%" not in action.help:
|
||||||
|
return action.help
|
||||||
|
if self._formatter is None:
|
||||||
|
self._formatter = self._parser.formatter_class(prog=self._parser.prog)
|
||||||
|
return self._formatter._expand_help(action)
|
||||||
|
|
||||||
|
def _get_subparser_completions(self, parser, cword_prefix):
|
||||||
|
aliases_by_parser: Dict[argparse.ArgumentParser, List[str]] = {}
|
||||||
|
for key in parser.choices.keys():
|
||||||
|
p = parser.choices[key]
|
||||||
|
aliases_by_parser.setdefault(p, []).append(key)
|
||||||
|
|
||||||
|
for action in parser._get_subactions():
|
||||||
|
for alias in aliases_by_parser[parser.choices[action.dest]]:
|
||||||
|
if alias.startswith(cword_prefix):
|
||||||
|
self._display_completions[alias] = self._get_action_help(action)
|
||||||
|
|
||||||
|
completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)]
|
||||||
|
return completions
|
||||||
|
|
||||||
|
def _include_options(self, action, cword_prefix):
|
||||||
|
if len(cword_prefix) > 0 or self.always_complete_options is True:
|
||||||
|
return [opt for opt in action.option_strings if opt.startswith(cword_prefix)]
|
||||||
|
long_opts = [opt for opt in action.option_strings if len(opt) > 2]
|
||||||
|
short_opts = [opt for opt in action.option_strings if len(opt) <= 2]
|
||||||
|
if self.always_complete_options == "long":
|
||||||
|
return long_opts if long_opts else short_opts
|
||||||
|
elif self.always_complete_options == "short":
|
||||||
|
return short_opts if short_opts else long_opts
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _get_option_completions(self, parser, cword_prefix):
|
||||||
|
for action in parser._actions:
|
||||||
|
if action.option_strings:
|
||||||
|
for option_string in action.option_strings:
|
||||||
|
if option_string.startswith(cword_prefix):
|
||||||
|
self._display_completions[option_string] = self._get_action_help(action)
|
||||||
|
|
||||||
|
option_completions = []
|
||||||
|
for action in parser._actions:
|
||||||
|
if not self.print_suppressed:
|
||||||
|
completer = getattr(action, "completer", None)
|
||||||
|
if isinstance(completer, SuppressCompleter) and completer.suppress():
|
||||||
|
continue
|
||||||
|
if action.help == argparse.SUPPRESS:
|
||||||
|
continue
|
||||||
|
if not self._action_allowed(action, parser):
|
||||||
|
continue
|
||||||
|
if not isinstance(action, argparse._SubParsersAction):
|
||||||
|
option_completions += self._include_options(action, cword_prefix)
|
||||||
|
return option_completions
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _action_allowed(action, parser):
|
||||||
|
# Logic adapted from take_action in ArgumentParser._parse_known_args
|
||||||
|
# (members are saved by vendor._argparse.IntrospectiveArgumentParser)
|
||||||
|
for conflict_action in parser._action_conflicts.get(action, []):
|
||||||
|
if conflict_action in parser._seen_non_default_actions:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions):
|
||||||
|
debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions))
|
||||||
|
|
||||||
|
isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars
|
||||||
|
optional_prefix = ""
|
||||||
|
greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)]
|
||||||
|
if greedy_actions:
|
||||||
|
assert len(greedy_actions) == 1, "expect at most 1 greedy action"
|
||||||
|
# This means the action will fail to parse if the word under the cursor is not given
|
||||||
|
# to it, so give it exclusive control over completions (flush previous completions)
|
||||||
|
debug("Resetting completions because", greedy_actions[0], "must consume the next argument")
|
||||||
|
self._display_completions = {}
|
||||||
|
completions = []
|
||||||
|
elif isoptional:
|
||||||
|
if "=" in cword_prefix:
|
||||||
|
# Special case for when the current word is "--optional=PARTIAL_VALUE".
|
||||||
|
# The completer runs on PARTIAL_VALUE. The prefix is added back to the completions
|
||||||
|
# (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic).
|
||||||
|
optional_prefix, _, cword_prefix = cword_prefix.partition("=")
|
||||||
|
else:
|
||||||
|
# Only run completers if current word does not start with - (is not an optional)
|
||||||
|
return completions
|
||||||
|
|
||||||
|
complete_remaining_positionals = False
|
||||||
|
# Use the single greedy action (if there is one) or all active actions.
|
||||||
|
for active_action in greedy_actions or parser.active_actions:
|
||||||
|
if not active_action.option_strings: # action is a positional
|
||||||
|
if action_is_open(active_action):
|
||||||
|
# Any positional arguments after this may slide down into this action
|
||||||
|
# if more arguments are added (since the user may not be done yet),
|
||||||
|
# so it is extremely difficult to tell which completers to run.
|
||||||
|
# Running all remaining completers will probably show more than the user wants
|
||||||
|
# but it also guarantees we won't miss anything.
|
||||||
|
complete_remaining_positionals = True
|
||||||
|
if not complete_remaining_positionals:
|
||||||
|
if action_is_satisfied(active_action) and not action_is_open(active_action):
|
||||||
|
debug("Skipping", active_action)
|
||||||
|
continue
|
||||||
|
|
||||||
|
debug("Activating completion for", active_action, active_action._orig_class)
|
||||||
|
# completer = getattr(active_action, "completer", DefaultCompleter())
|
||||||
|
completer = getattr(active_action, "completer", None)
|
||||||
|
|
||||||
|
if completer is None:
|
||||||
|
if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction):
|
||||||
|
completer = ChoicesCompleter(active_action.choices)
|
||||||
|
elif not isinstance(active_action, argparse._SubParsersAction):
|
||||||
|
completer = self.default_completer
|
||||||
|
|
||||||
|
if completer:
|
||||||
|
if isinstance(completer, SuppressCompleter) and completer.suppress():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if callable(completer):
|
||||||
|
completer_output = completer(
|
||||||
|
prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args
|
||||||
|
)
|
||||||
|
if isinstance(completer_output, Mapping):
|
||||||
|
for completion, description in completer_output.items():
|
||||||
|
if self.validator(completion, cword_prefix):
|
||||||
|
completions.append(completion)
|
||||||
|
self._display_completions[completion] = description
|
||||||
|
else:
|
||||||
|
for completion in completer_output:
|
||||||
|
if self.validator(completion, cword_prefix):
|
||||||
|
completions.append(completion)
|
||||||
|
if isinstance(completer, ChoicesCompleter):
|
||||||
|
self._display_completions[completion] = self._get_action_help(active_action)
|
||||||
|
else:
|
||||||
|
self._display_completions[completion] = ""
|
||||||
|
else:
|
||||||
|
debug("Completer is not callable, trying the readline completer protocol instead")
|
||||||
|
for i in range(9999):
|
||||||
|
next_completion = completer.complete(cword_prefix, i) # type: ignore
|
||||||
|
if next_completion is None:
|
||||||
|
break
|
||||||
|
if self.validator(next_completion, cword_prefix):
|
||||||
|
self._display_completions[next_completion] = ""
|
||||||
|
completions.append(next_completion)
|
||||||
|
if optional_prefix:
|
||||||
|
completions = [optional_prefix + "=" + completion for completion in completions]
|
||||||
|
debug("Completions:", completions)
|
||||||
|
return completions
|
||||||
|
|
||||||
|
def collect_completions(
|
||||||
|
self, active_parsers: List[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
Visits the active parsers and their actions, executes their completers or introspects them to collect their
|
||||||
|
option strings. Returns the resulting completions as a list of strings.
|
||||||
|
|
||||||
|
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
||||||
|
"""
|
||||||
|
completions: List[str] = []
|
||||||
|
|
||||||
|
debug("all active parsers:", active_parsers)
|
||||||
|
active_parser = active_parsers[-1]
|
||||||
|
debug("active_parser:", active_parser)
|
||||||
|
if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars):
|
||||||
|
completions += self._get_option_completions(active_parser, cword_prefix)
|
||||||
|
debug("optional options:", completions)
|
||||||
|
|
||||||
|
next_positional = self._get_next_positional()
|
||||||
|
debug("next_positional:", next_positional)
|
||||||
|
|
||||||
|
if isinstance(next_positional, argparse._SubParsersAction):
|
||||||
|
completions += self._get_subparser_completions(next_positional, cword_prefix)
|
||||||
|
|
||||||
|
completions = self._complete_active_option(
|
||||||
|
active_parser, next_positional, cword_prefix, parsed_args, completions
|
||||||
|
)
|
||||||
|
debug("active options:", completions)
|
||||||
|
debug("display completions:", self._display_completions)
|
||||||
|
|
||||||
|
return completions
|
||||||
|
|
||||||
|
def _get_next_positional(self):
|
||||||
|
"""
|
||||||
|
Get the next positional action if it exists.
|
||||||
|
"""
|
||||||
|
active_parser = self.active_parsers[-1]
|
||||||
|
last_positional = self.visited_positionals[-1]
|
||||||
|
|
||||||
|
all_positionals = active_parser._get_positional_actions()
|
||||||
|
if not all_positionals:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if active_parser == last_positional:
|
||||||
|
return all_positionals[0]
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for i in range(len(all_positionals)):
|
||||||
|
if all_positionals[i] == last_positional:
|
||||||
|
break
|
||||||
|
|
||||||
|
if i + 1 < len(all_positionals):
|
||||||
|
return all_positionals[i + 1]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def filter_completions(self, completions: List[str]) -> List[str]:
|
||||||
|
"""
|
||||||
|
De-duplicates completions and excludes those specified by ``exclude``.
|
||||||
|
Returns the filtered completions as a list.
|
||||||
|
|
||||||
|
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
||||||
|
"""
|
||||||
|
filtered_completions = []
|
||||||
|
for completion in completions:
|
||||||
|
if self.exclude is not None:
|
||||||
|
if completion in self.exclude:
|
||||||
|
continue
|
||||||
|
if completion not in filtered_completions:
|
||||||
|
filtered_completions.append(completion)
|
||||||
|
return filtered_completions
|
||||||
|
|
||||||
|
def quote_completions(
|
||||||
|
self, completions: List[str], cword_prequote: str, last_wordbreak_pos: Optional[int]
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes
|
||||||
|
occurrences of that quote character in the completions, and adds the quote to the beginning of each completion.
|
||||||
|
Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of
|
||||||
|
completions before the first colon if (``COMP_WORDBREAKS``) contains a colon.
|
||||||
|
|
||||||
|
If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``),
|
||||||
|
adds a space after the completion.
|
||||||
|
|
||||||
|
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
||||||
|
"""
|
||||||
|
special_chars = "\\"
|
||||||
|
# If the word under the cursor was quoted, escape the quote char.
|
||||||
|
# Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars.
|
||||||
|
if cword_prequote == "":
|
||||||
|
# Bash mangles completions which contain characters in COMP_WORDBREAKS.
|
||||||
|
# This workaround has the same effect as __ltrim_colon_completions in bash_completion
|
||||||
|
# (extended to characters other than the colon).
|
||||||
|
if last_wordbreak_pos is not None:
|
||||||
|
completions = [c[last_wordbreak_pos + 1 :] for c in completions]
|
||||||
|
special_chars += "();<>|&!`$* \t\n\"'"
|
||||||
|
elif cword_prequote == '"':
|
||||||
|
special_chars += '"`$!'
|
||||||
|
|
||||||
|
if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"):
|
||||||
|
# tcsh and fish escapes special characters itself.
|
||||||
|
special_chars = ""
|
||||||
|
elif cword_prequote == "'":
|
||||||
|
# Nothing can be escaped in single quotes, so we need to close
|
||||||
|
# the string, escape the single quote, then open a new string.
|
||||||
|
special_chars = ""
|
||||||
|
completions = [c.replace("'", r"'\''") for c in completions]
|
||||||
|
|
||||||
|
# PowerShell uses ` as escape character.
|
||||||
|
if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell":
|
||||||
|
escape_char = '`'
|
||||||
|
special_chars = special_chars.replace('`', '')
|
||||||
|
else:
|
||||||
|
escape_char = "\\"
|
||||||
|
if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
|
||||||
|
# zsh uses colon as a separator between a completion and its description.
|
||||||
|
special_chars += ":"
|
||||||
|
|
||||||
|
escaped_completions = []
|
||||||
|
for completion in completions:
|
||||||
|
escaped_completion = completion
|
||||||
|
for char in special_chars:
|
||||||
|
escaped_completion = escaped_completion.replace(char, escape_char + char)
|
||||||
|
escaped_completions.append(escaped_completion)
|
||||||
|
if completion in self._display_completions:
|
||||||
|
self._display_completions[escaped_completion] = self._display_completions[completion]
|
||||||
|
|
||||||
|
if self.append_space:
|
||||||
|
# Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete.
|
||||||
|
# Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character.
|
||||||
|
# This code is retained for environments where this isn't done natively.
|
||||||
|
continuation_chars = "=/:"
|
||||||
|
if len(escaped_completions) == 1 and escaped_completions[0][-1] not in continuation_chars:
|
||||||
|
if cword_prequote == "":
|
||||||
|
escaped_completions[0] += " "
|
||||||
|
|
||||||
|
return escaped_completions
|
||||||
|
|
||||||
|
def rl_complete(self, text, state):
|
||||||
|
"""
|
||||||
|
Alternate entry point for using the argcomplete completer in a readline-based REPL. See also
|
||||||
|
`rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_.
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import argcomplete, argparse, readline
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
...
|
||||||
|
completer = argcomplete.CompletionFinder(parser)
|
||||||
|
readline.set_completer_delims("")
|
||||||
|
readline.set_completer(completer.rl_complete)
|
||||||
|
readline.parse_and_bind("tab: complete")
|
||||||
|
result = input("prompt> ")
|
||||||
|
"""
|
||||||
|
if state == 0:
|
||||||
|
cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text)
|
||||||
|
comp_words.insert(0, sys.argv[0])
|
||||||
|
matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos)
|
||||||
|
self._rl_matches = [text + match[len(cword_prefix) :] for match in matches]
|
||||||
|
|
||||||
|
if state < len(self._rl_matches):
|
||||||
|
return self._rl_matches[state]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_display_completions(self):
|
||||||
|
"""
|
||||||
|
This function returns a mapping of completions to their help strings for displaying to the user.
|
||||||
|
"""
|
||||||
|
return self._display_completions
|
||||||
|
|
||||||
|
|
||||||
|
class ExclusiveCompletionFinder(CompletionFinder):
|
||||||
|
@staticmethod
|
||||||
|
def _action_allowed(action, parser):
|
||||||
|
if not CompletionFinder._action_allowed(action, parser):
|
||||||
|
return False
|
||||||
|
|
||||||
|
append_classes = (argparse._AppendAction, argparse._AppendConstAction)
|
||||||
|
if action._orig_class in append_classes:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if action not in parser._seen_non_default_actions:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
42
.venv_codegen/Lib/site-packages/argcomplete/io.py
Normal file
42
.venv_codegen/Lib/site-packages/argcomplete/io.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_DEBUG = "_ARC_DEBUG" in os.environ
|
||||||
|
|
||||||
|
debug_stream = sys.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def debug(*args):
|
||||||
|
if _DEBUG:
|
||||||
|
print(file=debug_stream, *args)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mute_stdout():
|
||||||
|
stdout = sys.stdout
|
||||||
|
sys.stdout = open(os.devnull, "w")
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.stdout = stdout
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mute_stderr():
|
||||||
|
stderr = sys.stderr
|
||||||
|
sys.stderr = open(os.devnull, "w")
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.stderr.close()
|
||||||
|
sys.stderr = stderr
|
||||||
|
|
||||||
|
|
||||||
|
def warn(*args):
|
||||||
|
"""
|
||||||
|
Prints **args** to standard error when running completions. This will interrupt the user's command line interaction;
|
||||||
|
use it to indicate an error condition that is preventing your completer from working.
|
||||||
|
"""
|
||||||
|
print(file=debug_stream)
|
||||||
|
print(file=debug_stream, *args)
|
||||||
57
.venv_codegen/Lib/site-packages/argcomplete/lexers.py
Normal file
57
.venv_codegen/Lib/site-packages/argcomplete/lexers.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from .exceptions import ArgcompleteException
|
||||||
|
from .io import debug
|
||||||
|
from .packages import _shlex
|
||||||
|
|
||||||
|
|
||||||
|
def split_line(line, point=None):
|
||||||
|
if point is None:
|
||||||
|
point = len(line)
|
||||||
|
line = line[:point]
|
||||||
|
lexer = _shlex.shlex(line, posix=True)
|
||||||
|
lexer.whitespace_split = True
|
||||||
|
lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "")
|
||||||
|
words = []
|
||||||
|
|
||||||
|
def split_word(word):
|
||||||
|
# TODO: make this less ugly
|
||||||
|
point_in_word = len(word) + point - lexer.instream.tell()
|
||||||
|
if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace:
|
||||||
|
point_in_word += 1
|
||||||
|
if point_in_word > len(word):
|
||||||
|
debug("In trailing whitespace")
|
||||||
|
words.append(word)
|
||||||
|
word = ""
|
||||||
|
prefix, suffix = word[:point_in_word], word[point_in_word:]
|
||||||
|
prequote = ""
|
||||||
|
# posix
|
||||||
|
if lexer.state is not None and lexer.state in lexer.quotes:
|
||||||
|
prequote = lexer.state
|
||||||
|
# non-posix
|
||||||
|
# if len(prefix) > 0 and prefix[0] in lexer.quotes:
|
||||||
|
# prequote, prefix = prefix[0], prefix[1:]
|
||||||
|
|
||||||
|
return prequote, prefix, suffix, words, lexer.last_wordbreak_pos
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
word = lexer.get_token()
|
||||||
|
if word == lexer.eof:
|
||||||
|
# TODO: check if this is ever unsafe
|
||||||
|
# raise ArgcompleteException("Unexpected end of input")
|
||||||
|
return "", "", "", words, None
|
||||||
|
if lexer.instream.tell() >= point:
|
||||||
|
debug("word", word, "split, lexer state: '{s}'".format(s=lexer.state))
|
||||||
|
return split_word(word)
|
||||||
|
words.append(word)
|
||||||
|
except ValueError:
|
||||||
|
debug("word", lexer.token, "split (lexer stopped, state: '{s}')".format(s=lexer.state))
|
||||||
|
if lexer.instream.tell() >= point:
|
||||||
|
return split_word(lexer.token)
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
"Unexpected internal state. "
|
||||||
|
"Please report this bug at https://github.com/kislyuk/argcomplete/issues."
|
||||||
|
)
|
||||||
|
raise ArgcompleteException(msg)
|
||||||
|
|
@ -0,0 +1,342 @@
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
|
||||||
|
# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
|
||||||
|
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
|
||||||
|
# See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
# This file contains argparse introspection utilities used in the course of argcomplete execution.
|
||||||
|
|
||||||
|
from argparse import (
|
||||||
|
ONE_OR_MORE,
|
||||||
|
OPTIONAL,
|
||||||
|
PARSER,
|
||||||
|
REMAINDER,
|
||||||
|
SUPPRESS,
|
||||||
|
ZERO_OR_MORE,
|
||||||
|
Action,
|
||||||
|
ArgumentError,
|
||||||
|
ArgumentParser,
|
||||||
|
_get_action_name,
|
||||||
|
_SubParsersAction,
|
||||||
|
)
|
||||||
|
from gettext import gettext
|
||||||
|
from typing import Dict, List, Set, Tuple
|
||||||
|
|
||||||
|
_num_consumed_args: Dict[Action, int] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def action_is_satisfied(action):
|
||||||
|
'''Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.'''
|
||||||
|
num_consumed_args = _num_consumed_args.get(action, 0)
|
||||||
|
|
||||||
|
if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]:
|
||||||
|
return True
|
||||||
|
if action.nargs == ONE_OR_MORE:
|
||||||
|
return num_consumed_args >= 1
|
||||||
|
if action.nargs == PARSER:
|
||||||
|
# Not sure what this should be, but this previously always returned False
|
||||||
|
# so at least this won't break anything that wasn't already broken.
|
||||||
|
return False
|
||||||
|
if action.nargs is None:
|
||||||
|
return num_consumed_args == 1
|
||||||
|
|
||||||
|
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
|
||||||
|
return num_consumed_args == action.nargs
|
||||||
|
|
||||||
|
|
||||||
|
def action_is_open(action):
|
||||||
|
'''Returns True if action could consume more arguments (i.e., its pattern is open).'''
|
||||||
|
num_consumed_args = _num_consumed_args.get(action, 0)
|
||||||
|
|
||||||
|
if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]:
|
||||||
|
return True
|
||||||
|
if action.nargs == OPTIONAL or action.nargs is None:
|
||||||
|
return num_consumed_args == 0
|
||||||
|
|
||||||
|
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
|
||||||
|
return num_consumed_args < action.nargs
|
||||||
|
|
||||||
|
|
||||||
|
def action_is_greedy(action, isoptional=False):
|
||||||
|
'''Returns True if action will necessarily consume the next argument.
|
||||||
|
isoptional indicates whether the argument is an optional (starts with -).
|
||||||
|
'''
|
||||||
|
num_consumed_args = _num_consumed_args.get(action, 0)
|
||||||
|
|
||||||
|
if action.option_strings:
|
||||||
|
if not isoptional and not action_is_satisfied(action):
|
||||||
|
return True
|
||||||
|
return action.nargs == REMAINDER
|
||||||
|
else:
|
||||||
|
return action.nargs == REMAINDER and num_consumed_args >= 1
|
||||||
|
|
||||||
|
|
||||||
|
class IntrospectiveArgumentParser(ArgumentParser):
|
||||||
|
'''The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3),
|
||||||
|
except for the lines that contain the string "Added by argcomplete".
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _parse_known_args(self, arg_strings, namespace, intermixed=False, **kwargs):
|
||||||
|
_num_consumed_args.clear() # Added by argcomplete
|
||||||
|
self._argcomplete_namespace = namespace
|
||||||
|
self.active_actions: List[Action] = [] # Added by argcomplete
|
||||||
|
# replace arg strings that are file references
|
||||||
|
if self.fromfile_prefix_chars is not None:
|
||||||
|
arg_strings = self._read_args_from_files(arg_strings)
|
||||||
|
|
||||||
|
# map all mutually exclusive arguments to the other arguments
|
||||||
|
# they can't occur with
|
||||||
|
action_conflicts: Dict[Action, List[Action]] = {}
|
||||||
|
self._action_conflicts = action_conflicts # Added by argcomplete
|
||||||
|
for mutex_group in self._mutually_exclusive_groups:
|
||||||
|
group_actions = mutex_group._group_actions
|
||||||
|
for i, mutex_action in enumerate(mutex_group._group_actions):
|
||||||
|
conflicts = action_conflicts.setdefault(mutex_action, [])
|
||||||
|
conflicts.extend(group_actions[:i])
|
||||||
|
conflicts.extend(group_actions[i + 1 :])
|
||||||
|
|
||||||
|
# find all option indices, and determine the arg_string_pattern
|
||||||
|
# which has an 'O' if there is an option at an index,
|
||||||
|
# an 'A' if there is an argument, or a '-' if there is a '--'
|
||||||
|
option_string_indices = {}
|
||||||
|
arg_string_pattern_parts = []
|
||||||
|
arg_strings_iter = iter(arg_strings)
|
||||||
|
for i, arg_string in enumerate(arg_strings_iter):
|
||||||
|
# all args after -- are non-options
|
||||||
|
if arg_string == '--':
|
||||||
|
arg_string_pattern_parts.append('-')
|
||||||
|
for arg_string in arg_strings_iter:
|
||||||
|
arg_string_pattern_parts.append('A')
|
||||||
|
|
||||||
|
# otherwise, add the arg to the arg strings
|
||||||
|
# and note the index if it was an option
|
||||||
|
else:
|
||||||
|
option_tuple = self._parse_optional(arg_string)
|
||||||
|
if option_tuple is None:
|
||||||
|
pattern = 'A'
|
||||||
|
else:
|
||||||
|
option_string_indices[i] = option_tuple
|
||||||
|
pattern = 'O'
|
||||||
|
arg_string_pattern_parts.append(pattern)
|
||||||
|
|
||||||
|
# join the pieces together to form the pattern
|
||||||
|
arg_strings_pattern = ''.join(arg_string_pattern_parts)
|
||||||
|
|
||||||
|
# converts arg strings to the appropriate and then takes the action
|
||||||
|
seen_actions: Set[Action] = set()
|
||||||
|
seen_non_default_actions: Set[Action] = set()
|
||||||
|
self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete
|
||||||
|
|
||||||
|
def take_action(action, argument_strings, option_string=None):
|
||||||
|
seen_actions.add(action)
|
||||||
|
argument_values = self._get_values(action, argument_strings)
|
||||||
|
|
||||||
|
# error if this argument is not allowed with other previously
|
||||||
|
# seen arguments, assuming that actions that use the default
|
||||||
|
# value don't really count as "present"
|
||||||
|
if argument_values is not action.default:
|
||||||
|
seen_non_default_actions.add(action)
|
||||||
|
for conflict_action in action_conflicts.get(action, []):
|
||||||
|
if conflict_action in seen_non_default_actions:
|
||||||
|
msg = gettext('not allowed with argument %s')
|
||||||
|
action_name = _get_action_name(conflict_action)
|
||||||
|
raise ArgumentError(action, msg % action_name)
|
||||||
|
|
||||||
|
# take the action if we didn't receive a SUPPRESS value
|
||||||
|
# (e.g. from a default)
|
||||||
|
if argument_values is not SUPPRESS or isinstance(action, _SubParsersAction):
|
||||||
|
try:
|
||||||
|
action(self, namespace, argument_values, option_string)
|
||||||
|
except BaseException:
|
||||||
|
# Begin added by argcomplete
|
||||||
|
# When a subparser action is taken and fails due to incomplete arguments, it does not merge the
|
||||||
|
# contents of its parsed namespace into the parent namespace. Do that here to allow completers to
|
||||||
|
# access the partially parsed arguments for the subparser.
|
||||||
|
if isinstance(action, _SubParsersAction):
|
||||||
|
subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace
|
||||||
|
for key, value in vars(subnamespace).items():
|
||||||
|
setattr(namespace, key, value)
|
||||||
|
# End added by argcomplete
|
||||||
|
raise
|
||||||
|
|
||||||
|
# function to convert arg_strings into an optional action
|
||||||
|
def consume_optional(start_index):
|
||||||
|
# get the optional identified at this index
|
||||||
|
option_tuple = option_string_indices[start_index]
|
||||||
|
if isinstance(option_tuple, list): # Python 3.12.7+
|
||||||
|
option_tuple = option_tuple[0]
|
||||||
|
if len(option_tuple) == 3:
|
||||||
|
action, option_string, explicit_arg = option_tuple
|
||||||
|
else: # Python 3.11.9+, 3.12.3+, 3.13+
|
||||||
|
action, option_string, _, explicit_arg = option_tuple
|
||||||
|
|
||||||
|
# identify additional optionals in the same arg string
|
||||||
|
# (e.g. -xyz is the same as -x -y -z if no args are required)
|
||||||
|
match_argument = self._match_argument
|
||||||
|
action_tuples: List[Tuple[Action, List[str], str]] = []
|
||||||
|
while True:
|
||||||
|
# if we found no optional action, skip it
|
||||||
|
if action is None:
|
||||||
|
extras.append(arg_strings[start_index])
|
||||||
|
return start_index + 1
|
||||||
|
|
||||||
|
# if there is an explicit argument, try to match the
|
||||||
|
# optional's string arguments to only this
|
||||||
|
if explicit_arg is not None:
|
||||||
|
arg_count = match_argument(action, 'A')
|
||||||
|
|
||||||
|
# if the action is a single-dash option and takes no
|
||||||
|
# arguments, try to parse more single-dash options out
|
||||||
|
# of the tail of the option string
|
||||||
|
chars = self.prefix_chars
|
||||||
|
if arg_count == 0 and option_string[1] not in chars:
|
||||||
|
action_tuples.append((action, [], option_string))
|
||||||
|
char = option_string[0]
|
||||||
|
option_string = char + explicit_arg[0]
|
||||||
|
new_explicit_arg = explicit_arg[1:] or None
|
||||||
|
optionals_map = self._option_string_actions
|
||||||
|
if option_string in optionals_map:
|
||||||
|
action = optionals_map[option_string]
|
||||||
|
explicit_arg = new_explicit_arg
|
||||||
|
else:
|
||||||
|
msg = gettext('ignored explicit argument %r')
|
||||||
|
raise ArgumentError(action, msg % explicit_arg)
|
||||||
|
|
||||||
|
# if the action expect exactly one argument, we've
|
||||||
|
# successfully matched the option; exit the loop
|
||||||
|
elif arg_count == 1:
|
||||||
|
stop = start_index + 1
|
||||||
|
args = [explicit_arg]
|
||||||
|
action_tuples.append((action, args, option_string))
|
||||||
|
break
|
||||||
|
|
||||||
|
# error if a double-dash option did not use the
|
||||||
|
# explicit argument
|
||||||
|
else:
|
||||||
|
msg = gettext('ignored explicit argument %r')
|
||||||
|
raise ArgumentError(action, msg % explicit_arg)
|
||||||
|
|
||||||
|
# if there is no explicit argument, try to match the
|
||||||
|
# optional's string arguments with the following strings
|
||||||
|
# if successful, exit the loop
|
||||||
|
else:
|
||||||
|
start = start_index + 1
|
||||||
|
selected_patterns = arg_strings_pattern[start:]
|
||||||
|
self.active_actions = [action] # Added by argcomplete
|
||||||
|
_num_consumed_args[action] = 0 # Added by argcomplete
|
||||||
|
arg_count = match_argument(action, selected_patterns)
|
||||||
|
stop = start + arg_count
|
||||||
|
args = arg_strings[start:stop]
|
||||||
|
|
||||||
|
# Begin added by argcomplete
|
||||||
|
# If the pattern is not open (e.g. no + at the end), remove the action from active actions (since
|
||||||
|
# it wouldn't be able to consume any more args)
|
||||||
|
_num_consumed_args[action] = len(args)
|
||||||
|
if not action_is_open(action):
|
||||||
|
self.active_actions.remove(action)
|
||||||
|
# End added by argcomplete
|
||||||
|
|
||||||
|
action_tuples.append((action, args, option_string))
|
||||||
|
break
|
||||||
|
|
||||||
|
# add the Optional to the list and return the index at which
|
||||||
|
# the Optional's string args stopped
|
||||||
|
assert action_tuples
|
||||||
|
for action, args, option_string in action_tuples:
|
||||||
|
take_action(action, args, option_string)
|
||||||
|
return stop
|
||||||
|
|
||||||
|
# the list of Positionals left to be parsed; this is modified
|
||||||
|
# by consume_positionals()
|
||||||
|
positionals = self._get_positional_actions()
|
||||||
|
|
||||||
|
# function to convert arg_strings into positional actions
|
||||||
|
def consume_positionals(start_index):
|
||||||
|
# match as many Positionals as possible
|
||||||
|
match_partial = self._match_arguments_partial
|
||||||
|
selected_pattern = arg_strings_pattern[start_index:]
|
||||||
|
arg_counts = match_partial(positionals, selected_pattern)
|
||||||
|
|
||||||
|
# slice off the appropriate arg strings for each Positional
|
||||||
|
# and add the Positional and its args to the list
|
||||||
|
for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete
|
||||||
|
self.active_actions.append(action) # Added by argcomplete
|
||||||
|
for action, arg_count in zip(positionals, arg_counts):
|
||||||
|
args = arg_strings[start_index : start_index + arg_count]
|
||||||
|
start_index += arg_count
|
||||||
|
_num_consumed_args[action] = len(args) # Added by argcomplete
|
||||||
|
take_action(action, args)
|
||||||
|
|
||||||
|
# slice off the Positionals that we just parsed and return the
|
||||||
|
# index at which the Positionals' string args stopped
|
||||||
|
positionals[:] = positionals[len(arg_counts) :]
|
||||||
|
return start_index
|
||||||
|
|
||||||
|
# consume Positionals and Optionals alternately, until we have
|
||||||
|
# passed the last option string
|
||||||
|
extras = []
|
||||||
|
start_index = 0
|
||||||
|
if option_string_indices:
|
||||||
|
max_option_string_index = max(option_string_indices)
|
||||||
|
else:
|
||||||
|
max_option_string_index = -1
|
||||||
|
while start_index <= max_option_string_index:
|
||||||
|
# consume any Positionals preceding the next option
|
||||||
|
next_option_string_index = min([index for index in option_string_indices if index >= start_index])
|
||||||
|
if start_index != next_option_string_index:
|
||||||
|
positionals_end_index = consume_positionals(start_index)
|
||||||
|
|
||||||
|
# only try to parse the next optional if we didn't consume
|
||||||
|
# the option string during the positionals parsing
|
||||||
|
if positionals_end_index > start_index:
|
||||||
|
start_index = positionals_end_index
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
start_index = positionals_end_index
|
||||||
|
|
||||||
|
# if we consumed all the positionals we could and we're not
|
||||||
|
# at the index of an option string, there were extra arguments
|
||||||
|
if start_index not in option_string_indices:
|
||||||
|
strings = arg_strings[start_index:next_option_string_index]
|
||||||
|
extras.extend(strings)
|
||||||
|
start_index = next_option_string_index
|
||||||
|
|
||||||
|
# consume the next optional and any arguments for it
|
||||||
|
start_index = consume_optional(start_index)
|
||||||
|
|
||||||
|
# consume any positionals following the last Optional
|
||||||
|
stop_index = consume_positionals(start_index)
|
||||||
|
|
||||||
|
# if we didn't consume all the argument strings, there were extras
|
||||||
|
extras.extend(arg_strings[stop_index:])
|
||||||
|
|
||||||
|
# if we didn't use all the Positional objects, there were too few
|
||||||
|
# arg strings supplied.
|
||||||
|
|
||||||
|
if positionals:
|
||||||
|
self.active_actions.append(positionals[0]) # Added by argcomplete
|
||||||
|
self.error(gettext('too few arguments'))
|
||||||
|
|
||||||
|
# make sure all required actions were present
|
||||||
|
for action in self._actions:
|
||||||
|
if action.required:
|
||||||
|
if action not in seen_actions:
|
||||||
|
name = _get_action_name(action)
|
||||||
|
self.error(gettext('argument %s is required') % name)
|
||||||
|
|
||||||
|
# make sure all required groups had one option present
|
||||||
|
for group in self._mutually_exclusive_groups:
|
||||||
|
if group.required:
|
||||||
|
for action in group._group_actions:
|
||||||
|
if action in seen_non_default_actions:
|
||||||
|
break
|
||||||
|
|
||||||
|
# if no actions were used, report the error
|
||||||
|
else:
|
||||||
|
names = [
|
||||||
|
str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS
|
||||||
|
]
|
||||||
|
msg = gettext('one of the arguments %s is required')
|
||||||
|
self.error(msg % ' '.join(names))
|
||||||
|
|
||||||
|
# return the updated namespace and the extra arguments
|
||||||
|
return namespace, extras
|
||||||
313
.venv_codegen/Lib/site-packages/argcomplete/packages/_shlex.py
Normal file
313
.venv_codegen/Lib/site-packages/argcomplete/packages/_shlex.py
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
# This copy of shlex.py from Python 3.6 is distributed with argcomplete.
|
||||||
|
# It contains only the shlex class, with modifications as noted.
|
||||||
|
|
||||||
|
"""A lexical analyzer class for simple shell-like syntaxes."""
|
||||||
|
|
||||||
|
# Module and documentation by Eric S. Raymond, 21 Dec 1998
|
||||||
|
# Input stacking and error message cleanup added by ESR, March 2000
|
||||||
|
# push_source() and pop_source() made explicit by ESR, January 2001.
|
||||||
|
# Posix compliance, split(), string arguments, and
|
||||||
|
# iterator interface by Gustavo Niemeyer, April 2003.
|
||||||
|
# changes to tokenize more like Posix shells by Vinay Sajip, July 2016.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections import deque
|
||||||
|
from io import StringIO
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class shlex:
|
||||||
|
"A lexical analyzer class for simple shell-like syntaxes."
|
||||||
|
|
||||||
|
def __init__(self, instream=None, infile=None, posix=False, punctuation_chars=False):
|
||||||
|
# Modified by argcomplete: 2/3 compatibility
|
||||||
|
if isinstance(instream, str):
|
||||||
|
instream = StringIO(instream)
|
||||||
|
if instream is not None:
|
||||||
|
self.instream = instream
|
||||||
|
self.infile = infile
|
||||||
|
else:
|
||||||
|
self.instream = sys.stdin
|
||||||
|
self.infile = None
|
||||||
|
self.posix = posix
|
||||||
|
if posix:
|
||||||
|
self.eof = None
|
||||||
|
else:
|
||||||
|
self.eof = ''
|
||||||
|
self.commenters = '#'
|
||||||
|
self.wordchars = 'abcdfeghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
|
||||||
|
# Modified by argcomplete: 2/3 compatibility
|
||||||
|
# if self.posix:
|
||||||
|
# self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
||||||
|
# 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
||||||
|
self.whitespace = ' \t\r\n'
|
||||||
|
self.whitespace_split = False
|
||||||
|
self.quotes = '\'"'
|
||||||
|
self.escape = '\\'
|
||||||
|
self.escapedquotes = '"'
|
||||||
|
self.state: Optional[str] = ' '
|
||||||
|
self.pushback: deque = deque()
|
||||||
|
self.lineno = 1
|
||||||
|
self.debug = 0
|
||||||
|
self.token = ''
|
||||||
|
self.filestack: deque = deque()
|
||||||
|
self.source = None
|
||||||
|
if not punctuation_chars:
|
||||||
|
punctuation_chars = ''
|
||||||
|
elif punctuation_chars is True:
|
||||||
|
punctuation_chars = '();<>|&'
|
||||||
|
self.punctuation_chars = punctuation_chars
|
||||||
|
if punctuation_chars:
|
||||||
|
# _pushback_chars is a push back queue used by lookahead logic
|
||||||
|
self._pushback_chars: deque = deque()
|
||||||
|
# these chars added because allowed in file names, args, wildcards
|
||||||
|
self.wordchars += '~-./*?='
|
||||||
|
# remove any punctuation chars from wordchars
|
||||||
|
t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars))
|
||||||
|
self.wordchars = self.wordchars.translate(t)
|
||||||
|
|
||||||
|
# Modified by argcomplete: Record last wordbreak position
|
||||||
|
self.last_wordbreak_pos = None
|
||||||
|
self.wordbreaks = ''
|
||||||
|
|
||||||
|
def push_token(self, tok):
|
||||||
|
"Push a token onto the stack popped by the get_token method"
|
||||||
|
if self.debug >= 1:
|
||||||
|
print("shlex: pushing token " + repr(tok))
|
||||||
|
self.pushback.appendleft(tok)
|
||||||
|
|
||||||
|
def push_source(self, newstream, newfile=None):
|
||||||
|
"Push an input source onto the lexer's input source stack."
|
||||||
|
# Modified by argcomplete: 2/3 compatibility
|
||||||
|
if isinstance(newstream, str):
|
||||||
|
newstream = StringIO(newstream)
|
||||||
|
self.filestack.appendleft((self.infile, self.instream, self.lineno))
|
||||||
|
self.infile = newfile
|
||||||
|
self.instream = newstream
|
||||||
|
self.lineno = 1
|
||||||
|
if self.debug:
|
||||||
|
if newfile is not None:
|
||||||
|
print('shlex: pushing to file %s' % (self.infile,))
|
||||||
|
else:
|
||||||
|
print('shlex: pushing to stream %s' % (self.instream,))
|
||||||
|
|
||||||
|
def pop_source(self):
|
||||||
|
"Pop the input source stack."
|
||||||
|
self.instream.close()
|
||||||
|
(self.infile, self.instream, self.lineno) = self.filestack.popleft()
|
||||||
|
if self.debug:
|
||||||
|
print('shlex: popping to %s, line %d' % (self.instream, self.lineno))
|
||||||
|
self.state = ' '
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
"Get a token from the input stream (or from stack if it's nonempty)"
|
||||||
|
if self.pushback:
|
||||||
|
tok = self.pushback.popleft()
|
||||||
|
if self.debug >= 1:
|
||||||
|
print("shlex: popping token " + repr(tok))
|
||||||
|
return tok
|
||||||
|
# No pushback. Get a token.
|
||||||
|
raw = self.read_token()
|
||||||
|
# Handle inclusions
|
||||||
|
if self.source is not None:
|
||||||
|
while raw == self.source:
|
||||||
|
spec = self.sourcehook(self.read_token())
|
||||||
|
if spec:
|
||||||
|
(newfile, newstream) = spec
|
||||||
|
self.push_source(newstream, newfile)
|
||||||
|
raw = self.get_token()
|
||||||
|
# Maybe we got EOF instead?
|
||||||
|
while raw == self.eof:
|
||||||
|
if not self.filestack:
|
||||||
|
return self.eof
|
||||||
|
else:
|
||||||
|
self.pop_source()
|
||||||
|
raw = self.get_token()
|
||||||
|
# Neither inclusion nor EOF
|
||||||
|
if self.debug >= 1:
|
||||||
|
if raw != self.eof:
|
||||||
|
print("shlex: token=" + repr(raw))
|
||||||
|
else:
|
||||||
|
print("shlex: token=EOF")
|
||||||
|
return raw
|
||||||
|
|
||||||
|
def read_token(self):
|
||||||
|
quoted = False
|
||||||
|
escapedstate = ' '
|
||||||
|
while True:
|
||||||
|
if self.punctuation_chars and self._pushback_chars:
|
||||||
|
nextchar = self._pushback_chars.pop()
|
||||||
|
else:
|
||||||
|
nextchar = self.instream.read(1)
|
||||||
|
if nextchar == '\n':
|
||||||
|
self.lineno += 1
|
||||||
|
if self.debug >= 3:
|
||||||
|
print("shlex: in state %r I see character: %r" % (self.state, nextchar))
|
||||||
|
if self.state is None:
|
||||||
|
self.token = '' # past end of file
|
||||||
|
break
|
||||||
|
elif self.state == ' ':
|
||||||
|
if not nextchar:
|
||||||
|
self.state = None # end of file
|
||||||
|
break
|
||||||
|
elif nextchar in self.whitespace:
|
||||||
|
if self.debug >= 2:
|
||||||
|
print("shlex: I see whitespace in whitespace state")
|
||||||
|
if self.token or (self.posix and quoted):
|
||||||
|
break # emit current token
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif nextchar in self.commenters:
|
||||||
|
self.instream.readline()
|
||||||
|
self.lineno += 1
|
||||||
|
elif self.posix and nextchar in self.escape:
|
||||||
|
escapedstate = 'a'
|
||||||
|
self.state = nextchar
|
||||||
|
elif nextchar in self.wordchars:
|
||||||
|
self.token = nextchar
|
||||||
|
self.state = 'a'
|
||||||
|
elif nextchar in self.punctuation_chars:
|
||||||
|
self.token = nextchar
|
||||||
|
self.state = 'c'
|
||||||
|
elif nextchar in self.quotes:
|
||||||
|
if not self.posix:
|
||||||
|
self.token = nextchar
|
||||||
|
self.state = nextchar
|
||||||
|
elif self.whitespace_split:
|
||||||
|
self.token = nextchar
|
||||||
|
self.state = 'a'
|
||||||
|
# Modified by argcomplete: Record last wordbreak position
|
||||||
|
if nextchar in self.wordbreaks:
|
||||||
|
self.last_wordbreak_pos = len(self.token) - 1
|
||||||
|
else:
|
||||||
|
self.token = nextchar
|
||||||
|
if self.token or (self.posix and quoted):
|
||||||
|
break # emit current token
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif self.state in self.quotes:
|
||||||
|
quoted = True
|
||||||
|
if not nextchar: # end of file
|
||||||
|
if self.debug >= 2:
|
||||||
|
print("shlex: I see EOF in quotes state")
|
||||||
|
# XXX what error should be raised here?
|
||||||
|
raise ValueError("No closing quotation")
|
||||||
|
if nextchar == self.state:
|
||||||
|
if not self.posix:
|
||||||
|
self.token += nextchar
|
||||||
|
self.state = ' '
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.state = 'a'
|
||||||
|
elif self.posix and nextchar in self.escape and self.state in self.escapedquotes:
|
||||||
|
escapedstate = self.state
|
||||||
|
self.state = nextchar
|
||||||
|
else:
|
||||||
|
self.token += nextchar
|
||||||
|
elif self.state in self.escape:
|
||||||
|
if not nextchar: # end of file
|
||||||
|
if self.debug >= 2:
|
||||||
|
print("shlex: I see EOF in escape state")
|
||||||
|
# XXX what error should be raised here?
|
||||||
|
raise ValueError("No escaped character")
|
||||||
|
# In posix shells, only the quote itself or the escape
|
||||||
|
# character may be escaped within quotes.
|
||||||
|
if escapedstate in self.quotes and nextchar != self.state and nextchar != escapedstate:
|
||||||
|
self.token += self.state
|
||||||
|
self.token += nextchar
|
||||||
|
self.state = escapedstate
|
||||||
|
elif self.state in ('a', 'c'):
|
||||||
|
if not nextchar:
|
||||||
|
self.state = None # end of file
|
||||||
|
break
|
||||||
|
elif nextchar in self.whitespace:
|
||||||
|
if self.debug >= 2:
|
||||||
|
print("shlex: I see whitespace in word state")
|
||||||
|
self.state = ' '
|
||||||
|
if self.token or (self.posix and quoted):
|
||||||
|
break # emit current token
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif nextchar in self.commenters:
|
||||||
|
self.instream.readline()
|
||||||
|
self.lineno += 1
|
||||||
|
if self.posix:
|
||||||
|
self.state = ' '
|
||||||
|
if self.token or (self.posix and quoted):
|
||||||
|
break # emit current token
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif self.posix and nextchar in self.quotes:
|
||||||
|
self.state = nextchar
|
||||||
|
elif self.posix and nextchar in self.escape:
|
||||||
|
escapedstate = 'a'
|
||||||
|
self.state = nextchar
|
||||||
|
elif self.state == 'c':
|
||||||
|
if nextchar in self.punctuation_chars:
|
||||||
|
self.token += nextchar
|
||||||
|
else:
|
||||||
|
if nextchar not in self.whitespace:
|
||||||
|
self._pushback_chars.append(nextchar)
|
||||||
|
self.state = ' '
|
||||||
|
break
|
||||||
|
elif nextchar in self.wordchars or nextchar in self.quotes or self.whitespace_split:
|
||||||
|
self.token += nextchar
|
||||||
|
# Modified by argcomplete: Record last wordbreak position
|
||||||
|
if nextchar in self.wordbreaks:
|
||||||
|
self.last_wordbreak_pos = len(self.token) - 1
|
||||||
|
else:
|
||||||
|
if self.punctuation_chars:
|
||||||
|
self._pushback_chars.append(nextchar)
|
||||||
|
else:
|
||||||
|
self.pushback.appendleft(nextchar)
|
||||||
|
if self.debug >= 2:
|
||||||
|
print("shlex: I see punctuation in word state")
|
||||||
|
self.state = ' '
|
||||||
|
if self.token or (self.posix and quoted):
|
||||||
|
break # emit current token
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
result: Optional[str] = self.token
|
||||||
|
self.token = ''
|
||||||
|
if self.posix and not quoted and result == '':
|
||||||
|
result = None
|
||||||
|
if self.debug > 1:
|
||||||
|
if result:
|
||||||
|
print("shlex: raw token=" + repr(result))
|
||||||
|
else:
|
||||||
|
print("shlex: raw token=EOF")
|
||||||
|
# Modified by argcomplete: Record last wordbreak position
|
||||||
|
if self.state == ' ':
|
||||||
|
self.last_wordbreak_pos = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def sourcehook(self, newfile):
|
||||||
|
"Hook called on a filename to be sourced."
|
||||||
|
if newfile[0] == '"':
|
||||||
|
newfile = newfile[1:-1]
|
||||||
|
# This implements cpp-like semantics for relative-path inclusion.
|
||||||
|
# Modified by argcomplete: 2/3 compatibility
|
||||||
|
if isinstance(self.infile, str) and not os.path.isabs(newfile):
|
||||||
|
newfile = os.path.join(os.path.dirname(self.infile), newfile)
|
||||||
|
return (newfile, open(newfile, "r"))
|
||||||
|
|
||||||
|
def error_leader(self, infile=None, lineno=None):
|
||||||
|
"Emit a C-compiler-like, Emacs-friendly error-message leader."
|
||||||
|
if infile is None:
|
||||||
|
infile = self.infile
|
||||||
|
if lineno is None:
|
||||||
|
lineno = self.lineno
|
||||||
|
return "\"%s\", line %d: " % (infile, lineno)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
token = self.get_token()
|
||||||
|
if token == self.eof:
|
||||||
|
raise StopIteration
|
||||||
|
return token
|
||||||
|
|
||||||
|
# Modified by argcomplete: 2/3 compatibility
|
||||||
|
next = __next__
|
||||||
0
.venv_codegen/Lib/site-packages/argcomplete/py.typed
Normal file
0
.venv_codegen/Lib/site-packages/argcomplete/py.typed
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
|
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
|
||||||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Activate the generic bash-completion script or zsh completion autoload function for the argcomplete module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import site
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import argcomplete
|
||||||
|
|
||||||
|
# PEP 366
|
||||||
|
__package__ = "argcomplete.scripts"
|
||||||
|
|
||||||
|
zsh_shellcode = """
|
||||||
|
# Begin added by argcomplete
|
||||||
|
fpath=( {zsh_fpath} "${{fpath[@]}}" )
|
||||||
|
# End added by argcomplete
|
||||||
|
"""
|
||||||
|
|
||||||
|
bash_shellcode = """
|
||||||
|
# Begin added by argcomplete
|
||||||
|
source "{activator}"
|
||||||
|
# End added by argcomplete
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("-y", "--yes", help="automatically answer yes for all questions", action="store_true")
|
||||||
|
parser.add_argument("--dest", help='Specify the shell completion modules directory to install into, or "-" for stdout')
|
||||||
|
parser.add_argument("--user", help="Install into user directory", action="store_true")
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
args = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_dir():
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(["brew", "--prefix"]).decode().strip()
|
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||||
|
return "/usr/local"
|
||||||
|
|
||||||
|
|
||||||
|
def get_zsh_system_dir():
|
||||||
|
return f"{get_local_dir()}/share/zsh/site-functions"
|
||||||
|
|
||||||
|
|
||||||
|
def get_bash_system_dir():
|
||||||
|
if "BASH_COMPLETION_COMPAT_DIR" in os.environ:
|
||||||
|
return os.environ["BASH_COMPLETION_COMPAT_DIR"]
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
return f"{get_local_dir()}/etc/bash_completion.d" # created by homebrew
|
||||||
|
else:
|
||||||
|
return "/etc/bash_completion.d" # created by bash-completion
|
||||||
|
|
||||||
|
|
||||||
|
def get_activator_dir():
|
||||||
|
return os.path.join(os.path.abspath(os.path.dirname(argcomplete.__file__)), "bash_completion.d")
|
||||||
|
|
||||||
|
|
||||||
|
def get_activator_path():
|
||||||
|
return os.path.join(get_activator_dir(), "_python-argcomplete")
|
||||||
|
|
||||||
|
|
||||||
|
def install_to_destination(dest):
|
||||||
|
activator = get_activator_path()
|
||||||
|
if dest == "-":
|
||||||
|
with open(activator) as fh:
|
||||||
|
sys.stdout.write(fh.read())
|
||||||
|
return
|
||||||
|
destdir = os.path.dirname(dest)
|
||||||
|
if not os.path.exists(destdir):
|
||||||
|
try:
|
||||||
|
os.makedirs(destdir, exist_ok=True)
|
||||||
|
except Exception as e:
|
||||||
|
parser.error(
|
||||||
|
f"path {destdir} does not exist and could not be created: {e}. Please run this command using sudo, or see --help for more options."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
print(f"Installing {activator} to {dest}...", file=sys.stderr)
|
||||||
|
shutil.copy(activator, dest)
|
||||||
|
print("Installed.", file=sys.stderr)
|
||||||
|
except Exception as e:
|
||||||
|
parser.error(
|
||||||
|
f"while installing to {dest}: {e}. Please run this command using sudo, or see --help for more options."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_consent():
|
||||||
|
assert args is not None
|
||||||
|
if args.yes is True:
|
||||||
|
return True
|
||||||
|
while True:
|
||||||
|
res = input("OK to proceed? [y/n] ")
|
||||||
|
if res.lower() not in {"y", "n", "yes", "no"}:
|
||||||
|
print('Please answer "yes" or "no".', file=sys.stderr)
|
||||||
|
elif res.lower() in {"y", "yes"}:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def append_to_config_file(path, shellcode):
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, 'r') as fh:
|
||||||
|
if shellcode in fh.read():
|
||||||
|
print(f"The code already exists in the file {path}.", file=sys.stderr)
|
||||||
|
return
|
||||||
|
print(f"argcomplete needs to append to the file {path}. The following code will be appended:", file=sys.stderr)
|
||||||
|
for line in shellcode.splitlines():
|
||||||
|
print(">", line, file=sys.stderr)
|
||||||
|
if not get_consent():
|
||||||
|
print("Not added.", file=sys.stderr)
|
||||||
|
return
|
||||||
|
print(f"Adding shellcode to {path}...", file=sys.stderr)
|
||||||
|
with open(path, "a") as fh:
|
||||||
|
fh.write(shellcode)
|
||||||
|
print("Added.", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def link_zsh_user_rcfile(zsh_fpath=None):
|
||||||
|
zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv")
|
||||||
|
append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=zsh_fpath or get_activator_dir()))
|
||||||
|
|
||||||
|
|
||||||
|
def link_bash_user_rcfile():
|
||||||
|
bash_completion_user_file = os.path.expanduser("~/.bash_completion")
|
||||||
|
append_to_config_file(bash_completion_user_file, bash_shellcode.format(activator=get_activator_path()))
|
||||||
|
|
||||||
|
|
||||||
|
def link_user_rcfiles():
|
||||||
|
# TODO: warn if running as superuser
|
||||||
|
link_zsh_user_rcfile()
|
||||||
|
link_bash_user_rcfile()
|
||||||
|
|
||||||
|
|
||||||
|
def add_zsh_system_dir_to_fpath_for_user():
|
||||||
|
if "zsh" not in os.environ.get("SHELL", ""):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
zsh_system_dir = get_zsh_system_dir()
|
||||||
|
fpath_output = subprocess.check_output([os.environ["SHELL"], "-c", 'printf "%s\n" "${fpath[@]}"'])
|
||||||
|
for fpath in fpath_output.decode().splitlines():
|
||||||
|
if fpath == zsh_system_dir:
|
||||||
|
return
|
||||||
|
link_zsh_user_rcfile(zsh_fpath=zsh_system_dir)
|
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global args
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
destinations = []
|
||||||
|
|
||||||
|
if args.dest:
|
||||||
|
if args.dest != "-" and not os.path.exists(args.dest):
|
||||||
|
parser.error(f"directory {args.dest} was specified via --dest, but it does not exist")
|
||||||
|
destinations.append(args.dest)
|
||||||
|
elif site.ENABLE_USER_SITE and site.USER_SITE and site.USER_SITE in argcomplete.__file__:
|
||||||
|
print(
|
||||||
|
"Argcomplete was installed in the user site local directory. Defaulting to user installation.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
link_user_rcfiles()
|
||||||
|
elif sys.prefix != sys.base_prefix:
|
||||||
|
print("Argcomplete was installed in a virtual environment. Defaulting to user installation.", file=sys.stderr)
|
||||||
|
link_user_rcfiles()
|
||||||
|
elif args.user:
|
||||||
|
link_user_rcfiles()
|
||||||
|
else:
|
||||||
|
print("Defaulting to system-wide installation.", file=sys.stderr)
|
||||||
|
destinations.append(f"{get_zsh_system_dir()}/_python-argcomplete")
|
||||||
|
destinations.append(f"{get_bash_system_dir()}/python-argcomplete")
|
||||||
|
|
||||||
|
for destination in destinations:
|
||||||
|
install_to_destination(destination)
|
||||||
|
|
||||||
|
add_zsh_system_dir_to_fpath_for_user()
|
||||||
|
|
||||||
|
if args.dest is None:
|
||||||
|
print("Please restart your shell or source the installed file to activate it.", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
|
||||||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script is part of the Python argcomplete package (https://github.com/kislyuk/argcomplete).
|
||||||
|
It is used to check if an EASY-INSTALL-SCRIPT wrapper redirects to a script that contains the string
|
||||||
|
"PYTHON_ARGCOMPLETE_OK". If you have enabled global completion in argcomplete, the completion hook will run it every
|
||||||
|
time you press <TAB> in your shell.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python-argcomplete-check-easy-install-script <input executable file>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# PEP 366
|
||||||
|
__package__ = "argcomplete.scripts"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
sys.exit(__doc__)
|
||||||
|
|
||||||
|
sys.tracebacklimit = 0
|
||||||
|
|
||||||
|
with open(sys.argv[1]) as fh:
|
||||||
|
line1, head = fh.read(1024).split("\n", 1)[:2]
|
||||||
|
if line1.startswith("#") and ("py" in line1 or "Py" in line1):
|
||||||
|
import re
|
||||||
|
|
||||||
|
lines = head.split("\n", 12)
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("# EASY-INSTALL-SCRIPT"):
|
||||||
|
import pkg_resources # type: ignore
|
||||||
|
|
||||||
|
re_match = re.match("# EASY-INSTALL-SCRIPT: '(.+)','(.+)'", line)
|
||||||
|
assert re_match is not None
|
||||||
|
dist, script = re_match.groups()
|
||||||
|
if "PYTHON_ARGCOMPLETE_OK" in pkg_resources.get_distribution(dist).get_metadata(
|
||||||
|
"scripts/" + script
|
||||||
|
):
|
||||||
|
return 0
|
||||||
|
elif line.startswith("# EASY-INSTALL-ENTRY-SCRIPT"):
|
||||||
|
re_match = re.match("# EASY-INSTALL-ENTRY-SCRIPT: '(.+)','(.+)','(.+)'", line)
|
||||||
|
assert re_match is not None
|
||||||
|
dist, group, name = re_match.groups()
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
import pkg_resources # type: ignore
|
||||||
|
|
||||||
|
entry_point_info = pkg_resources.get_distribution(dist).get_entry_info(group, name)
|
||||||
|
assert entry_point_info is not None
|
||||||
|
module_name = entry_point_info.module_name
|
||||||
|
with open(pkgutil.get_loader(module_name).get_filename()) as mod_fh: # type: ignore
|
||||||
|
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
|
||||||
|
return 0
|
||||||
|
elif line.startswith("# EASY-INSTALL-DEV-SCRIPT"):
|
||||||
|
for line2 in lines:
|
||||||
|
if line2.startswith("__file__"):
|
||||||
|
re_match = re.match("__file__ = '(.+)'", line2)
|
||||||
|
assert re_match is not None
|
||||||
|
filename = re_match.group(1)
|
||||||
|
with open(filename) as mod_fh:
|
||||||
|
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
|
||||||
|
return 0
|
||||||
|
elif line.startswith("# PBR Generated"):
|
||||||
|
re_match = re.search("from (.*) import", head)
|
||||||
|
assert re_match is not None
|
||||||
|
module = re_match.groups()[0]
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
import pkg_resources # type: ignore
|
||||||
|
|
||||||
|
with open(pkgutil.get_loader(module).get_filename()) as mod_fh: # type: ignore
|
||||||
|
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
|
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
|
||||||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Register a Python executable for use with the argcomplete module.
|
||||||
|
|
||||||
|
To perform the registration, source the output of this script in your bash shell
|
||||||
|
(quote the output to avoid interpolation).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
$ eval "$(register-python-argcomplete my-favorite-script.py)"
|
||||||
|
|
||||||
|
For Tcsh
|
||||||
|
|
||||||
|
$ eval `register-python-argcomplete --shell tcsh my-favorite-script.py`
|
||||||
|
|
||||||
|
For Fish
|
||||||
|
|
||||||
|
$ register-python-argcomplete --shell fish my-favourite-script.py > ~/.config/fish/my-favourite-script.py.fish
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import argcomplete
|
||||||
|
|
||||||
|
# PEP 366
|
||||||
|
__package__ = "argcomplete.scripts"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-defaults",
|
||||||
|
dest="use_defaults",
|
||||||
|
action="store_false",
|
||||||
|
default=True,
|
||||||
|
help="when no matches are generated, do not fallback to readline's default completion (affects bash only)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--complete-arguments",
|
||||||
|
nargs=argparse.REMAINDER,
|
||||||
|
help="arguments to call complete with; use of this option discards default options (affects bash only)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--shell",
|
||||||
|
choices=("bash", "zsh", "tcsh", "fish", "powershell"),
|
||||||
|
default="bash",
|
||||||
|
help="output code for the specified shell",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-e", "--external-argcomplete-script", help="external argcomplete script for auto completion of the executable"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("executable", nargs="+", help="executable to completed (when invoked by exactly this name)")
|
||||||
|
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
sys.stdout.write(
|
||||||
|
argcomplete.shellcode(
|
||||||
|
args.executable, args.use_defaults, args.shell, args.complete_arguments, args.external_argcomplete_script
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
202
.venv_codegen/Lib/site-packages/argcomplete/shell_integration.py
Normal file
202
.venv_codegen/Lib/site-packages/argcomplete/shell_integration.py
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
|
||||||
|
# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
|
||||||
|
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
|
||||||
|
# See https://github.com/kislyuk/argcomplete for more info.
|
||||||
|
|
||||||
|
from shlex import quote
|
||||||
|
|
||||||
|
bashcode = r"""#compdef %(executables)s
|
||||||
|
# Run something, muting output or redirecting it to the debug stream
|
||||||
|
# depending on the value of _ARC_DEBUG.
|
||||||
|
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
|
||||||
|
__python_argcomplete_run() {
|
||||||
|
if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
|
||||||
|
__python_argcomplete_run_inner "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local tmpfile="$(mktemp)"
|
||||||
|
_ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
|
||||||
|
local code=$?
|
||||||
|
cat "$tmpfile"
|
||||||
|
rm "$tmpfile"
|
||||||
|
return $code
|
||||||
|
}
|
||||||
|
|
||||||
|
__python_argcomplete_run_inner() {
|
||||||
|
if [[ -z "${_ARC_DEBUG-}" ]]; then
|
||||||
|
"$@" 8>&1 9>&2 1>/dev/null 2>&1 </dev/null
|
||||||
|
else
|
||||||
|
"$@" 8>&1 9>&2 1>&9 2>&1 </dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_python_argcomplete%(function_suffix)s() {
|
||||||
|
local IFS=$'\013'
|
||||||
|
local script="%(argcomplete_script)s"
|
||||||
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
||||||
|
local completions
|
||||||
|
completions=($(IFS="$IFS" \
|
||||||
|
COMP_LINE="$BUFFER" \
|
||||||
|
COMP_POINT="$CURSOR" \
|
||||||
|
_ARGCOMPLETE=1 \
|
||||||
|
_ARGCOMPLETE_SHELL="zsh" \
|
||||||
|
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
|
||||||
|
__python_argcomplete_run ${script:-${words[1]}}))
|
||||||
|
local nosort=()
|
||||||
|
local nospace=()
|
||||||
|
if is-at-least 5.8; then
|
||||||
|
nosort=(-o nosort)
|
||||||
|
fi
|
||||||
|
if [[ "${completions-}" =~ ([^\\\\]): && "${match[1]}" =~ [=/:] ]]; then
|
||||||
|
nospace=(-S '')
|
||||||
|
fi
|
||||||
|
_describe "${words[1]}" completions "${nosort[@]}" "${nospace[@]}"
|
||||||
|
else
|
||||||
|
local SUPPRESS_SPACE=0
|
||||||
|
if compopt +o nospace 2> /dev/null; then
|
||||||
|
SUPPRESS_SPACE=1
|
||||||
|
fi
|
||||||
|
COMPREPLY=($(IFS="$IFS" \
|
||||||
|
COMP_LINE="$COMP_LINE" \
|
||||||
|
COMP_POINT="$COMP_POINT" \
|
||||||
|
COMP_TYPE="$COMP_TYPE" \
|
||||||
|
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
|
||||||
|
_ARGCOMPLETE=1 \
|
||||||
|
_ARGCOMPLETE_SHELL="bash" \
|
||||||
|
_ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
|
||||||
|
__python_argcomplete_run ${script:-$1}))
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
unset COMPREPLY
|
||||||
|
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
if [[ -z "${ZSH_VERSION-}" ]]; then
|
||||||
|
complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s
|
||||||
|
else
|
||||||
|
# When called by the Zsh completion system, this will end with
|
||||||
|
# "loadautofunc" when initially autoloaded and "shfunc" later on, otherwise,
|
||||||
|
# the script was "eval"-ed so use "compdef" to register it with the
|
||||||
|
# completion system
|
||||||
|
autoload is-at-least
|
||||||
|
if [[ $zsh_eval_context == *func ]]; then
|
||||||
|
_python_argcomplete%(function_suffix)s "$@"
|
||||||
|
else
|
||||||
|
compdef _python_argcomplete%(function_suffix)s %(executables)s
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
tcshcode = """\
|
||||||
|
complete "%(executable)s" 'p@*@`python-argcomplete-tcsh "%(argcomplete_script)s"`@' ;
|
||||||
|
"""
|
||||||
|
|
||||||
|
fishcode = r"""
|
||||||
|
function __fish_%(function_name)s_complete
|
||||||
|
set -lx _ARGCOMPLETE 1
|
||||||
|
set -lx _ARGCOMPLETE_DFS \t
|
||||||
|
set -lx _ARGCOMPLETE_IFS \n
|
||||||
|
set -lx _ARGCOMPLETE_SUPPRESS_SPACE 1
|
||||||
|
set -lx _ARGCOMPLETE_SHELL fish
|
||||||
|
set -lx COMP_LINE (commandline -p)
|
||||||
|
set -lx COMP_POINT (string length (commandline -cp))
|
||||||
|
set -lx COMP_TYPE
|
||||||
|
if set -q _ARC_DEBUG
|
||||||
|
%(argcomplete_script)s 8>&1 9>&2 1>&9 2>&1
|
||||||
|
else
|
||||||
|
%(argcomplete_script)s 8>&1 9>&2 1>/dev/null 2>&1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
complete %(completion_arg)s %(executable)s -f -a '(__fish_%(function_name)s_complete)'
|
||||||
|
"""
|
||||||
|
|
||||||
|
powershell_code = r"""
|
||||||
|
Register-ArgumentCompleter -Native -CommandName %(executable)s -ScriptBlock {
|
||||||
|
param($commandName, $wordToComplete, $cursorPosition)
|
||||||
|
$completion_file = New-TemporaryFile
|
||||||
|
$env:ARGCOMPLETE_USE_TEMPFILES = 1
|
||||||
|
$env:_ARGCOMPLETE_STDOUT_FILENAME = $completion_file
|
||||||
|
$env:COMP_LINE = $wordToComplete
|
||||||
|
$env:COMP_POINT = $cursorPosition
|
||||||
|
$env:_ARGCOMPLETE = 1
|
||||||
|
$env:_ARGCOMPLETE_SUPPRESS_SPACE = 0
|
||||||
|
$env:_ARGCOMPLETE_IFS = "`n"
|
||||||
|
$env:_ARGCOMPLETE_SHELL = "powershell"
|
||||||
|
%(argcomplete_script)s 2>&1 | Out-Null
|
||||||
|
|
||||||
|
Get-Content $completion_file | ForEach-Object {
|
||||||
|
[System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_)
|
||||||
|
}
|
||||||
|
Remove-Item $completion_file, Env:\_ARGCOMPLETE_STDOUT_FILENAME, Env:\ARGCOMPLETE_USE_TEMPFILES, Env:\COMP_LINE, Env:\COMP_POINT, Env:\_ARGCOMPLETE, Env:\_ARGCOMPLETE_SUPPRESS_SPACE, Env:\_ARGCOMPLETE_IFS, Env:\_ARGCOMPLETE_SHELL
|
||||||
|
}
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}
|
||||||
|
|
||||||
|
|
||||||
|
def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None):
|
||||||
|
"""
|
||||||
|
Provide the shell code required to register a python executable for use with the argcomplete module.
|
||||||
|
|
||||||
|
:param list(str) executables: Executables to be completed (when invoked exactly with this name)
|
||||||
|
:param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated
|
||||||
|
(affects bash only)
|
||||||
|
:param str shell: Name of the shell to output code for
|
||||||
|
:param complete_arguments: Arguments to call complete with (affects bash only)
|
||||||
|
:type complete_arguments: list(str) or None
|
||||||
|
:param argcomplete_script: Script to call complete with, if not the executable to complete.
|
||||||
|
If supplied, will be used to complete *all* passed executables.
|
||||||
|
:type argcomplete_script: str or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if complete_arguments is None:
|
||||||
|
complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault"
|
||||||
|
else:
|
||||||
|
complete_options = " ".join(complete_arguments)
|
||||||
|
|
||||||
|
if shell == "bash" or shell == "zsh":
|
||||||
|
quoted_executables = [quote(i) for i in executables]
|
||||||
|
executables_list = " ".join(quoted_executables)
|
||||||
|
script = argcomplete_script
|
||||||
|
if script:
|
||||||
|
# If the script path contain a space, this would generate an invalid function name.
|
||||||
|
function_suffix = "_" + script.replace(" ", "_SPACE_")
|
||||||
|
else:
|
||||||
|
script = ""
|
||||||
|
function_suffix = ""
|
||||||
|
code = bashcode % dict(
|
||||||
|
complete_opts=complete_options,
|
||||||
|
executables=executables_list,
|
||||||
|
argcomplete_script=script,
|
||||||
|
function_suffix=function_suffix,
|
||||||
|
)
|
||||||
|
elif shell == "fish":
|
||||||
|
code = ""
|
||||||
|
for executable in executables:
|
||||||
|
script = argcomplete_script or executable
|
||||||
|
completion_arg = "--path" if "/" in executable else "--command" # use path for absolute paths
|
||||||
|
function_name = executable.replace("/", "_") # / not allowed in function name
|
||||||
|
|
||||||
|
code += fishcode % dict(
|
||||||
|
executable=executable,
|
||||||
|
argcomplete_script=script,
|
||||||
|
completion_arg=completion_arg,
|
||||||
|
function_name=function_name,
|
||||||
|
)
|
||||||
|
elif shell == "powershell":
|
||||||
|
code = ""
|
||||||
|
for executable in executables:
|
||||||
|
script = argcomplete_script or executable
|
||||||
|
code += powershell_code % dict(executable=executable, argcomplete_script=script)
|
||||||
|
|
||||||
|
else:
|
||||||
|
code = ""
|
||||||
|
for executable in executables:
|
||||||
|
script = argcomplete_script
|
||||||
|
# If no script was specified, default to the executable being completed.
|
||||||
|
if not script:
|
||||||
|
script = executable
|
||||||
|
code += shell_codes.get(shell, "") % dict(executable=executable, argcomplete_script=script)
|
||||||
|
|
||||||
|
return code
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
2478
.venv_codegen/Lib/site-packages/black-26.3.1.dist-info/METADATA
Normal file
2478
.venv_codegen/Lib/site-packages/black-26.3.1.dist-info/METADATA
Normal file
File diff suppressed because it is too large
Load diff
129
.venv_codegen/Lib/site-packages/black-26.3.1.dist-info/RECORD
Normal file
129
.venv_codegen/Lib/site-packages/black-26.3.1.dist-info/RECORD
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
../../Scripts/black.exe,sha256=lhcJd8I9AH1_FkLjfvqcnPOLHz0XFoqDhnLQB5N-h-0,106405
|
||||||
|
../../Scripts/blackd.exe,sha256=Jwo6pubjvwyPXYi3y9qnDeFYvZu9Bp2ydBcr5AXhSps,106406
|
||||||
|
30fcd23745efe32ce681__mypyc.cp310-win_amd64.pyd,sha256=pRx5IY_gCT0OEtnnUPYJ-S7KQFzZ18cVYpUNuZSV6RA,2723840
|
||||||
|
__pycache__/_black_version.cpython-310.pyc,,
|
||||||
|
_black_version.py,sha256=XVC2FRpYPCFN0Q1PSMLN8zuPbMVa0LuePuoHB8WIV64,20
|
||||||
|
_black_version.pyi,sha256=gXF2iFKaJ_mJncCEFLjOHdt3GxGdW_dheaJIpcZ2IwA,14
|
||||||
|
black-26.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
black-26.3.1.dist-info/METADATA,sha256=-mxo0oe8mMLTKryPQmYKZ3kNYIIzqE0ZfKRALpwfd4Y,91891
|
||||||
|
black-26.3.1.dist-info/RECORD,,
|
||||||
|
black-26.3.1.dist-info/WHEEL,sha256=IEheya3TgCujf4RsksFGJu9E_Hw3yzEacSYrp-thffM,97
|
||||||
|
black-26.3.1.dist-info/entry_points.txt,sha256=XTCA4X2yVA0tMiV7l96Gv9TyxhVhoCaznLN2XThqYSA,144
|
||||||
|
black-26.3.1.dist-info/licenses/LICENSE,sha256=XQJSBb4crFXeCOvZ-WHsfXTQ-Zj2XxeFbd0ien078zM,1101
|
||||||
|
black/__init__.cp310-win_amd64.pyd,sha256=L54lM-4wF3DHk-p9O4CHFjDJlL5krNRM5RpeSJBbR6Q,10752
|
||||||
|
black/__init__.py,sha256=lhUdQrZYaGHJJCkbLo-kpT24OWakCxY_aZpgoe3FCzo,56459
|
||||||
|
black/__main__.py,sha256=6V0pV9Zeh8940mbQbVTCPdTX4Gjq1HGrFCA6E4HLGaM,50
|
||||||
|
black/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
black/__pycache__/__main__.cpython-310.pyc,,
|
||||||
|
black/__pycache__/_width_table.cpython-310.pyc,,
|
||||||
|
black/__pycache__/brackets.cpython-310.pyc,,
|
||||||
|
black/__pycache__/cache.cpython-310.pyc,,
|
||||||
|
black/__pycache__/comments.cpython-310.pyc,,
|
||||||
|
black/__pycache__/concurrency.cpython-310.pyc,,
|
||||||
|
black/__pycache__/const.cpython-310.pyc,,
|
||||||
|
black/__pycache__/debug.cpython-310.pyc,,
|
||||||
|
black/__pycache__/files.cpython-310.pyc,,
|
||||||
|
black/__pycache__/handle_ipynb_magics.cpython-310.pyc,,
|
||||||
|
black/__pycache__/linegen.cpython-310.pyc,,
|
||||||
|
black/__pycache__/lines.cpython-310.pyc,,
|
||||||
|
black/__pycache__/mode.cpython-310.pyc,,
|
||||||
|
black/__pycache__/nodes.cpython-310.pyc,,
|
||||||
|
black/__pycache__/numerics.cpython-310.pyc,,
|
||||||
|
black/__pycache__/output.cpython-310.pyc,,
|
||||||
|
black/__pycache__/parsing.cpython-310.pyc,,
|
||||||
|
black/__pycache__/ranges.cpython-310.pyc,,
|
||||||
|
black/__pycache__/report.cpython-310.pyc,,
|
||||||
|
black/__pycache__/rusty.cpython-310.pyc,,
|
||||||
|
black/__pycache__/schema.cpython-310.pyc,,
|
||||||
|
black/__pycache__/strings.cpython-310.pyc,,
|
||||||
|
black/__pycache__/trans.cpython-310.pyc,,
|
||||||
|
black/_width_table.cp310-win_amd64.pyd,sha256=3-ePVeUx_8MmrbCNPsL7ApaULMWxPMq4XoeaRjvpypg,10752
|
||||||
|
black/_width_table.py,sha256=nOVdpn2H49kF-h4bPzNAJ_zfb6PJ7a1czEx4N124hq0,3228
|
||||||
|
black/brackets.cp310-win_amd64.pyd,sha256=daWUB_RaD40HspuiWWbK58hjXuK1hHwged6NKWdPgyQ,10752
|
||||||
|
black/brackets.py,sha256=ugO-Aga8aidqXdgqwlHNcBNBcxV1QtEWVPsT5JwXelY,12793
|
||||||
|
black/cache.cp310-win_amd64.pyd,sha256=6EaoXprryOVxGp_M0iBwjsUu9iSNIfTEDtATRaB3ks8,10752
|
||||||
|
black/cache.py,sha256=ty9qn9qL7dz7a82dFa8zYFvQprEL4avnJ6zAlDcqwqA,4904
|
||||||
|
black/comments.cp310-win_amd64.pyd,sha256=sYjdbgOUbHjeslo3Poqf5t6Jb_NSkf5rkADjFhoINq4,10752
|
||||||
|
black/comments.py,sha256=pRHiP7N6ZxkmtW2hXlFiRUErMw566j188kSrtdMH194,30580
|
||||||
|
black/concurrency.py,sha256=_b74I3Nwo3Dh4OYOW8mLZdATvQgYZnVGSulZPY1MTAg,7794
|
||||||
|
black/const.cp310-win_amd64.pyd,sha256=f1QmZs7xbbQqYISzBJYF_zCe60U4rTCa9nQeS3GOsfc,10752
|
||||||
|
black/const.py,sha256=FP5YcSxH6Cb0jqSkwF0nI4dHxPyQtL34hoWBfAqnAhI,325
|
||||||
|
black/debug.py,sha256=DJ5Cm2HaJT_J8H5_hqw-gYX1nVX5rh8595e2xagmRKI,1977
|
||||||
|
black/files.py,sha256=-wVRP-4ZOUM8x3ylqiESy6QXa2pTpv_e63b2Sh0w2t0,15090
|
||||||
|
black/handle_ipynb_magics.cp310-win_amd64.pyd,sha256=G91PG1b_ti09i66GKK6cQNd-SVIxoZnDI6BK2-VsqbM,10752
|
||||||
|
black/handle_ipynb_magics.py,sha256=4dDae_maXzIXdjK6lwVBi6hzRFzrCG9Bht7IsYy6xdU,16416
|
||||||
|
black/linegen.cp310-win_amd64.pyd,sha256=fT5E-jPH-o-_JdwGt_6n7HX1vgbTub7MEQ55gzWCK28,10752
|
||||||
|
black/linegen.py,sha256=gWWdrjPRFqattB2lB3cjq13k6-4LTo9RxnOb4b5EplI,80963
|
||||||
|
black/lines.cp310-win_amd64.pyd,sha256=rmapn6CaW6h5SWM_XRbo2DOj27mIyAi41SJxV08_fgE,10752
|
||||||
|
black/lines.py,sha256=VU467q5lSrOGhrz5aBA7lupTVHHrln-P8LUtZf1w25M,43008
|
||||||
|
black/mode.cp310-win_amd64.pyd,sha256=ceKQLApCljoRr5kGSn7-qkG4a8EtFzPpf-3A8clXHaA,10752
|
||||||
|
black/mode.py,sha256=T8cW29hIPF4LfcpylQsMrI4mrBq6onpOlhDInPvY3Fk,10563
|
||||||
|
black/nodes.cp310-win_amd64.pyd,sha256=gPJdyZnN7cnHHzm9noaymedyWUOsCSQJ_CMj955TYL4,10752
|
||||||
|
black/nodes.py,sha256=kz60pL_XrHXfKSSB7tAp1IfKtSNRqzJmW8QufRjeMmM,33285
|
||||||
|
black/numerics.cp310-win_amd64.pyd,sha256=xtQTv2Ev8lv2v7Y76IXjWnTle9-LmzZfrWo66jrf4z0,10752
|
||||||
|
black/numerics.py,sha256=gB1T1-npxj44Vhex63ov-oGsoIPwx_PZlT-n_qUcwO4,1716
|
||||||
|
black/output.py,sha256=4hEQsqNDlgWZTAJ-n0muusxin2HR8AxSNRmuZrjWoCM,4033
|
||||||
|
black/parsing.cp310-win_amd64.pyd,sha256=_HGIpSgugg28qylaF1J7yfylzQhvh30R0_-Ygy7B2NU,10752
|
||||||
|
black/parsing.py,sha256=eJB0i3dCwNQe2xjBqwsltH81bZMNagPax-6hIceRuF0,8671
|
||||||
|
black/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
black/ranges.cp310-win_amd64.pyd,sha256=ASUtNiCp0uxjr1-6y602Ai5DE4hwcA7Jg2bDAzxZq4o,10752
|
||||||
|
black/ranges.py,sha256=V6JhiIQCNUhgykRTsgdhqwz0CzHu3VeYk2JuRk5lxRw,21128
|
||||||
|
black/report.py,sha256=8Xies3PseQeTN4gYfHS7RewVQRjDsDBfFDR3sSNytco,3559
|
||||||
|
black/resources/__init__.cp310-win_amd64.pyd,sha256=ARg5-6QvY_72FGxJZKZCqLjly9cOzGvHqaFMc3dsaXk,10752
|
||||||
|
black/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
black/resources/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
black/resources/black.schema.json,sha256=qnDD2eskOv808T9d-NLuJNnf-wPIJJA46xI0Crd7E9I,7539
|
||||||
|
black/rusty.cp310-win_amd64.pyd,sha256=ISpVXhA4ts_TJeGICydabM4PGOFYZbx1Zb04v1knlZY,10752
|
||||||
|
black/rusty.py,sha256=RogIomJ1RCLMTOK_RA6U3EMbzWV_ZHxPtrXveXbMjzQ,585
|
||||||
|
black/schema.cp310-win_amd64.pyd,sha256=3iGsU3DJvIFNaJO4GX8TwC-pGq_On9Py33c0r0k0_GM,10752
|
||||||
|
black/schema.py,sha256=ZLKjanGVK4bG12GD7mkDzjOtLkv_g86p1-TXrdN9Skc,446
|
||||||
|
black/strings.cp310-win_amd64.pyd,sha256=W0-3Bp5rqsoW91NpdCHSeAnuVciXYaa1VBdMNDLi_sA,10752
|
||||||
|
black/strings.py,sha256=YOkxFUbnc228R2gQjMTA-QTAko9WDuOFykddBnEsYNI,13695
|
||||||
|
black/trans.cp310-win_amd64.pyd,sha256=k3xhkx-ZlSljtR0sF5IdpcochmDZ3Vg6XOwxGhy5_oM,10752
|
||||||
|
black/trans.py,sha256=svyMSYybDMDSYKM-EHYHPwNOsKpINsmd6mClZvVAdQA,99638
|
||||||
|
blackd/__init__.py,sha256=3Ethlw9pAXQVzT7eGf2MnvTzjjonF1t9d-FiTi7rwGU,10664
|
||||||
|
blackd/__main__.py,sha256=-2NrSIZ5Es7pTFThp8w5JL9LwmmxtF1akhe7NU1OGvs,40
|
||||||
|
blackd/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
blackd/__pycache__/__main__.cpython-310.pyc,,
|
||||||
|
blackd/__pycache__/client.cpython-310.pyc,,
|
||||||
|
blackd/__pycache__/middlewares.cpython-310.pyc,,
|
||||||
|
blackd/client.py,sha256=u7mkAPVsONR2Y4ucQNFgCLwK3WOHX7UojGQzXKpJDCI,4078
|
||||||
|
blackd/middlewares.py,sha256=mnIQU5_0sp7sVW5RESY9fSNxBDr9wpRsXdNA1_bYAIQ,1532
|
||||||
|
blib2to3/Grammar.txt,sha256=sYVk_h721g2t_Y_VKFjCgqCdBEDbVkA4aGkOPyFVRug,12290
|
||||||
|
blib2to3/LICENSE,sha256=D2HM6JsydKABNqFe2-_N4Lf8VxxE1_5DVQtAFzw2_w8,13016
|
||||||
|
blib2to3/PatternGrammar.txt,sha256=tH-u-ZLZsHJGgjZEfXN2S8GVCKXleQwWvOf7sO4hlmo,835
|
||||||
|
blib2to3/README,sha256=G-DiXkC8aKINCNv7smI2q_mz-8k6kC4yYO2OrMb0Nqs,1098
|
||||||
|
blib2to3/__init__.py,sha256=CSR2VOIKJL-JnGG41PcfbQZQEPCw43jfeK_EUisNsFQ,9
|
||||||
|
blib2to3/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
blib2to3/__pycache__/pygram.cpython-310.pyc,,
|
||||||
|
blib2to3/__pycache__/pytree.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__init__.py,sha256=z8NemtNtAaIBocPMl0aMLgxaQMedsKOS_dOVAy8c3TI,147
|
||||||
|
blib2to3/pgen2/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/conv.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/driver.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/grammar.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/literals.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/parse.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/pgen.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/token.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/__pycache__/tokenize.cpython-310.pyc,,
|
||||||
|
blib2to3/pgen2/conv.cp310-win_amd64.pyd,sha256=mC_dVmr1ePRMsx80Aux7HvPEZFr7bVeEdSKpgTgec_0,10752
|
||||||
|
blib2to3/pgen2/conv.py,sha256=oxdquGP469PeQwFmGM92A9FJEtZlQVb7_v4nFqzzsXY,9852
|
||||||
|
blib2to3/pgen2/driver.cp310-win_amd64.pyd,sha256=tfFfDPKpBhAef2uWB06m8zXhXVLdmWD3D_WR7pqBDiI,10752
|
||||||
|
blib2to3/pgen2/driver.py,sha256=MSY0gFbSqNfvdjD81gLFK5-sTIebRZqPRs90-ZLE47Q,10756
|
||||||
|
blib2to3/pgen2/grammar.cp310-win_amd64.pyd,sha256=iEJuT9EGThsxg0RM183_GzHcN2tgN9bzF-JpP5CCrNk,10752
|
||||||
|
blib2to3/pgen2/grammar.py,sha256=kWLJf3bdvHO4g_b3M_EEo98YIoBAwUBtFFYWd7kFH0c,7074
|
||||||
|
blib2to3/pgen2/literals.cp310-win_amd64.pyd,sha256=GZjTMJNnTbZ9--Ys0cYI7GiFHnCPaI7o9k4EKyle1nM,10752
|
||||||
|
blib2to3/pgen2/literals.py,sha256=eWQ54eEpsIJ5wohd__6dd0kk960QM5eCgMSgkg7eFZk,1645
|
||||||
|
blib2to3/pgen2/parse.cp310-win_amd64.pyd,sha256=QBJGwKFsC1TAIjZW_S2hU7m3c1HS6vAbq2kONT_-MGg,10752
|
||||||
|
blib2to3/pgen2/parse.py,sha256=RaVLB5PKkVv-OWQR02hOhfVPzUXr_wp8vKyTYjVTf08,15875
|
||||||
|
blib2to3/pgen2/pgen.cp310-win_amd64.pyd,sha256=mxPD0jlnzHjnQuUCCDvSnieIQ9gMUOfxp6n5gE1t5_M,10752
|
||||||
|
blib2to3/pgen2/pgen.py,sha256=wECI5dJFX4j_4HNL0s1V4YnvbdgKN-c56dToxAoCj3g,15489
|
||||||
|
blib2to3/pgen2/token.cp310-win_amd64.pyd,sha256=hPL9mktghHVQn5H_woMXV5ZJhpYvAJN0TGVjurakZ9g,10752
|
||||||
|
blib2to3/pgen2/token.py,sha256=zm2Ah0psZm5khl8fFQKwfKq6QQW_nlfEJjDHfJoXPkU,2065
|
||||||
|
blib2to3/pgen2/tokenize.cp310-win_amd64.pyd,sha256=jk8G3Cy4lw3pbh9fMjOucvBhO5dT74z6G4DrYd1i95c,10752
|
||||||
|
blib2to3/pgen2/tokenize.py,sha256=PFQYuR-Wy2G_fthIjGIFexDOCzVvkBP0yXk19zC7I1A,7604
|
||||||
|
blib2to3/pygram.cp310-win_amd64.pyd,sha256=lcaj-c3XjqwA53XGhRYvh5EUyECZTFGvEfj-Am7ap8I,10752
|
||||||
|
blib2to3/pygram.py,sha256=lZcZqHOonqdsUed8s83obK61RKkDoJVXmhdn9tBQJqs,5228
|
||||||
|
blib2to3/pytree.cp310-win_amd64.pyd,sha256=CpdO2hMynemn0cS1bSnIu-_jlwMdy2MnF0Fkl0uKb4U,10752
|
||||||
|
blib2to3/pytree.py,sha256=QG4GKi89fI18tHjfGm_Lf9v6Gh0PlVIR4PdS8ZfE47Q,33387
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: hatchling 1.29.0
|
||||||
|
Root-Is-Purelib: false
|
||||||
|
Tag: cp310-cp310-win_amd64
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
[console_scripts]
|
||||||
|
black = black:patched_main
|
||||||
|
blackd = blackd:patched_main [d]
|
||||||
|
|
||||||
|
[validate_pyproject.tool_schema]
|
||||||
|
black = black.schema:get_schema
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Łukasz Langa
|
||||||
|
|
||||||
|
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.
|
||||||
1679
.venv_codegen/Lib/site-packages/black/__init__.py
Normal file
1679
.venv_codegen/Lib/site-packages/black/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
3
.venv_codegen/Lib/site-packages/black/__main__.py
Normal file
3
.venv_codegen/Lib/site-packages/black/__main__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from black import patched_main
|
||||||
|
|
||||||
|
patched_main()
|
||||||
132
.venv_codegen/Lib/site-packages/black/_width_table.py
Normal file
132
.venv_codegen/Lib/site-packages/black/_width_table.py
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
# Generated by make_width_table.py
|
||||||
|
# wcwidth 0.2.14
|
||||||
|
# Unicode 17.0.0
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
WIDTH_TABLE: Final[list[tuple[int, int, int]]] = [
|
||||||
|
(4352, 4447, 2),
|
||||||
|
(8986, 8987, 2),
|
||||||
|
(9001, 9002, 2),
|
||||||
|
(9193, 9196, 2),
|
||||||
|
(9200, 9200, 2),
|
||||||
|
(9203, 9203, 2),
|
||||||
|
(9725, 9726, 2),
|
||||||
|
(9748, 9749, 2),
|
||||||
|
(9776, 9783, 2),
|
||||||
|
(9800, 9811, 2),
|
||||||
|
(9855, 9855, 2),
|
||||||
|
(9866, 9871, 2),
|
||||||
|
(9875, 9875, 2),
|
||||||
|
(9889, 9889, 2),
|
||||||
|
(9898, 9899, 2),
|
||||||
|
(9917, 9918, 2),
|
||||||
|
(9924, 9925, 2),
|
||||||
|
(9934, 9934, 2),
|
||||||
|
(9940, 9940, 2),
|
||||||
|
(9962, 9962, 2),
|
||||||
|
(9970, 9971, 2),
|
||||||
|
(9973, 9973, 2),
|
||||||
|
(9978, 9978, 2),
|
||||||
|
(9981, 9981, 2),
|
||||||
|
(9989, 9989, 2),
|
||||||
|
(9994, 9995, 2),
|
||||||
|
(10024, 10024, 2),
|
||||||
|
(10060, 10060, 2),
|
||||||
|
(10062, 10062, 2),
|
||||||
|
(10067, 10069, 2),
|
||||||
|
(10071, 10071, 2),
|
||||||
|
(10133, 10135, 2),
|
||||||
|
(10160, 10160, 2),
|
||||||
|
(10175, 10175, 2),
|
||||||
|
(11035, 11036, 2),
|
||||||
|
(11088, 11088, 2),
|
||||||
|
(11093, 11093, 2),
|
||||||
|
(11904, 11929, 2),
|
||||||
|
(11931, 12019, 2),
|
||||||
|
(12032, 12245, 2),
|
||||||
|
(12272, 12329, 2),
|
||||||
|
(12336, 12350, 2),
|
||||||
|
(12353, 12438, 2),
|
||||||
|
(12443, 12543, 2),
|
||||||
|
(12549, 12591, 2),
|
||||||
|
(12593, 12686, 2),
|
||||||
|
(12688, 12773, 2),
|
||||||
|
(12783, 12830, 2),
|
||||||
|
(12832, 12871, 2),
|
||||||
|
(12880, 42124, 2),
|
||||||
|
(42128, 42182, 2),
|
||||||
|
(43360, 43388, 2),
|
||||||
|
(44032, 55203, 2),
|
||||||
|
(63744, 64255, 2),
|
||||||
|
(65040, 65049, 2),
|
||||||
|
(65072, 65106, 2),
|
||||||
|
(65108, 65126, 2),
|
||||||
|
(65128, 65131, 2),
|
||||||
|
(65281, 65376, 2),
|
||||||
|
(65504, 65510, 2),
|
||||||
|
(94176, 94179, 2),
|
||||||
|
(94194, 94198, 2),
|
||||||
|
(94208, 101589, 2),
|
||||||
|
(101631, 101662, 2),
|
||||||
|
(101760, 101874, 2),
|
||||||
|
(110576, 110579, 2),
|
||||||
|
(110581, 110587, 2),
|
||||||
|
(110589, 110590, 2),
|
||||||
|
(110592, 110882, 2),
|
||||||
|
(110898, 110898, 2),
|
||||||
|
(110928, 110930, 2),
|
||||||
|
(110933, 110933, 2),
|
||||||
|
(110948, 110951, 2),
|
||||||
|
(110960, 111355, 2),
|
||||||
|
(119552, 119638, 2),
|
||||||
|
(119648, 119670, 2),
|
||||||
|
(126980, 126980, 2),
|
||||||
|
(127183, 127183, 2),
|
||||||
|
(127374, 127374, 2),
|
||||||
|
(127377, 127386, 2),
|
||||||
|
(127488, 127490, 2),
|
||||||
|
(127504, 127547, 2),
|
||||||
|
(127552, 127560, 2),
|
||||||
|
(127568, 127569, 2),
|
||||||
|
(127584, 127589, 2),
|
||||||
|
(127744, 127776, 2),
|
||||||
|
(127789, 127797, 2),
|
||||||
|
(127799, 127868, 2),
|
||||||
|
(127870, 127891, 2),
|
||||||
|
(127904, 127946, 2),
|
||||||
|
(127951, 127955, 2),
|
||||||
|
(127968, 127984, 2),
|
||||||
|
(127988, 127988, 2),
|
||||||
|
(127992, 127994, 2),
|
||||||
|
(128000, 128062, 2),
|
||||||
|
(128064, 128064, 2),
|
||||||
|
(128066, 128252, 2),
|
||||||
|
(128255, 128317, 2),
|
||||||
|
(128331, 128334, 2),
|
||||||
|
(128336, 128359, 2),
|
||||||
|
(128378, 128378, 2),
|
||||||
|
(128405, 128406, 2),
|
||||||
|
(128420, 128420, 2),
|
||||||
|
(128507, 128591, 2),
|
||||||
|
(128640, 128709, 2),
|
||||||
|
(128716, 128716, 2),
|
||||||
|
(128720, 128722, 2),
|
||||||
|
(128725, 128728, 2),
|
||||||
|
(128732, 128735, 2),
|
||||||
|
(128747, 128748, 2),
|
||||||
|
(128756, 128764, 2),
|
||||||
|
(128992, 129003, 2),
|
||||||
|
(129008, 129008, 2),
|
||||||
|
(129292, 129338, 2),
|
||||||
|
(129340, 129349, 2),
|
||||||
|
(129351, 129535, 2),
|
||||||
|
(129648, 129660, 2),
|
||||||
|
(129664, 129674, 2),
|
||||||
|
(129678, 129734, 2),
|
||||||
|
(129736, 129736, 2),
|
||||||
|
(129741, 129756, 2),
|
||||||
|
(129759, 129770, 2),
|
||||||
|
(129775, 129784, 2),
|
||||||
|
(131072, 196605, 2),
|
||||||
|
(196608, 262141, 2),
|
||||||
|
]
|
||||||
383
.venv_codegen/Lib/site-packages/black/brackets.py
Normal file
383
.venv_codegen/Lib/site-packages/black/brackets.py
Normal file
|
|
@ -0,0 +1,383 @@
|
||||||
|
"""Builds on top of nodes.py to track brackets."""
|
||||||
|
|
||||||
|
from collections.abc import Iterable, Sequence
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Final, Union
|
||||||
|
|
||||||
|
from black.nodes import (
|
||||||
|
BRACKET,
|
||||||
|
CLOSING_BRACKETS,
|
||||||
|
COMPARATORS,
|
||||||
|
LOGIC_OPERATORS,
|
||||||
|
MATH_OPERATORS,
|
||||||
|
OPENING_BRACKETS,
|
||||||
|
UNPACKING_PARENTS,
|
||||||
|
VARARGS_PARENTS,
|
||||||
|
is_vararg,
|
||||||
|
syms,
|
||||||
|
)
|
||||||
|
from blib2to3.pgen2 import token
|
||||||
|
from blib2to3.pytree import Leaf, Node
|
||||||
|
|
||||||
|
# types
|
||||||
|
LN = Union[Leaf, Node]
|
||||||
|
Depth = int
|
||||||
|
LeafID = int
|
||||||
|
NodeType = int
|
||||||
|
Priority = int
|
||||||
|
|
||||||
|
|
||||||
|
COMPREHENSION_PRIORITY: Final = 20
|
||||||
|
COMMA_PRIORITY: Final = 18
|
||||||
|
TERNARY_PRIORITY: Final = 16
|
||||||
|
LOGIC_PRIORITY: Final = 14
|
||||||
|
STRING_PRIORITY: Final = 12
|
||||||
|
COMPARATOR_PRIORITY: Final = 10
|
||||||
|
MATH_PRIORITIES: Final = {
|
||||||
|
token.VBAR: 9,
|
||||||
|
token.CIRCUMFLEX: 8,
|
||||||
|
token.AMPER: 7,
|
||||||
|
token.LEFTSHIFT: 6,
|
||||||
|
token.RIGHTSHIFT: 6,
|
||||||
|
token.PLUS: 5,
|
||||||
|
token.MINUS: 5,
|
||||||
|
token.STAR: 4,
|
||||||
|
token.SLASH: 4,
|
||||||
|
token.DOUBLESLASH: 4,
|
||||||
|
token.PERCENT: 4,
|
||||||
|
token.AT: 4,
|
||||||
|
token.TILDE: 3,
|
||||||
|
token.DOUBLESTAR: 2,
|
||||||
|
}
|
||||||
|
DOT_PRIORITY: Final = 1
|
||||||
|
|
||||||
|
|
||||||
|
class BracketMatchError(Exception):
|
||||||
|
"""Raised when an opening bracket is unable to be matched to a closing bracket."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BracketTracker:
|
||||||
|
"""Keeps track of brackets on a line."""
|
||||||
|
|
||||||
|
depth: int = 0
|
||||||
|
bracket_match: dict[tuple[Depth, NodeType], Leaf] = field(default_factory=dict)
|
||||||
|
delimiters: dict[LeafID, Priority] = field(default_factory=dict)
|
||||||
|
previous: Leaf | None = None
|
||||||
|
_for_loop_depths: list[int] = field(default_factory=list)
|
||||||
|
_lambda_argument_depths: list[int] = field(default_factory=list)
|
||||||
|
invisible: list[Leaf] = field(default_factory=list)
|
||||||
|
|
||||||
|
def mark(self, leaf: Leaf) -> None:
|
||||||
|
"""Mark `leaf` with bracket-related metadata. Keep track of delimiters.
|
||||||
|
|
||||||
|
All leaves receive an int `bracket_depth` field that stores how deep
|
||||||
|
within brackets a given leaf is. 0 means there are no enclosing brackets
|
||||||
|
that started on this line.
|
||||||
|
|
||||||
|
If a leaf is itself a closing bracket and there is a matching opening
|
||||||
|
bracket earlier, it receives an `opening_bracket` field with which it forms a
|
||||||
|
pair. This is a one-directional link to avoid reference cycles. Closing
|
||||||
|
bracket without opening happens on lines continued from previous
|
||||||
|
breaks, e.g. `) -> "ReturnType":` as part of a funcdef where we place
|
||||||
|
the return type annotation on its own line of the previous closing RPAR.
|
||||||
|
|
||||||
|
If a leaf is a delimiter (a token on which Black can split the line if
|
||||||
|
needed) and it's on depth 0, its `id()` is stored in the tracker's
|
||||||
|
`delimiters` field.
|
||||||
|
"""
|
||||||
|
if leaf.type == token.COMMENT:
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.depth == 0
|
||||||
|
and leaf.type in CLOSING_BRACKETS
|
||||||
|
and (self.depth, leaf.type) not in self.bracket_match
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.maybe_decrement_after_for_loop_variable(leaf)
|
||||||
|
self.maybe_decrement_after_lambda_arguments(leaf)
|
||||||
|
if leaf.type in CLOSING_BRACKETS:
|
||||||
|
self.depth -= 1
|
||||||
|
try:
|
||||||
|
opening_bracket = self.bracket_match.pop((self.depth, leaf.type))
|
||||||
|
except KeyError as e:
|
||||||
|
raise BracketMatchError(
|
||||||
|
"Unable to match a closing bracket to the following opening"
|
||||||
|
f" bracket: {leaf}"
|
||||||
|
) from e
|
||||||
|
leaf.opening_bracket = opening_bracket
|
||||||
|
if not leaf.value:
|
||||||
|
self.invisible.append(leaf)
|
||||||
|
leaf.bracket_depth = self.depth
|
||||||
|
if self.depth == 0:
|
||||||
|
delim = is_split_before_delimiter(leaf, self.previous)
|
||||||
|
if delim and self.previous is not None:
|
||||||
|
self.delimiters[id(self.previous)] = delim
|
||||||
|
else:
|
||||||
|
delim = is_split_after_delimiter(leaf)
|
||||||
|
if delim:
|
||||||
|
self.delimiters[id(leaf)] = delim
|
||||||
|
if leaf.type in OPENING_BRACKETS:
|
||||||
|
self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf
|
||||||
|
self.depth += 1
|
||||||
|
if not leaf.value:
|
||||||
|
self.invisible.append(leaf)
|
||||||
|
self.previous = leaf
|
||||||
|
self.maybe_increment_lambda_arguments(leaf)
|
||||||
|
self.maybe_increment_for_loop_variable(leaf)
|
||||||
|
|
||||||
|
def any_open_for_or_lambda(self) -> bool:
|
||||||
|
"""Return True if there is an open for or lambda expression on the line.
|
||||||
|
|
||||||
|
See maybe_increment_for_loop_variable and maybe_increment_lambda_arguments
|
||||||
|
for details."""
|
||||||
|
return bool(self._for_loop_depths or self._lambda_argument_depths)
|
||||||
|
|
||||||
|
def any_open_brackets(self) -> bool:
|
||||||
|
"""Return True if there is an yet unmatched open bracket on the line."""
|
||||||
|
return bool(self.bracket_match)
|
||||||
|
|
||||||
|
def max_delimiter_priority(self, exclude: Iterable[LeafID] = ()) -> Priority:
|
||||||
|
"""Return the highest priority of a delimiter found on the line.
|
||||||
|
|
||||||
|
Values are consistent with what `is_split_*_delimiter()` return.
|
||||||
|
Raises ValueError on no delimiters.
|
||||||
|
"""
|
||||||
|
return max(v for k, v in self.delimiters.items() if k not in exclude)
|
||||||
|
|
||||||
|
def delimiter_count_with_priority(self, priority: Priority = 0) -> int:
|
||||||
|
"""Return the number of delimiters with the given `priority`.
|
||||||
|
|
||||||
|
If no `priority` is passed, defaults to max priority on the line.
|
||||||
|
"""
|
||||||
|
if not self.delimiters:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
priority = priority or self.max_delimiter_priority()
|
||||||
|
return sum(1 for p in self.delimiters.values() if p == priority)
|
||||||
|
|
||||||
|
def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
|
||||||
|
"""In a for loop, or comprehension, the variables are often unpacks.
|
||||||
|
|
||||||
|
To avoid splitting on the comma in this situation, increase the depth of
|
||||||
|
tokens between `for` and `in`.
|
||||||
|
"""
|
||||||
|
if leaf.type == token.NAME and leaf.value == "for":
|
||||||
|
self.depth += 1
|
||||||
|
self._for_loop_depths.append(self.depth)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
|
||||||
|
"""See `maybe_increment_for_loop_variable` above for explanation."""
|
||||||
|
if (
|
||||||
|
self._for_loop_depths
|
||||||
|
and self._for_loop_depths[-1] == self.depth
|
||||||
|
and leaf.type == token.NAME
|
||||||
|
and leaf.value == "in"
|
||||||
|
):
|
||||||
|
self.depth -= 1
|
||||||
|
self._for_loop_depths.pop()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def maybe_increment_lambda_arguments(self, leaf: Leaf) -> bool:
|
||||||
|
"""In a lambda expression, there might be more than one argument.
|
||||||
|
|
||||||
|
To avoid splitting on the comma in this situation, increase the depth of
|
||||||
|
tokens between `lambda` and `:`.
|
||||||
|
"""
|
||||||
|
if leaf.type == token.NAME and leaf.value == "lambda":
|
||||||
|
self.depth += 1
|
||||||
|
self._lambda_argument_depths.append(self.depth)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def maybe_decrement_after_lambda_arguments(self, leaf: Leaf) -> bool:
|
||||||
|
"""See `maybe_increment_lambda_arguments` above for explanation."""
|
||||||
|
if (
|
||||||
|
self._lambda_argument_depths
|
||||||
|
and self._lambda_argument_depths[-1] == self.depth
|
||||||
|
and leaf.type == token.COLON
|
||||||
|
):
|
||||||
|
self.depth -= 1
|
||||||
|
self._lambda_argument_depths.pop()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_open_lsqb(self) -> Leaf | None:
|
||||||
|
"""Return the most recent opening square bracket (if any)."""
|
||||||
|
return self.bracket_match.get((self.depth - 1, token.RSQB))
|
||||||
|
|
||||||
|
|
||||||
|
def is_split_after_delimiter(leaf: Leaf) -> Priority:
|
||||||
|
"""Return the priority of the `leaf` delimiter, given a line break after it.
|
||||||
|
|
||||||
|
The delimiter priorities returned here are from those delimiters that would
|
||||||
|
cause a line break after themselves.
|
||||||
|
|
||||||
|
Higher numbers are higher priority.
|
||||||
|
"""
|
||||||
|
if leaf.type == token.COMMA:
|
||||||
|
return COMMA_PRIORITY
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def is_split_before_delimiter(leaf: Leaf, previous: Leaf | None = None) -> Priority:
|
||||||
|
"""Return the priority of the `leaf` delimiter, given a line break before it.
|
||||||
|
|
||||||
|
The delimiter priorities returned here are from those delimiters that would
|
||||||
|
cause a line break before themselves.
|
||||||
|
|
||||||
|
Higher numbers are higher priority.
|
||||||
|
"""
|
||||||
|
if is_vararg(leaf, within=VARARGS_PARENTS | UNPACKING_PARENTS):
|
||||||
|
# * and ** might also be MATH_OPERATORS but in this case they are not.
|
||||||
|
# Don't treat them as a delimiter.
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.type == token.DOT
|
||||||
|
and leaf.parent
|
||||||
|
and leaf.parent.type not in {syms.import_from, syms.dotted_name}
|
||||||
|
and (previous is None or previous.type in CLOSING_BRACKETS)
|
||||||
|
):
|
||||||
|
return DOT_PRIORITY
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.type in MATH_OPERATORS
|
||||||
|
and leaf.parent
|
||||||
|
and leaf.parent.type not in {syms.factor, syms.star_expr}
|
||||||
|
):
|
||||||
|
return MATH_PRIORITIES[leaf.type]
|
||||||
|
|
||||||
|
if leaf.type in COMPARATORS:
|
||||||
|
return COMPARATOR_PRIORITY
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.type == token.STRING
|
||||||
|
and previous is not None
|
||||||
|
and previous.type == token.STRING
|
||||||
|
):
|
||||||
|
return STRING_PRIORITY
|
||||||
|
|
||||||
|
if leaf.type not in {token.NAME, token.ASYNC}:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.value == "for"
|
||||||
|
and leaf.parent
|
||||||
|
and leaf.parent.type in {syms.comp_for, syms.old_comp_for}
|
||||||
|
or leaf.type == token.ASYNC
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
not isinstance(leaf.prev_sibling, Leaf)
|
||||||
|
or leaf.prev_sibling.value != "async"
|
||||||
|
):
|
||||||
|
return COMPREHENSION_PRIORITY
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.value == "if"
|
||||||
|
and leaf.parent
|
||||||
|
and leaf.parent.type in {syms.comp_if, syms.old_comp_if}
|
||||||
|
):
|
||||||
|
return COMPREHENSION_PRIORITY
|
||||||
|
|
||||||
|
if leaf.value in {"if", "else"} and leaf.parent and leaf.parent.type == syms.test:
|
||||||
|
return TERNARY_PRIORITY
|
||||||
|
|
||||||
|
if leaf.value == "is":
|
||||||
|
return COMPARATOR_PRIORITY
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.value == "in"
|
||||||
|
and leaf.parent
|
||||||
|
and leaf.parent.type in {syms.comp_op, syms.comparison}
|
||||||
|
and not (
|
||||||
|
previous is not None
|
||||||
|
and previous.type == token.NAME
|
||||||
|
and previous.value == "not"
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return COMPARATOR_PRIORITY
|
||||||
|
|
||||||
|
if (
|
||||||
|
leaf.value == "not"
|
||||||
|
and leaf.parent
|
||||||
|
and leaf.parent.type == syms.comp_op
|
||||||
|
and not (
|
||||||
|
previous is not None
|
||||||
|
and previous.type == token.NAME
|
||||||
|
and previous.value == "is"
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return COMPARATOR_PRIORITY
|
||||||
|
|
||||||
|
if leaf.value in LOGIC_OPERATORS and leaf.parent:
|
||||||
|
return LOGIC_PRIORITY
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def max_delimiter_priority_in_atom(node: LN) -> Priority:
|
||||||
|
"""Return maximum delimiter priority inside `node`.
|
||||||
|
|
||||||
|
This is specific to atoms with contents contained in a pair of parentheses.
|
||||||
|
If `node` isn't an atom or there are no enclosing parentheses, returns 0.
|
||||||
|
"""
|
||||||
|
if node.type != syms.atom:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
first = node.children[0]
|
||||||
|
last = node.children[-1]
|
||||||
|
if not (first.type == token.LPAR and last.type == token.RPAR):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
bt = BracketTracker()
|
||||||
|
for c in node.children[1:-1]:
|
||||||
|
if isinstance(c, Leaf):
|
||||||
|
bt.mark(c)
|
||||||
|
else:
|
||||||
|
for leaf in c.leaves():
|
||||||
|
bt.mark(leaf)
|
||||||
|
try:
|
||||||
|
return bt.max_delimiter_priority()
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_leaves_inside_matching_brackets(leaves: Sequence[Leaf]) -> set[LeafID]:
|
||||||
|
"""Return leaves that are inside matching brackets.
|
||||||
|
|
||||||
|
The input `leaves` can have non-matching brackets at the head or tail parts.
|
||||||
|
Matching brackets are included.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Start with the first opening bracket and ignore closing brackets before.
|
||||||
|
start_index = next(
|
||||||
|
i for i, l in enumerate(leaves) if l.type in OPENING_BRACKETS
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
return set()
|
||||||
|
bracket_stack = []
|
||||||
|
ids = set()
|
||||||
|
for i in range(start_index, len(leaves)):
|
||||||
|
leaf = leaves[i]
|
||||||
|
if leaf.type in OPENING_BRACKETS:
|
||||||
|
bracket_stack.append((BRACKET[leaf.type], i))
|
||||||
|
if leaf.type in CLOSING_BRACKETS:
|
||||||
|
if bracket_stack and leaf.type == bracket_stack[-1][0]:
|
||||||
|
_, start = bracket_stack.pop()
|
||||||
|
for j in range(start, i + 1):
|
||||||
|
ids.add(id(leaves[j]))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return ids
|
||||||
150
.venv_codegen/Lib/site-packages/black/cache.py
Normal file
150
.venv_codegen/Lib/site-packages/black/cache.py
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
"""Caching of formatted files with feature-based invalidation."""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
from platformdirs import user_cache_dir
|
||||||
|
|
||||||
|
from _black_version import version as __version__
|
||||||
|
from black.mode import Mode
|
||||||
|
from black.output import err
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
from typing import Self
|
||||||
|
else:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|
||||||
|
class FileData(NamedTuple):
|
||||||
|
st_mtime: float
|
||||||
|
st_size: int
|
||||||
|
hash: str
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_dir() -> Path:
|
||||||
|
"""Get the cache directory used by black.
|
||||||
|
|
||||||
|
Users can customize this directory on all systems using `BLACK_CACHE_DIR`
|
||||||
|
environment variable. By default, the cache directory is the user cache directory
|
||||||
|
under the black application.
|
||||||
|
|
||||||
|
This result is immediately set to a constant `black.cache.CACHE_DIR` as to avoid
|
||||||
|
repeated calls.
|
||||||
|
"""
|
||||||
|
# NOTE: Function mostly exists as a clean way to test getting the cache directory.
|
||||||
|
default_cache_dir = user_cache_dir("black")
|
||||||
|
cache_dir = Path(os.environ.get("BLACK_CACHE_DIR", default_cache_dir))
|
||||||
|
cache_dir = cache_dir / __version__
|
||||||
|
return cache_dir
|
||||||
|
|
||||||
|
|
||||||
|
CACHE_DIR = get_cache_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_file(mode: Mode) -> Path:
|
||||||
|
return CACHE_DIR / f"cache.{mode.get_cache_key()}.pickle"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Cache:
|
||||||
|
mode: Mode
|
||||||
|
cache_file: Path
|
||||||
|
file_data: dict[str, FileData] = field(default_factory=dict)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read(cls, mode: Mode) -> Self:
|
||||||
|
"""Read the cache if it exists and is well-formed.
|
||||||
|
|
||||||
|
If it is not well-formed, the call to write later should
|
||||||
|
resolve the issue.
|
||||||
|
"""
|
||||||
|
cache_file = get_cache_file(mode)
|
||||||
|
try:
|
||||||
|
exists = cache_file.exists()
|
||||||
|
except OSError as e:
|
||||||
|
# Likely file too long; see #4172 and #4174
|
||||||
|
err(f"Unable to read cache file {cache_file} due to {e}")
|
||||||
|
return cls(mode, cache_file)
|
||||||
|
if not exists:
|
||||||
|
return cls(mode, cache_file)
|
||||||
|
|
||||||
|
with cache_file.open("rb") as fobj:
|
||||||
|
try:
|
||||||
|
data: dict[str, tuple[float, int, str]] = pickle.load(fobj)
|
||||||
|
file_data = {k: FileData(*v) for k, v in data.items()}
|
||||||
|
except (pickle.UnpicklingError, ValueError, IndexError):
|
||||||
|
return cls(mode, cache_file)
|
||||||
|
|
||||||
|
return cls(mode, cache_file, file_data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hash_digest(path: Path) -> str:
|
||||||
|
"""Return hash digest for path."""
|
||||||
|
|
||||||
|
data = path.read_bytes()
|
||||||
|
return hashlib.sha256(data).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_file_data(path: Path) -> FileData:
|
||||||
|
"""Return file data for path."""
|
||||||
|
|
||||||
|
stat = path.stat()
|
||||||
|
hash = Cache.hash_digest(path)
|
||||||
|
return FileData(stat.st_mtime, stat.st_size, hash)
|
||||||
|
|
||||||
|
def is_changed(self, source: Path) -> bool:
|
||||||
|
"""Check if source has changed compared to cached version."""
|
||||||
|
res_src = source.resolve()
|
||||||
|
old = self.file_data.get(str(res_src))
|
||||||
|
if old is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
st = res_src.stat()
|
||||||
|
if st.st_size != old.st_size:
|
||||||
|
return True
|
||||||
|
if st.st_mtime != old.st_mtime:
|
||||||
|
new_hash = Cache.hash_digest(res_src)
|
||||||
|
if new_hash != old.hash:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def filtered_cached(self, sources: Iterable[Path]) -> tuple[set[Path], set[Path]]:
|
||||||
|
"""Split an iterable of paths in `sources` into two sets.
|
||||||
|
|
||||||
|
The first contains paths of files that modified on disk or are not in the
|
||||||
|
cache. The other contains paths to non-modified files.
|
||||||
|
"""
|
||||||
|
changed: set[Path] = set()
|
||||||
|
done: set[Path] = set()
|
||||||
|
for src in sources:
|
||||||
|
if self.is_changed(src):
|
||||||
|
changed.add(src)
|
||||||
|
else:
|
||||||
|
done.add(src)
|
||||||
|
return changed, done
|
||||||
|
|
||||||
|
def write(self, sources: Iterable[Path]) -> None:
|
||||||
|
"""Update the cache file data and write a new cache file."""
|
||||||
|
self.file_data.update(
|
||||||
|
**{str(src.resolve()): Cache.get_file_data(src) for src in sources}
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
dir=str(self.cache_file.parent), delete=False
|
||||||
|
) as f:
|
||||||
|
# We store raw tuples in the cache because it's faster.
|
||||||
|
data: dict[str, tuple[float, int, str]] = {
|
||||||
|
k: (*v,) for k, v in self.file_data.items()
|
||||||
|
}
|
||||||
|
pickle.dump(data, f, protocol=4)
|
||||||
|
os.replace(f.name, self.cache_file)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
827
.venv_codegen/Lib/site-packages/black/comments.py
Normal file
827
.venv_codegen/Lib/site-packages/black/comments.py
Normal file
|
|
@ -0,0 +1,827 @@
|
||||||
|
import re
|
||||||
|
from collections.abc import Collection, Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import lru_cache
|
||||||
|
from typing import Final, Union
|
||||||
|
|
||||||
|
from black.mode import Mode
|
||||||
|
from black.nodes import (
|
||||||
|
CLOSING_BRACKETS,
|
||||||
|
STANDALONE_COMMENT,
|
||||||
|
STATEMENT,
|
||||||
|
WHITESPACE,
|
||||||
|
container_of,
|
||||||
|
first_leaf_of,
|
||||||
|
is_type_comment_string,
|
||||||
|
make_simple_prefix,
|
||||||
|
preceding_leaf,
|
||||||
|
syms,
|
||||||
|
)
|
||||||
|
from blib2to3.pgen2 import token
|
||||||
|
from blib2to3.pytree import Leaf, Node
|
||||||
|
|
||||||
|
# types
|
||||||
|
LN = Union[Leaf, Node]
|
||||||
|
|
||||||
|
FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"}
|
||||||
|
FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"}
|
||||||
|
FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"}
|
||||||
|
|
||||||
|
# Compound statements we care about for fmt: skip handling
|
||||||
|
# (excludes except_clause and case_block which aren't standalone compound statements)
|
||||||
|
_COMPOUND_STATEMENTS: Final = STATEMENT - {syms.except_clause, syms.case_block}
|
||||||
|
|
||||||
|
COMMENT_EXCEPTIONS = " !:#'"
|
||||||
|
_COMMENT_PREFIX = "# "
|
||||||
|
_COMMENT_LIST_SEPARATOR = ";"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ProtoComment:
|
||||||
|
"""Describes a piece of syntax that is a comment.
|
||||||
|
|
||||||
|
It's not a :class:`blib2to3.pytree.Leaf` so that:
|
||||||
|
|
||||||
|
* it can be cached (`Leaf` objects should not be reused more than once as
|
||||||
|
they store their lineno, column, prefix, and parent information);
|
||||||
|
* `newlines` and `consumed` fields are kept separate from the `value`. This
|
||||||
|
simplifies handling of special marker comments like ``# fmt: off/on``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type: int # token.COMMENT or STANDALONE_COMMENT
|
||||||
|
value: str # content of the comment
|
||||||
|
newlines: int # how many newlines before the comment
|
||||||
|
consumed: int # how many characters of the original leaf's prefix did we consume
|
||||||
|
form_feed: bool # is there a form feed before the comment
|
||||||
|
leading_whitespace: str # leading whitespace before the comment, if any
|
||||||
|
|
||||||
|
|
||||||
|
def generate_comments(leaf: LN, mode: Mode) -> Iterator[Leaf]:
|
||||||
|
"""Clean the prefix of the `leaf` and generate comments from it, if any.
|
||||||
|
|
||||||
|
Comments in lib2to3 are shoved into the whitespace prefix. This happens
|
||||||
|
in `pgen2/driver.py:Driver.parse_tokens()`. This was a brilliant implementation
|
||||||
|
move because it does away with modifying the grammar to include all the
|
||||||
|
possible places in which comments can be placed.
|
||||||
|
|
||||||
|
The sad consequence for us though is that comments don't "belong" anywhere.
|
||||||
|
This is why this function generates simple parentless Leaf objects for
|
||||||
|
comments. We simply don't know what the correct parent should be.
|
||||||
|
|
||||||
|
No matter though, we can live without this. We really only need to
|
||||||
|
differentiate between inline and standalone comments. The latter don't
|
||||||
|
share the line with any code.
|
||||||
|
|
||||||
|
Inline comments are emitted as regular token.COMMENT leaves. Standalone
|
||||||
|
are emitted with a fake STANDALONE_COMMENT token identifier.
|
||||||
|
"""
|
||||||
|
total_consumed = 0
|
||||||
|
for pc in list_comments(
|
||||||
|
leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER, mode=mode
|
||||||
|
):
|
||||||
|
total_consumed = pc.consumed
|
||||||
|
prefix = make_simple_prefix(pc.newlines, pc.form_feed)
|
||||||
|
yield Leaf(pc.type, pc.value, prefix=prefix)
|
||||||
|
normalize_trailing_prefix(leaf, total_consumed)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=4096)
|
||||||
|
def list_comments(prefix: str, *, is_endmarker: bool, mode: Mode) -> list[ProtoComment]:
|
||||||
|
"""Return a list of :class:`ProtoComment` objects parsed from the given `prefix`."""
|
||||||
|
result: list[ProtoComment] = []
|
||||||
|
if not prefix or "#" not in prefix:
|
||||||
|
return result
|
||||||
|
|
||||||
|
consumed = 0
|
||||||
|
nlines = 0
|
||||||
|
ignored_lines = 0
|
||||||
|
form_feed = False
|
||||||
|
for index, full_line in enumerate(re.split("\r?\n|\r", prefix)):
|
||||||
|
consumed += len(full_line) + 1 # adding the length of the split '\n'
|
||||||
|
match = re.match(r"^(\s*)(\S.*|)$", full_line)
|
||||||
|
assert match
|
||||||
|
whitespace, line = match.groups()
|
||||||
|
if not line:
|
||||||
|
nlines += 1
|
||||||
|
if "\f" in full_line:
|
||||||
|
form_feed = True
|
||||||
|
if not line.startswith("#"):
|
||||||
|
# Escaped newlines outside of a comment are not really newlines at
|
||||||
|
# all. We treat a single-line comment following an escaped newline
|
||||||
|
# as a simple trailing comment.
|
||||||
|
if line.endswith("\\"):
|
||||||
|
ignored_lines += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if index == ignored_lines and not is_endmarker:
|
||||||
|
comment_type = token.COMMENT # simple trailing comment
|
||||||
|
else:
|
||||||
|
comment_type = STANDALONE_COMMENT
|
||||||
|
comment = make_comment(line, mode=mode)
|
||||||
|
result.append(
|
||||||
|
ProtoComment(
|
||||||
|
type=comment_type,
|
||||||
|
value=comment,
|
||||||
|
newlines=nlines,
|
||||||
|
consumed=consumed,
|
||||||
|
form_feed=form_feed,
|
||||||
|
leading_whitespace=whitespace,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
form_feed = False
|
||||||
|
nlines = 0
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_trailing_prefix(leaf: LN, total_consumed: int) -> None:
|
||||||
|
"""Normalize the prefix that's left over after generating comments.
|
||||||
|
|
||||||
|
Note: don't use backslashes for formatting or you'll lose your voting rights.
|
||||||
|
"""
|
||||||
|
remainder = leaf.prefix[total_consumed:]
|
||||||
|
if "\\" not in remainder:
|
||||||
|
nl_count = remainder.count("\n")
|
||||||
|
form_feed = "\f" in remainder and remainder.endswith("\n")
|
||||||
|
leaf.prefix = make_simple_prefix(nl_count, form_feed)
|
||||||
|
return
|
||||||
|
|
||||||
|
leaf.prefix = ""
|
||||||
|
|
||||||
|
|
||||||
|
def make_comment(content: str, mode: Mode) -> str:
|
||||||
|
"""Return a consistently formatted comment from the given `content` string.
|
||||||
|
|
||||||
|
All comments (except for "##", "#!", "#:", '#'") should have a single
|
||||||
|
space between the hash sign and the content.
|
||||||
|
|
||||||
|
If `content` didn't start with a hash sign, one is provided.
|
||||||
|
|
||||||
|
Comments containing fmt directives are preserved exactly as-is to respect
|
||||||
|
user intent (e.g., `#no space # fmt: skip` stays as-is).
|
||||||
|
"""
|
||||||
|
content = content.rstrip()
|
||||||
|
if not content:
|
||||||
|
return "#"
|
||||||
|
|
||||||
|
# Preserve comments with fmt directives exactly as-is
|
||||||
|
if content.startswith("#") and contains_fmt_directive(content):
|
||||||
|
return content
|
||||||
|
|
||||||
|
if content[0] == "#":
|
||||||
|
content = content[1:]
|
||||||
|
if (
|
||||||
|
content
|
||||||
|
and content[0] == "\N{NO-BREAK SPACE}"
|
||||||
|
and not is_type_comment_string("# " + content.lstrip(), mode=mode)
|
||||||
|
):
|
||||||
|
content = " " + content[1:] # Replace NBSP by a simple space
|
||||||
|
if (
|
||||||
|
content
|
||||||
|
and "\N{NO-BREAK SPACE}" not in content
|
||||||
|
and is_type_comment_string("#" + content, mode=mode)
|
||||||
|
):
|
||||||
|
type_part, value_part = content.split(":", 1)
|
||||||
|
content = type_part.strip() + ": " + value_part.strip()
|
||||||
|
|
||||||
|
if content and content[0] not in COMMENT_EXCEPTIONS:
|
||||||
|
content = " " + content
|
||||||
|
return "#" + content
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_fmt_off(
|
||||||
|
node: Node, mode: Mode, lines: Collection[tuple[int, int]]
|
||||||
|
) -> None:
|
||||||
|
"""Convert content between `# fmt: off`/`# fmt: on` into standalone comments."""
|
||||||
|
try_again = True
|
||||||
|
while try_again:
|
||||||
|
try_again = convert_one_fmt_off_pair(node, mode, lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _should_process_fmt_comment(
|
||||||
|
comment: ProtoComment, leaf: Leaf
|
||||||
|
) -> tuple[bool, bool, bool]:
|
||||||
|
"""Check if comment should be processed for fmt handling.
|
||||||
|
|
||||||
|
Returns (should_process, is_fmt_off, is_fmt_skip).
|
||||||
|
"""
|
||||||
|
is_fmt_off = contains_fmt_directive(comment.value, FMT_OFF)
|
||||||
|
is_fmt_skip = contains_fmt_directive(comment.value, FMT_SKIP)
|
||||||
|
|
||||||
|
if not is_fmt_off and not is_fmt_skip:
|
||||||
|
return False, False, False
|
||||||
|
|
||||||
|
# Invalid use when `# fmt: off` is applied before a closing bracket
|
||||||
|
if is_fmt_off and leaf.type in CLOSING_BRACKETS:
|
||||||
|
return False, False, False
|
||||||
|
|
||||||
|
return True, is_fmt_off, is_fmt_skip
|
||||||
|
|
||||||
|
|
||||||
|
def _is_valid_standalone_fmt_comment(
|
||||||
|
comment: ProtoComment, leaf: Leaf, is_fmt_off: bool, is_fmt_skip: bool
|
||||||
|
) -> bool:
|
||||||
|
"""Check if comment is a valid standalone fmt directive.
|
||||||
|
|
||||||
|
We only want standalone comments. If there's no previous leaf or if
|
||||||
|
the previous leaf is indentation, it's a standalone comment in disguise.
|
||||||
|
"""
|
||||||
|
if comment.type == STANDALONE_COMMENT:
|
||||||
|
return True
|
||||||
|
|
||||||
|
prev = preceding_leaf(leaf)
|
||||||
|
if not prev:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Treat STANDALONE_COMMENT nodes as whitespace for check
|
||||||
|
if is_fmt_off and prev.type not in WHITESPACE and prev.type != STANDALONE_COMMENT:
|
||||||
|
return False
|
||||||
|
if is_fmt_skip and prev.type in WHITESPACE:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_comment_only_fmt_block(
|
||||||
|
leaf: Leaf,
|
||||||
|
comment: ProtoComment,
|
||||||
|
previous_consumed: int,
|
||||||
|
mode: Mode,
|
||||||
|
) -> bool:
|
||||||
|
"""Handle fmt:off/on blocks that contain only comments.
|
||||||
|
|
||||||
|
Returns True if a block was converted, False otherwise.
|
||||||
|
"""
|
||||||
|
all_comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode)
|
||||||
|
|
||||||
|
# Find the first fmt:off and its matching fmt:on
|
||||||
|
fmt_off_idx = None
|
||||||
|
fmt_on_idx = None
|
||||||
|
for idx, c in enumerate(all_comments):
|
||||||
|
if fmt_off_idx is None and contains_fmt_directive(c.value, FMT_OFF):
|
||||||
|
fmt_off_idx = idx
|
||||||
|
if (
|
||||||
|
fmt_off_idx is not None
|
||||||
|
and idx > fmt_off_idx
|
||||||
|
and contains_fmt_directive(c.value, FMT_ON)
|
||||||
|
):
|
||||||
|
fmt_on_idx = idx
|
||||||
|
break
|
||||||
|
|
||||||
|
# Only proceed if we found both directives
|
||||||
|
if fmt_on_idx is None or fmt_off_idx is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
comment = all_comments[fmt_off_idx]
|
||||||
|
fmt_on_comment = all_comments[fmt_on_idx]
|
||||||
|
original_prefix = leaf.prefix
|
||||||
|
|
||||||
|
# Build the hidden value
|
||||||
|
start_pos = comment.consumed
|
||||||
|
end_pos = fmt_on_comment.consumed
|
||||||
|
content_between_and_fmt_on = original_prefix[start_pos:end_pos]
|
||||||
|
hidden_value = comment.value + "\n" + content_between_and_fmt_on
|
||||||
|
|
||||||
|
if hidden_value.endswith("\n"):
|
||||||
|
hidden_value = hidden_value[:-1]
|
||||||
|
|
||||||
|
# Build the standalone comment prefix - preserve all content before fmt:off
|
||||||
|
# including any comments that precede it
|
||||||
|
if fmt_off_idx == 0:
|
||||||
|
# No comments before fmt:off, use previous_consumed
|
||||||
|
pre_fmt_off_consumed = previous_consumed
|
||||||
|
else:
|
||||||
|
# Use the consumed position of the last comment before fmt:off
|
||||||
|
# This preserves all comments and content before the fmt:off directive
|
||||||
|
pre_fmt_off_consumed = all_comments[fmt_off_idx - 1].consumed
|
||||||
|
|
||||||
|
standalone_comment_prefix = (
|
||||||
|
original_prefix[:pre_fmt_off_consumed] + "\n" * comment.newlines
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt_off_prefix = original_prefix.split(comment.value)[0]
|
||||||
|
if "\n" in fmt_off_prefix:
|
||||||
|
fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
|
||||||
|
standalone_comment_prefix += fmt_off_prefix
|
||||||
|
|
||||||
|
# Update leaf prefix
|
||||||
|
leaf.prefix = original_prefix[fmt_on_comment.consumed :]
|
||||||
|
|
||||||
|
# Insert the STANDALONE_COMMENT
|
||||||
|
parent = leaf.parent
|
||||||
|
assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (prefix only)"
|
||||||
|
|
||||||
|
leaf_idx = None
|
||||||
|
for idx, child in enumerate(parent.children):
|
||||||
|
if child is leaf:
|
||||||
|
leaf_idx = idx
|
||||||
|
break
|
||||||
|
|
||||||
|
assert leaf_idx is not None, "INTERNAL ERROR: fmt: on/off handling (leaf index)"
|
||||||
|
|
||||||
|
parent.insert_child(
|
||||||
|
leaf_idx,
|
||||||
|
Leaf(
|
||||||
|
STANDALONE_COMMENT,
|
||||||
|
hidden_value,
|
||||||
|
prefix=standalone_comment_prefix,
|
||||||
|
fmt_pass_converted_first_leaf=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def convert_one_fmt_off_pair(
|
||||||
|
node: Node, mode: Mode, lines: Collection[tuple[int, int]]
|
||||||
|
) -> bool:
|
||||||
|
"""Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
|
||||||
|
|
||||||
|
Returns True if a pair was converted.
|
||||||
|
"""
|
||||||
|
for leaf in node.leaves():
|
||||||
|
# Skip STANDALONE_COMMENT nodes that were created by fmt:off/on/skip processing
|
||||||
|
# to avoid reprocessing them in subsequent iterations
|
||||||
|
if leaf.type == STANDALONE_COMMENT and hasattr(
|
||||||
|
leaf, "fmt_pass_converted_first_leaf"
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
previous_consumed = 0
|
||||||
|
for comment in list_comments(leaf.prefix, is_endmarker=False, mode=mode):
|
||||||
|
should_process, is_fmt_off, is_fmt_skip = _should_process_fmt_comment(
|
||||||
|
comment, leaf
|
||||||
|
)
|
||||||
|
if not should_process:
|
||||||
|
previous_consumed = comment.consumed
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not _is_valid_standalone_fmt_comment(
|
||||||
|
comment, leaf, is_fmt_off, is_fmt_skip
|
||||||
|
):
|
||||||
|
previous_consumed = comment.consumed
|
||||||
|
continue
|
||||||
|
|
||||||
|
ignored_nodes = list(generate_ignored_nodes(leaf, comment, mode))
|
||||||
|
|
||||||
|
# Handle comment-only blocks
|
||||||
|
if not ignored_nodes and is_fmt_off:
|
||||||
|
if _handle_comment_only_fmt_block(
|
||||||
|
leaf, comment, previous_consumed, mode
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Need actual nodes to process
|
||||||
|
if not ignored_nodes:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle regular fmt blocks
|
||||||
|
|
||||||
|
_handle_regular_fmt_block(
|
||||||
|
ignored_nodes,
|
||||||
|
comment,
|
||||||
|
previous_consumed,
|
||||||
|
is_fmt_skip,
|
||||||
|
lines,
|
||||||
|
leaf,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_regular_fmt_block(
|
||||||
|
ignored_nodes: list[LN],
|
||||||
|
comment: ProtoComment,
|
||||||
|
previous_consumed: int,
|
||||||
|
is_fmt_skip: bool,
|
||||||
|
lines: Collection[tuple[int, int]],
|
||||||
|
leaf: Leaf,
|
||||||
|
) -> None:
|
||||||
|
"""Handle fmt blocks with actual AST nodes."""
|
||||||
|
first = ignored_nodes[0] # Can be a container node with the `leaf`.
|
||||||
|
parent = first.parent
|
||||||
|
prefix = first.prefix
|
||||||
|
|
||||||
|
if contains_fmt_directive(comment.value, FMT_OFF):
|
||||||
|
first.prefix = prefix[comment.consumed :]
|
||||||
|
if is_fmt_skip:
|
||||||
|
first.prefix = ""
|
||||||
|
standalone_comment_prefix = prefix
|
||||||
|
else:
|
||||||
|
standalone_comment_prefix = prefix[:previous_consumed] + "\n" * comment.newlines
|
||||||
|
|
||||||
|
# Ensure STANDALONE_COMMENT nodes have trailing newlines when stringified
|
||||||
|
# This prevents multiple fmt: skip comments from being concatenated on one line
|
||||||
|
parts = []
|
||||||
|
for node in ignored_nodes:
|
||||||
|
if isinstance(node, Leaf) and node.type == STANDALONE_COMMENT:
|
||||||
|
# Add newline after STANDALONE_COMMENT Leaf
|
||||||
|
node_str = str(node)
|
||||||
|
if not node_str.endswith("\n"):
|
||||||
|
node_str += "\n"
|
||||||
|
parts.append(node_str)
|
||||||
|
elif isinstance(node, Node):
|
||||||
|
# For nodes that might contain STANDALONE_COMMENT leaves,
|
||||||
|
# we need custom stringify
|
||||||
|
has_standalone = any(
|
||||||
|
leaf.type == STANDALONE_COMMENT for leaf in node.leaves()
|
||||||
|
)
|
||||||
|
if has_standalone:
|
||||||
|
# Stringify node with STANDALONE_COMMENT leaves having trailing newlines
|
||||||
|
def stringify_node(n: LN) -> str:
|
||||||
|
if isinstance(n, Leaf):
|
||||||
|
if n.type == STANDALONE_COMMENT:
|
||||||
|
result = n.prefix + n.value
|
||||||
|
if not result.endswith("\n"):
|
||||||
|
result += "\n"
|
||||||
|
return result
|
||||||
|
return str(n)
|
||||||
|
else:
|
||||||
|
# For nested nodes, recursively process children
|
||||||
|
return "".join(stringify_node(child) for child in n.children)
|
||||||
|
|
||||||
|
parts.append(stringify_node(node))
|
||||||
|
else:
|
||||||
|
parts.append(str(node))
|
||||||
|
else:
|
||||||
|
parts.append(str(node))
|
||||||
|
|
||||||
|
hidden_value = "".join(parts)
|
||||||
|
comment_lineno = leaf.lineno - comment.newlines
|
||||||
|
|
||||||
|
if contains_fmt_directive(comment.value, FMT_OFF):
|
||||||
|
fmt_off_prefix = ""
|
||||||
|
if len(lines) > 0 and not any(
|
||||||
|
line[0] <= comment_lineno <= line[1] for line in lines
|
||||||
|
):
|
||||||
|
# keeping indentation of comment by preserving original whitespaces.
|
||||||
|
fmt_off_prefix = prefix.split(comment.value)[0]
|
||||||
|
if "\n" in fmt_off_prefix:
|
||||||
|
fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
|
||||||
|
standalone_comment_prefix += fmt_off_prefix
|
||||||
|
hidden_value = comment.value + "\n" + hidden_value
|
||||||
|
|
||||||
|
if is_fmt_skip:
|
||||||
|
hidden_value += comment.leading_whitespace + comment.value
|
||||||
|
|
||||||
|
if hidden_value.endswith("\n"):
|
||||||
|
# That happens when one of the `ignored_nodes` ended with a NEWLINE
|
||||||
|
# leaf (possibly followed by a DEDENT).
|
||||||
|
hidden_value = hidden_value[:-1]
|
||||||
|
|
||||||
|
first_idx: int | None = None
|
||||||
|
for ignored in ignored_nodes:
|
||||||
|
index = ignored.remove()
|
||||||
|
if first_idx is None:
|
||||||
|
first_idx = index
|
||||||
|
|
||||||
|
assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (1)"
|
||||||
|
assert first_idx is not None, "INTERNAL ERROR: fmt: on/off handling (2)"
|
||||||
|
|
||||||
|
parent.insert_child(
|
||||||
|
first_idx,
|
||||||
|
Leaf(
|
||||||
|
STANDALONE_COMMENT,
|
||||||
|
hidden_value,
|
||||||
|
prefix=standalone_comment_prefix,
|
||||||
|
fmt_pass_converted_first_leaf=first_leaf_of(first),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ignored_nodes(
|
||||||
|
leaf: Leaf, comment: ProtoComment, mode: Mode
|
||||||
|
) -> Iterator[LN]:
|
||||||
|
"""Starting from the container of `leaf`, generate all leaves until `# fmt: on`.
|
||||||
|
|
||||||
|
If comment is skip, returns leaf only.
|
||||||
|
Stops at the end of the block.
|
||||||
|
"""
|
||||||
|
if contains_fmt_directive(comment.value, FMT_SKIP):
|
||||||
|
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode)
|
||||||
|
return
|
||||||
|
container: LN | None = container_of(leaf)
|
||||||
|
while container is not None and container.type != token.ENDMARKER:
|
||||||
|
if is_fmt_on(container, mode=mode):
|
||||||
|
return
|
||||||
|
|
||||||
|
# fix for fmt: on in children
|
||||||
|
if children_contains_fmt_on(container, mode=mode):
|
||||||
|
for index, child in enumerate(container.children):
|
||||||
|
if isinstance(child, Leaf) and is_fmt_on(child, mode=mode):
|
||||||
|
if child.type in CLOSING_BRACKETS:
|
||||||
|
# This means `# fmt: on` is placed at a different bracket level
|
||||||
|
# than `# fmt: off`. This is an invalid use, but as a courtesy,
|
||||||
|
# we include this closing bracket in the ignored nodes.
|
||||||
|
# The alternative is to fail the formatting.
|
||||||
|
yield child
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
child.type == token.INDENT
|
||||||
|
and index < len(container.children) - 1
|
||||||
|
and children_contains_fmt_on(
|
||||||
|
container.children[index + 1], mode=mode
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# This means `# fmt: on` is placed right after an indentation
|
||||||
|
# level, and we shouldn't swallow the previous INDENT token.
|
||||||
|
return
|
||||||
|
if children_contains_fmt_on(child, mode=mode):
|
||||||
|
return
|
||||||
|
yield child
|
||||||
|
else:
|
||||||
|
if container.type == token.DEDENT and container.next_sibling is None:
|
||||||
|
# This can happen when there is no matching `# fmt: on` comment at the
|
||||||
|
# same level as `# fmt: on`. We need to keep this DEDENT.
|
||||||
|
return
|
||||||
|
yield container
|
||||||
|
container = container.next_sibling
|
||||||
|
|
||||||
|
|
||||||
|
def _find_compound_statement_context(parent: Node) -> Node | None:
|
||||||
|
"""Return the body node of a compound statement if we should respect fmt: skip.
|
||||||
|
|
||||||
|
This handles one-line compound statements like:
|
||||||
|
if condition: body # fmt: skip
|
||||||
|
|
||||||
|
When Black expands such statements, they temporarily look like:
|
||||||
|
if condition:
|
||||||
|
body # fmt: skip
|
||||||
|
|
||||||
|
In both cases, we want to return the body node (either the simple_stmt directly
|
||||||
|
or the suite containing it).
|
||||||
|
"""
|
||||||
|
if parent.type != syms.simple_stmt:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not isinstance(parent.parent, Node):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Case 1: Expanded form after Black's initial formatting pass.
|
||||||
|
# The one-liner has been split across multiple lines:
|
||||||
|
# if True:
|
||||||
|
# print("a"); print("b") # fmt: skip
|
||||||
|
# Structure: compound_stmt -> suite -> simple_stmt
|
||||||
|
if (
|
||||||
|
parent.parent.type == syms.suite
|
||||||
|
and isinstance(parent.parent.parent, Node)
|
||||||
|
and parent.parent.parent.type in _COMPOUND_STATEMENTS
|
||||||
|
):
|
||||||
|
return parent.parent
|
||||||
|
|
||||||
|
# Case 2: Original one-line form from the input source.
|
||||||
|
# The statement is still on a single line:
|
||||||
|
# if True: print("a"); print("b") # fmt: skip
|
||||||
|
# Structure: compound_stmt -> simple_stmt
|
||||||
|
if parent.parent.type in _COMPOUND_STATEMENTS:
|
||||||
|
return parent
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _should_keep_compound_statement_inline(
|
||||||
|
body_node: Node, simple_stmt_parent: Node
|
||||||
|
) -> bool:
|
||||||
|
"""Check if a compound statement should be kept on one line.
|
||||||
|
|
||||||
|
Returns True only for compound statements with semicolon-separated bodies,
|
||||||
|
like: if True: print("a"); print("b") # fmt: skip
|
||||||
|
"""
|
||||||
|
# Check if there are semicolons in the body
|
||||||
|
for leaf in body_node.leaves():
|
||||||
|
if leaf.type == token.SEMI:
|
||||||
|
# Verify it's a single-line body (one simple_stmt)
|
||||||
|
if body_node.type == syms.suite:
|
||||||
|
# After formatting: check suite has one simple_stmt child
|
||||||
|
simple_stmts = [
|
||||||
|
child
|
||||||
|
for child in body_node.children
|
||||||
|
if child.type == syms.simple_stmt
|
||||||
|
]
|
||||||
|
return len(simple_stmts) == 1 and simple_stmts[0] is simple_stmt_parent
|
||||||
|
else:
|
||||||
|
# Original form: body_node IS the simple_stmt
|
||||||
|
return body_node is simple_stmt_parent
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _get_compound_statement_header(
|
||||||
|
body_node: Node, simple_stmt_parent: Node
|
||||||
|
) -> list[LN]:
|
||||||
|
"""Get header nodes for a compound statement that should be preserved inline."""
|
||||||
|
if not _should_keep_compound_statement_inline(body_node, simple_stmt_parent):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Get the compound statement (parent of body)
|
||||||
|
compound_stmt = body_node.parent
|
||||||
|
if compound_stmt is None or compound_stmt.type not in _COMPOUND_STATEMENTS:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Collect all header leaves before the body
|
||||||
|
header_leaves: list[LN] = []
|
||||||
|
for child in compound_stmt.children:
|
||||||
|
if child is body_node:
|
||||||
|
break
|
||||||
|
if isinstance(child, Leaf):
|
||||||
|
if child.type not in (token.NEWLINE, token.INDENT):
|
||||||
|
header_leaves.append(child)
|
||||||
|
else:
|
||||||
|
header_leaves.extend(child.leaves())
|
||||||
|
return header_leaves
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_ignored_nodes_from_fmt_skip(
|
||||||
|
leaf: Leaf, comment: ProtoComment, mode: Mode
|
||||||
|
) -> Iterator[LN]:
|
||||||
|
"""Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
|
||||||
|
prev_sibling = leaf.prev_sibling
|
||||||
|
parent = leaf.parent
|
||||||
|
ignored_nodes: list[LN] = []
|
||||||
|
# Need to properly format the leaf prefix to compare it to comment.value,
|
||||||
|
# which is also formatted
|
||||||
|
comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode)
|
||||||
|
if not comments or comment.value != comments[0].value:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not prev_sibling and parent:
|
||||||
|
prev_sibling = parent.prev_sibling
|
||||||
|
|
||||||
|
if prev_sibling is not None:
|
||||||
|
leaf.prefix = leaf.prefix[comment.consumed :]
|
||||||
|
|
||||||
|
# Generates the nodes to be ignored by `fmt: skip`.
|
||||||
|
|
||||||
|
# Nodes to ignore are the ones on the same line as the
|
||||||
|
# `# fmt: skip` comment, excluding the `# fmt: skip`
|
||||||
|
# node itself.
|
||||||
|
|
||||||
|
# Traversal process (starting at the `# fmt: skip` node):
|
||||||
|
# 1. Move to the `prev_sibling` of the current node.
|
||||||
|
# 2. If `prev_sibling` has children, go to its rightmost leaf.
|
||||||
|
# 3. If there's no `prev_sibling`, move up to the parent
|
||||||
|
# node and repeat.
|
||||||
|
# 4. Continue until:
|
||||||
|
# a. You encounter an `INDENT` or `NEWLINE` node (indicates
|
||||||
|
# start of the line).
|
||||||
|
# b. You reach the root node.
|
||||||
|
|
||||||
|
# Include all visited LEAVES in the ignored list, except INDENT
|
||||||
|
# or NEWLINE leaves.
|
||||||
|
|
||||||
|
current_node = prev_sibling
|
||||||
|
ignored_nodes = [current_node]
|
||||||
|
if current_node.prev_sibling is None and current_node.parent is not None:
|
||||||
|
current_node = current_node.parent
|
||||||
|
|
||||||
|
# Track seen nodes to detect cycles that can occur after tree modifications
|
||||||
|
seen_nodes = {id(current_node)}
|
||||||
|
|
||||||
|
while "\n" not in current_node.prefix and current_node.prev_sibling is not None:
|
||||||
|
leaf_nodes = list(current_node.prev_sibling.leaves())
|
||||||
|
next_node = leaf_nodes[-1] if leaf_nodes else current_node
|
||||||
|
|
||||||
|
# Detect infinite loop - if we've seen this node before, stop
|
||||||
|
# This can happen when STANDALONE_COMMENT nodes are inserted
|
||||||
|
# during processing
|
||||||
|
if id(next_node) in seen_nodes:
|
||||||
|
break
|
||||||
|
|
||||||
|
current_node = next_node
|
||||||
|
seen_nodes.add(id(current_node))
|
||||||
|
|
||||||
|
# Stop if we encounter a STANDALONE_COMMENT created by fmt processing
|
||||||
|
if (
|
||||||
|
isinstance(current_node, Leaf)
|
||||||
|
and current_node.type == STANDALONE_COMMENT
|
||||||
|
and hasattr(current_node, "fmt_pass_converted_first_leaf")
|
||||||
|
):
|
||||||
|
break
|
||||||
|
|
||||||
|
if (
|
||||||
|
current_node.type in CLOSING_BRACKETS
|
||||||
|
and current_node.parent
|
||||||
|
and current_node.parent.type == syms.atom
|
||||||
|
):
|
||||||
|
current_node = current_node.parent
|
||||||
|
|
||||||
|
if current_node.type in (token.NEWLINE, token.INDENT):
|
||||||
|
current_node.prefix = ""
|
||||||
|
break
|
||||||
|
|
||||||
|
if current_node.type == token.DEDENT:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Special case for with expressions
|
||||||
|
# Without this, we can stuck inside the asexpr_test's children's children
|
||||||
|
if (
|
||||||
|
current_node.parent
|
||||||
|
and current_node.parent.type == syms.asexpr_test
|
||||||
|
and current_node.parent.parent
|
||||||
|
and current_node.parent.parent.type == syms.with_stmt
|
||||||
|
):
|
||||||
|
current_node = current_node.parent
|
||||||
|
|
||||||
|
ignored_nodes.insert(0, current_node)
|
||||||
|
|
||||||
|
if current_node.prev_sibling is None and current_node.parent is not None:
|
||||||
|
current_node = current_node.parent
|
||||||
|
|
||||||
|
# Special handling for compound statements with semicolon-separated bodies
|
||||||
|
if isinstance(parent, Node):
|
||||||
|
body_node = _find_compound_statement_context(parent)
|
||||||
|
if body_node is not None:
|
||||||
|
header_nodes = _get_compound_statement_header(body_node, parent)
|
||||||
|
if header_nodes:
|
||||||
|
ignored_nodes = header_nodes + ignored_nodes
|
||||||
|
|
||||||
|
yield from ignored_nodes
|
||||||
|
elif (
|
||||||
|
parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
|
||||||
|
):
|
||||||
|
# The `# fmt: skip` is on the colon line of the if/while/def/class/...
|
||||||
|
# statements. The ignored nodes should be previous siblings of the
|
||||||
|
# parent suite node.
|
||||||
|
leaf.prefix = ""
|
||||||
|
parent_sibling = parent.prev_sibling
|
||||||
|
while parent_sibling is not None and parent_sibling.type != syms.suite:
|
||||||
|
ignored_nodes.insert(0, parent_sibling)
|
||||||
|
parent_sibling = parent_sibling.prev_sibling
|
||||||
|
# Special case for `async_stmt` where the ASYNC token is on the
|
||||||
|
# grandparent node.
|
||||||
|
grandparent = parent.parent
|
||||||
|
if (
|
||||||
|
grandparent is not None
|
||||||
|
and grandparent.prev_sibling is not None
|
||||||
|
and grandparent.prev_sibling.type == token.ASYNC
|
||||||
|
):
|
||||||
|
ignored_nodes.insert(0, grandparent.prev_sibling)
|
||||||
|
yield from iter(ignored_nodes)
|
||||||
|
|
||||||
|
|
||||||
|
def is_fmt_on(container: LN, mode: Mode) -> bool:
|
||||||
|
"""Determine whether formatting is switched on within a container.
|
||||||
|
Determined by whether the last `# fmt:` comment is `on` or `off`.
|
||||||
|
"""
|
||||||
|
fmt_on = False
|
||||||
|
for comment in list_comments(container.prefix, is_endmarker=False, mode=mode):
|
||||||
|
if contains_fmt_directive(comment.value, FMT_ON):
|
||||||
|
fmt_on = True
|
||||||
|
elif contains_fmt_directive(comment.value, FMT_OFF):
|
||||||
|
fmt_on = False
|
||||||
|
return fmt_on
|
||||||
|
|
||||||
|
|
||||||
|
def children_contains_fmt_on(container: LN, mode: Mode) -> bool:
|
||||||
|
"""Determine if children have formatting switched on."""
|
||||||
|
for child in container.children:
|
||||||
|
leaf = first_leaf_of(child)
|
||||||
|
if leaf is not None and is_fmt_on(leaf, mode=mode):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def contains_pragma_comment(comment_list: list[Leaf]) -> bool:
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
True iff one of the comments in @comment_list is a pragma used by one
|
||||||
|
of the more common static analysis tools for python (e.g. mypy, flake8,
|
||||||
|
pylint).
|
||||||
|
"""
|
||||||
|
for comment in comment_list:
|
||||||
|
if comment.value.startswith(("# type:", "# noqa", "# pylint:")):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def contains_fmt_directive(
|
||||||
|
comment_line: str, directives: set[str] = FMT_OFF | FMT_ON | FMT_SKIP
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the given comment contains format directives, alone or paired with
|
||||||
|
other comments.
|
||||||
|
|
||||||
|
Defaults to checking all directives (skip, off, on, yapf), but can be
|
||||||
|
narrowed to specific ones.
|
||||||
|
|
||||||
|
Matching styles:
|
||||||
|
# foobar <-- single comment
|
||||||
|
# foobar # foobar # foobar <-- multiple comments
|
||||||
|
# foobar; foobar <-- list of comments (; separated)
|
||||||
|
"""
|
||||||
|
semantic_comment_blocks = [
|
||||||
|
comment_line,
|
||||||
|
*[
|
||||||
|
_COMMENT_PREFIX + comment.strip()
|
||||||
|
for comment in comment_line.split(_COMMENT_PREFIX)[1:]
|
||||||
|
],
|
||||||
|
*[
|
||||||
|
_COMMENT_PREFIX + comment.strip()
|
||||||
|
for comment in comment_line.strip(_COMMENT_PREFIX).split(
|
||||||
|
_COMMENT_LIST_SEPARATOR
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
return any(comment in directives for comment in semantic_comment_blocks)
|
||||||
221
.venv_codegen/Lib/site-packages/black/concurrency.py
Normal file
221
.venv_codegen/Lib/site-packages/black/concurrency.py
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
"""
|
||||||
|
Formatting many files at once via multiprocessing. Contains entrypoint and utilities.
|
||||||
|
|
||||||
|
NOTE: this module is only imported if we need to format several files at once.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor
|
||||||
|
from multiprocessing import Manager
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from mypy_extensions import mypyc_attr
|
||||||
|
|
||||||
|
from black import WriteBack, format_file_in_place
|
||||||
|
from black.cache import Cache
|
||||||
|
from black.mode import Mode
|
||||||
|
from black.output import err
|
||||||
|
from black.report import Changed, Report
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_use_uvloop() -> asyncio.AbstractEventLoop:
|
||||||
|
"""If our environment has uvloop or winloop installed we use it otherwise
|
||||||
|
a normal asyncio eventloop is called as fallback.
|
||||||
|
|
||||||
|
This is called only from command-line entry points to avoid
|
||||||
|
interfering with the parent process if Black is used as a library.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if sys.platform != "win32":
|
||||||
|
import uvloop
|
||||||
|
|
||||||
|
return uvloop.new_event_loop()
|
||||||
|
else:
|
||||||
|
import winloop
|
||||||
|
|
||||||
|
return winloop.new_event_loop()
|
||||||
|
except ImportError:
|
||||||
|
return asyncio.new_event_loop()
|
||||||
|
|
||||||
|
|
||||||
|
def cancel(tasks: Iterable[asyncio.Future[Any]]) -> None:
|
||||||
|
"""asyncio signal handler that cancels all `tasks` and reports to stderr."""
|
||||||
|
err("Aborted!")
|
||||||
|
for task in tasks:
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown(loop: asyncio.AbstractEventLoop) -> None:
|
||||||
|
"""Cancel all pending tasks on `loop`, wait for them, and close the loop."""
|
||||||
|
try:
|
||||||
|
# This part is borrowed from asyncio/runners.py in Python 3.7b2.
|
||||||
|
to_cancel = [task for task in asyncio.all_tasks(loop) if not task.done()]
|
||||||
|
if not to_cancel:
|
||||||
|
return
|
||||||
|
|
||||||
|
for task in to_cancel:
|
||||||
|
task.cancel()
|
||||||
|
loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))
|
||||||
|
finally:
|
||||||
|
# `concurrent.futures.Future` objects cannot be cancelled once they
|
||||||
|
# are already running. There might be some when the `shutdown()` happened.
|
||||||
|
# Silence their logger's spew about the event loop being closed.
|
||||||
|
cf_logger = logging.getLogger("concurrent.futures")
|
||||||
|
cf_logger.setLevel(logging.CRITICAL)
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
# diff-shades depends on being to monkeypatch this function to operate. I know it's
|
||||||
|
# not ideal, but this shouldn't cause any issues ... hopefully. ~ichard26
|
||||||
|
@mypyc_attr(patchable=True)
|
||||||
|
def reformat_many(
|
||||||
|
sources: set[Path],
|
||||||
|
fast: bool,
|
||||||
|
write_back: WriteBack,
|
||||||
|
mode: Mode,
|
||||||
|
report: Report,
|
||||||
|
workers: int | None,
|
||||||
|
no_cache: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Reformat multiple files using a ProcessPoolExecutor."""
|
||||||
|
|
||||||
|
if workers is None:
|
||||||
|
workers = int(os.environ.get("BLACK_NUM_WORKERS", 0))
|
||||||
|
workers = workers or os.cpu_count() or 1
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# Work around https://bugs.python.org/issue26903
|
||||||
|
workers = min(workers, 60)
|
||||||
|
if getattr(sys, "frozen", False):
|
||||||
|
# In frozen builds (e.g. PyInstaller), avoid spawning worker processes (i.e.
|
||||||
|
# avoid using ProcessPoolExecutor) to prevent shutdown errors when workers
|
||||||
|
# try to import modules after cleanup begins.
|
||||||
|
# See https://github.com/psf/black/issues/4823
|
||||||
|
workers = 1
|
||||||
|
|
||||||
|
executor: Executor | None = None
|
||||||
|
if workers > 1:
|
||||||
|
try:
|
||||||
|
executor = ProcessPoolExecutor(max_workers=workers)
|
||||||
|
except (ImportError, NotImplementedError, OSError):
|
||||||
|
# we arrive here if the underlying system does not support multi-processing
|
||||||
|
# like in AWS Lambda or Termux, in which case we gracefully fallback to
|
||||||
|
# a ThreadPoolExecutor with just a single worker (more workers would not do
|
||||||
|
# us any good due to the Global Interpreter Lock)
|
||||||
|
pass
|
||||||
|
|
||||||
|
if executor is None:
|
||||||
|
executor = ThreadPoolExecutor(max_workers=1)
|
||||||
|
|
||||||
|
loop = maybe_use_uvloop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(
|
||||||
|
schedule_formatting(
|
||||||
|
sources=sources,
|
||||||
|
fast=fast,
|
||||||
|
write_back=write_back,
|
||||||
|
mode=mode,
|
||||||
|
report=report,
|
||||||
|
loop=loop,
|
||||||
|
executor=executor,
|
||||||
|
no_cache=no_cache,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
shutdown(loop)
|
||||||
|
finally:
|
||||||
|
asyncio.set_event_loop(None)
|
||||||
|
if executor is not None:
|
||||||
|
executor.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
async def schedule_formatting(
|
||||||
|
sources: set[Path],
|
||||||
|
fast: bool,
|
||||||
|
write_back: WriteBack,
|
||||||
|
mode: Mode,
|
||||||
|
report: Report,
|
||||||
|
loop: asyncio.AbstractEventLoop,
|
||||||
|
executor: Executor,
|
||||||
|
no_cache: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Run formatting of `sources` in parallel using the provided `executor`.
|
||||||
|
|
||||||
|
(Use ProcessPoolExecutors for actual parallelism.)
|
||||||
|
|
||||||
|
`write_back`, `fast`, and `mode` options are passed to
|
||||||
|
:func:`format_file_in_place`.
|
||||||
|
"""
|
||||||
|
cache = None if no_cache else Cache.read(mode)
|
||||||
|
if cache is not None and write_back not in (
|
||||||
|
WriteBack.DIFF,
|
||||||
|
WriteBack.COLOR_DIFF,
|
||||||
|
):
|
||||||
|
sources, cached = cache.filtered_cached(sources)
|
||||||
|
for src in sorted(cached):
|
||||||
|
report.done(src, Changed.CACHED)
|
||||||
|
if not sources:
|
||||||
|
return
|
||||||
|
|
||||||
|
cancelled = []
|
||||||
|
sources_to_cache = []
|
||||||
|
lock = None
|
||||||
|
manager = None
|
||||||
|
if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
|
||||||
|
# For diff output, we need locks to ensure we don't interleave output
|
||||||
|
# from different processes.
|
||||||
|
manager = Manager()
|
||||||
|
lock = manager.Lock()
|
||||||
|
|
||||||
|
try:
|
||||||
|
tasks = {
|
||||||
|
asyncio.ensure_future(
|
||||||
|
loop.run_in_executor(
|
||||||
|
executor, format_file_in_place, src, fast, mode, write_back, lock
|
||||||
|
)
|
||||||
|
): src
|
||||||
|
for src in sorted(sources)
|
||||||
|
}
|
||||||
|
pending = tasks.keys()
|
||||||
|
try:
|
||||||
|
loop.add_signal_handler(signal.SIGINT, cancel, pending)
|
||||||
|
loop.add_signal_handler(signal.SIGTERM, cancel, pending)
|
||||||
|
except NotImplementedError:
|
||||||
|
# There are no good alternatives for these on Windows.
|
||||||
|
pass
|
||||||
|
while pending:
|
||||||
|
done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
||||||
|
for task in done:
|
||||||
|
src = tasks.pop(task)
|
||||||
|
if task.cancelled():
|
||||||
|
cancelled.append(task)
|
||||||
|
elif exc := task.exception():
|
||||||
|
if report.verbose:
|
||||||
|
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
||||||
|
report.failed(src, str(exc))
|
||||||
|
else:
|
||||||
|
changed = Changed.YES if task.result() else Changed.NO
|
||||||
|
# If the file was written back or was successfully checked as
|
||||||
|
# well-formatted, store this information in the cache.
|
||||||
|
if write_back is WriteBack.YES or (
|
||||||
|
write_back is WriteBack.CHECK and changed is Changed.NO
|
||||||
|
):
|
||||||
|
sources_to_cache.append(src)
|
||||||
|
report.done(src, changed)
|
||||||
|
if cancelled:
|
||||||
|
await asyncio.gather(*cancelled, return_exceptions=True)
|
||||||
|
if sources_to_cache and not no_cache and cache is not None:
|
||||||
|
cache.write(sources_to_cache)
|
||||||
|
finally:
|
||||||
|
if manager is not None:
|
||||||
|
manager.shutdown()
|
||||||
4
.venv_codegen/Lib/site-packages/black/const.py
Normal file
4
.venv_codegen/Lib/site-packages/black/const.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
DEFAULT_LINE_LENGTH = 88
|
||||||
|
DEFAULT_EXCLUDES = r"/(\.direnv|\.eggs|\.git|\.hg|\.ipynb_checkpoints|\.mypy_cache|\.nox|\.pytest_cache|\.ruff_cache|\.tox|\.svn|\.venv|\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/" # noqa: B950
|
||||||
|
DEFAULT_INCLUDES = r"(\.pyi?|\.ipynb)$"
|
||||||
|
STDIN_PLACEHOLDER = "__BLACK_STDIN_FILENAME__"
|
||||||
55
.venv_codegen/Lib/site-packages/black/debug.py
Normal file
55
.venv_codegen/Lib/site-packages/black/debug.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any, TypeVar, Union
|
||||||
|
|
||||||
|
from black.nodes import Visitor
|
||||||
|
from black.output import out
|
||||||
|
from black.parsing import lib2to3_parse
|
||||||
|
from blib2to3.pgen2 import token
|
||||||
|
from blib2to3.pytree import Leaf, Node, type_repr
|
||||||
|
|
||||||
|
LN = Union[Leaf, Node]
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DebugVisitor(Visitor[T]):
|
||||||
|
tree_depth: int = 0
|
||||||
|
list_output: list[str] = field(default_factory=list)
|
||||||
|
print_output: bool = True
|
||||||
|
|
||||||
|
def out(self, message: str, *args: Any, **kwargs: Any) -> None:
|
||||||
|
self.list_output.append(message)
|
||||||
|
if self.print_output:
|
||||||
|
out(message, *args, **kwargs)
|
||||||
|
|
||||||
|
def visit_default(self, node: LN) -> Iterator[T]:
|
||||||
|
indent = " " * (2 * self.tree_depth)
|
||||||
|
if isinstance(node, Node):
|
||||||
|
_type = type_repr(node.type)
|
||||||
|
self.out(f"{indent}{_type}", fg="yellow")
|
||||||
|
self.tree_depth += 1
|
||||||
|
for child in node.children:
|
||||||
|
yield from self.visit(child)
|
||||||
|
|
||||||
|
self.tree_depth -= 1
|
||||||
|
self.out(f"{indent}/{_type}", fg="yellow", bold=False)
|
||||||
|
else:
|
||||||
|
_type = token.tok_name.get(node.type, str(node.type))
|
||||||
|
self.out(f"{indent}{_type}", fg="blue", nl=False)
|
||||||
|
if node.prefix:
|
||||||
|
# We don't have to handle prefixes for `Node` objects since
|
||||||
|
# that delegates to the first child anyway.
|
||||||
|
self.out(f" {node.prefix!r}", fg="green", bold=False, nl=False)
|
||||||
|
self.out(f" {node.value!r}", fg="blue", bold=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def show(cls, code: str | Leaf | Node) -> None:
|
||||||
|
"""Pretty-print the lib2to3 AST of a given string of `code`.
|
||||||
|
|
||||||
|
Convenience method for debugging.
|
||||||
|
"""
|
||||||
|
v: DebugVisitor[None] = DebugVisitor()
|
||||||
|
if isinstance(code, str):
|
||||||
|
code = lib2to3_parse(code)
|
||||||
|
list(v.visit(code))
|
||||||
426
.venv_codegen/Lib/site-packages/black/files.py
Normal file
426
.venv_codegen/Lib/site-packages/black/files.py
Normal file
|
|
@ -0,0 +1,426 @@
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections.abc import Iterable, Iterator, Sequence
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
from re import Pattern
|
||||||
|
from typing import TYPE_CHECKING, Any, Union
|
||||||
|
|
||||||
|
from mypy_extensions import mypyc_attr
|
||||||
|
from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
|
||||||
|
from packaging.version import InvalidVersion, Version
|
||||||
|
from pathspec import GitIgnoreSpec
|
||||||
|
from pathspec.patterns.gitignore import GitIgnorePatternError
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ImportError:
|
||||||
|
# Help users on older alphas
|
||||||
|
if not TYPE_CHECKING:
|
||||||
|
import tomli as tomllib
|
||||||
|
else:
|
||||||
|
import tomli as tomllib
|
||||||
|
|
||||||
|
from black.handle_ipynb_magics import jupyter_dependencies_are_installed
|
||||||
|
from black.mode import TargetVersion
|
||||||
|
from black.output import err
|
||||||
|
from black.report import Report
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import colorama
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def _load_toml(path: Path | str) -> dict[str, Any]:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return tomllib.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def _cached_resolve(path: Path) -> Path:
|
||||||
|
return path.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def find_project_root(
|
||||||
|
srcs: Sequence[str], stdin_filename: str | None = None
|
||||||
|
) -> tuple[Path, str]:
|
||||||
|
"""Return a directory containing .git, .hg, or pyproject.toml.
|
||||||
|
|
||||||
|
pyproject.toml files are only considered if they contain a [tool.black]
|
||||||
|
section and are ignored otherwise.
|
||||||
|
|
||||||
|
That directory will be a common parent of all files and directories
|
||||||
|
passed in `srcs`.
|
||||||
|
|
||||||
|
If no directory in the tree contains a marker that would specify it's the
|
||||||
|
project root, the root of the file system is returned.
|
||||||
|
|
||||||
|
Returns a two-tuple with the first element as the project root path and
|
||||||
|
the second element as a string describing the method by which the
|
||||||
|
project root was discovered.
|
||||||
|
"""
|
||||||
|
if stdin_filename is not None:
|
||||||
|
srcs = tuple(stdin_filename if s == "-" else s for s in srcs)
|
||||||
|
if not srcs:
|
||||||
|
srcs = [str(_cached_resolve(Path.cwd()))]
|
||||||
|
|
||||||
|
path_srcs = [_cached_resolve(Path(Path.cwd(), src)) for src in srcs]
|
||||||
|
|
||||||
|
# A list of lists of parents for each 'src'. 'src' is included as a
|
||||||
|
# "parent" of itself if it is a directory
|
||||||
|
src_parents = [
|
||||||
|
list(path.parents) + ([path] if path.is_dir() else []) for path in path_srcs
|
||||||
|
]
|
||||||
|
|
||||||
|
common_base = max(
|
||||||
|
set.intersection(*(set(parents) for parents in src_parents)),
|
||||||
|
key=lambda path: path.parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
for directory in (common_base, *common_base.parents):
|
||||||
|
if (directory / ".git").exists():
|
||||||
|
return directory, ".git directory"
|
||||||
|
|
||||||
|
if (directory / ".hg").is_dir():
|
||||||
|
return directory, ".hg directory"
|
||||||
|
|
||||||
|
if (directory / "pyproject.toml").is_file():
|
||||||
|
pyproject_toml = _load_toml(directory / "pyproject.toml")
|
||||||
|
if "black" in pyproject_toml.get("tool", {}):
|
||||||
|
return directory, "pyproject.toml"
|
||||||
|
|
||||||
|
return directory, "file system root"
|
||||||
|
|
||||||
|
|
||||||
|
def find_pyproject_toml(
|
||||||
|
path_search_start: tuple[str, ...], stdin_filename: str | None = None
|
||||||
|
) -> str | None:
|
||||||
|
"""Find the absolute filepath to a pyproject.toml if it exists"""
|
||||||
|
path_project_root, _ = find_project_root(path_search_start, stdin_filename)
|
||||||
|
path_pyproject_toml = path_project_root / "pyproject.toml"
|
||||||
|
if path_pyproject_toml.is_file():
|
||||||
|
return str(path_pyproject_toml)
|
||||||
|
|
||||||
|
try:
|
||||||
|
path_user_pyproject_toml = find_user_pyproject_toml()
|
||||||
|
return (
|
||||||
|
str(path_user_pyproject_toml)
|
||||||
|
if path_user_pyproject_toml.is_file()
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except (PermissionError, RuntimeError) as e:
|
||||||
|
# We do not have access to the user-level config directory, so ignore it.
|
||||||
|
err(f"Ignoring user configuration directory due to {e!r}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@mypyc_attr(patchable=True)
|
||||||
|
def parse_pyproject_toml(path_config: str) -> dict[str, Any]:
|
||||||
|
"""Parse a pyproject toml file, pulling out relevant parts for Black.
|
||||||
|
|
||||||
|
If parsing fails, will raise a tomllib.TOMLDecodeError.
|
||||||
|
"""
|
||||||
|
pyproject_toml = _load_toml(path_config)
|
||||||
|
config: dict[str, Any] = pyproject_toml.get("tool", {}).get("black", {})
|
||||||
|
config = {k.replace("--", "").replace("-", "_"): v for k, v in config.items()}
|
||||||
|
|
||||||
|
if "target_version" not in config:
|
||||||
|
inferred_target_version = infer_target_version(pyproject_toml)
|
||||||
|
if inferred_target_version is not None:
|
||||||
|
config["target_version"] = [v.name.lower() for v in inferred_target_version]
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def infer_target_version(
|
||||||
|
pyproject_toml: dict[str, Any],
|
||||||
|
) -> list[TargetVersion] | None:
|
||||||
|
"""Infer Black's target version from the project metadata in pyproject.toml.
|
||||||
|
|
||||||
|
Supports the PyPA standard format (PEP 621):
|
||||||
|
https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#requires-python
|
||||||
|
|
||||||
|
If the target version cannot be inferred, returns None.
|
||||||
|
"""
|
||||||
|
project_metadata = pyproject_toml.get("project", {})
|
||||||
|
requires_python = project_metadata.get("requires-python", None)
|
||||||
|
if requires_python is not None:
|
||||||
|
try:
|
||||||
|
return parse_req_python_version(requires_python)
|
||||||
|
except InvalidVersion:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return parse_req_python_specifier(requires_python)
|
||||||
|
except (InvalidSpecifier, InvalidVersion):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_req_python_version(requires_python: str) -> list[TargetVersion] | None:
|
||||||
|
"""Parse a version string (i.e. ``"3.7"``) to a list of TargetVersion.
|
||||||
|
|
||||||
|
If parsing fails, will raise a packaging.version.InvalidVersion error.
|
||||||
|
If the parsed version cannot be mapped to a valid TargetVersion, returns None.
|
||||||
|
"""
|
||||||
|
version = Version(requires_python)
|
||||||
|
if version.release[0] != 3:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return [TargetVersion(version.release[1])]
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_req_python_specifier(requires_python: str) -> list[TargetVersion] | None:
|
||||||
|
"""Parse a specifier string (i.e. ``">=3.7,<3.10"``) to a list of TargetVersion.
|
||||||
|
|
||||||
|
If parsing fails, will raise a packaging.specifiers.InvalidSpecifier error.
|
||||||
|
If the parsed specifier cannot be mapped to a valid TargetVersion, returns None.
|
||||||
|
"""
|
||||||
|
specifier_set = strip_specifier_set(SpecifierSet(requires_python))
|
||||||
|
if not specifier_set:
|
||||||
|
return None
|
||||||
|
|
||||||
|
target_version_map = {f"3.{v.value}": v for v in TargetVersion}
|
||||||
|
compatible_versions: list[str] = list(specifier_set.filter(target_version_map))
|
||||||
|
if compatible_versions:
|
||||||
|
return [target_version_map[v] for v in compatible_versions]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def strip_specifier_set(specifier_set: SpecifierSet) -> SpecifierSet:
|
||||||
|
"""Strip minor versions for some specifiers in the specifier set.
|
||||||
|
|
||||||
|
For background on version specifiers, see PEP 440:
|
||||||
|
https://peps.python.org/pep-0440/#version-specifiers
|
||||||
|
"""
|
||||||
|
specifiers = []
|
||||||
|
for s in specifier_set:
|
||||||
|
if "*" in str(s):
|
||||||
|
specifiers.append(s)
|
||||||
|
elif s.operator in ["~=", "==", ">=", "==="]:
|
||||||
|
version = Version(s.version)
|
||||||
|
stripped = Specifier(f"{s.operator}{version.major}.{version.minor}")
|
||||||
|
specifiers.append(stripped)
|
||||||
|
elif s.operator == ">":
|
||||||
|
version = Version(s.version)
|
||||||
|
if len(version.release) > 2:
|
||||||
|
s = Specifier(f">={version.major}.{version.minor}")
|
||||||
|
specifiers.append(s)
|
||||||
|
else:
|
||||||
|
specifiers.append(s)
|
||||||
|
|
||||||
|
return SpecifierSet(",".join(str(s) for s in specifiers))
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def find_user_pyproject_toml() -> Path:
|
||||||
|
r"""Return the path to the top-level user configuration for black.
|
||||||
|
|
||||||
|
This looks for ~\.black on Windows and ~/.config/black on Linux and other
|
||||||
|
Unix systems.
|
||||||
|
|
||||||
|
May raise:
|
||||||
|
- RuntimeError: if the current user has no homedir
|
||||||
|
- PermissionError: if the current process cannot access the user's homedir
|
||||||
|
"""
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# Windows
|
||||||
|
user_config_path = Path.home() / ".black"
|
||||||
|
else:
|
||||||
|
config_root = os.environ.get("XDG_CONFIG_HOME", "~/.config")
|
||||||
|
user_config_path = Path(config_root).expanduser() / "black"
|
||||||
|
return _cached_resolve(user_config_path)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_gitignore(root: Path) -> GitIgnoreSpec:
|
||||||
|
"""Return a GitIgnoreSpec matching gitignore content if present."""
|
||||||
|
gitignore = root / ".gitignore"
|
||||||
|
lines: list[str] = []
|
||||||
|
if gitignore.is_file():
|
||||||
|
with gitignore.open(encoding="utf-8") as gf:
|
||||||
|
lines = gf.readlines()
|
||||||
|
try:
|
||||||
|
return GitIgnoreSpec.from_lines(lines)
|
||||||
|
except GitIgnorePatternError as e:
|
||||||
|
err(f"Could not parse {gitignore}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def resolves_outside_root_or_cannot_stat(
|
||||||
|
path: Path,
|
||||||
|
root: Path,
|
||||||
|
report: Report | None = None,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Returns whether the path is a symbolic link that points outside the
|
||||||
|
root directory. Also returns True if we failed to resolve the path.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resolved_path = _cached_resolve(path)
|
||||||
|
except OSError as e:
|
||||||
|
if report:
|
||||||
|
report.path_ignored(path, f"cannot be read because {e}")
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
resolved_path.relative_to(root)
|
||||||
|
except ValueError:
|
||||||
|
if report:
|
||||||
|
report.path_ignored(path, f"is a symbolic link that points outside {root}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def best_effort_relative_path(path: Path, root: Path) -> Path:
|
||||||
|
# Precondition: resolves_outside_root_or_cannot_stat(path, root) is False
|
||||||
|
try:
|
||||||
|
return path.absolute().relative_to(root)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
root_parent = next((p for p in path.parents if _cached_resolve(p) == root), None)
|
||||||
|
if root_parent is not None:
|
||||||
|
return path.relative_to(root_parent)
|
||||||
|
# something adversarial, fallback to path guaranteed by precondition
|
||||||
|
return _cached_resolve(path).relative_to(root)
|
||||||
|
|
||||||
|
|
||||||
|
def _path_is_ignored(
|
||||||
|
root_relative_path: str,
|
||||||
|
root: Path,
|
||||||
|
gitignore_dict: dict[Path, GitIgnoreSpec],
|
||||||
|
) -> bool:
|
||||||
|
path = root / root_relative_path
|
||||||
|
# Note that this logic is sensitive to the ordering of gitignore_dict. Callers must
|
||||||
|
# ensure that gitignore_dict is ordered from least specific to most specific.
|
||||||
|
for gitignore_path, pattern in gitignore_dict.items():
|
||||||
|
try:
|
||||||
|
relative_path = path.relative_to(gitignore_path).as_posix()
|
||||||
|
if path.is_dir():
|
||||||
|
relative_path = relative_path + "/"
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
if pattern.match_file(relative_path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def path_is_excluded(
|
||||||
|
normalized_path: str,
|
||||||
|
pattern: Pattern[str] | None,
|
||||||
|
) -> bool:
|
||||||
|
match = pattern.search(normalized_path) if pattern else None
|
||||||
|
return bool(match and match.group(0))
|
||||||
|
|
||||||
|
|
||||||
|
def gen_python_files(
|
||||||
|
paths: Iterable[Path],
|
||||||
|
root: Path,
|
||||||
|
include: Pattern[str],
|
||||||
|
exclude: Pattern[str],
|
||||||
|
extend_exclude: Pattern[str] | None,
|
||||||
|
force_exclude: Pattern[str] | None,
|
||||||
|
report: Report,
|
||||||
|
gitignore_dict: dict[Path, GitIgnoreSpec] | None,
|
||||||
|
*,
|
||||||
|
verbose: bool,
|
||||||
|
quiet: bool,
|
||||||
|
) -> Iterator[Path]:
|
||||||
|
"""Generate all files under `path` whose paths are not excluded by the
|
||||||
|
`exclude_regex`, `extend_exclude`, or `force_exclude` regexes,
|
||||||
|
but are included by the `include` regex.
|
||||||
|
|
||||||
|
Symbolic links pointing outside of the `root` directory are ignored.
|
||||||
|
|
||||||
|
`report` is where output about exclusions goes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}"
|
||||||
|
for child in paths:
|
||||||
|
assert child.is_absolute()
|
||||||
|
root_relative_path = child.relative_to(root).as_posix()
|
||||||
|
|
||||||
|
# First ignore files matching .gitignore, if passed
|
||||||
|
if gitignore_dict and _path_is_ignored(
|
||||||
|
root_relative_path, root, gitignore_dict
|
||||||
|
):
|
||||||
|
report.path_ignored(child, "matches a .gitignore file content")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Then ignore with `--exclude` `--extend-exclude` and `--force-exclude` options.
|
||||||
|
root_relative_path = "/" + root_relative_path
|
||||||
|
if child.is_dir():
|
||||||
|
root_relative_path += "/"
|
||||||
|
|
||||||
|
if path_is_excluded(root_relative_path, exclude):
|
||||||
|
report.path_ignored(child, "matches the --exclude regular expression")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if path_is_excluded(root_relative_path, extend_exclude):
|
||||||
|
report.path_ignored(
|
||||||
|
child, "matches the --extend-exclude regular expression"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if path_is_excluded(root_relative_path, force_exclude):
|
||||||
|
report.path_ignored(child, "matches the --force-exclude regular expression")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if resolves_outside_root_or_cannot_stat(child, root, report):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if child.is_dir():
|
||||||
|
# If gitignore is None, gitignore usage is disabled, while a Falsey
|
||||||
|
# gitignore is when the directory doesn't have a .gitignore file.
|
||||||
|
if gitignore_dict is not None:
|
||||||
|
new_gitignore_dict = {
|
||||||
|
**gitignore_dict,
|
||||||
|
root / child: get_gitignore(child),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
new_gitignore_dict = None
|
||||||
|
yield from gen_python_files(
|
||||||
|
child.iterdir(),
|
||||||
|
root,
|
||||||
|
include,
|
||||||
|
exclude,
|
||||||
|
extend_exclude,
|
||||||
|
force_exclude,
|
||||||
|
report,
|
||||||
|
new_gitignore_dict,
|
||||||
|
verbose=verbose,
|
||||||
|
quiet=quiet,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif child.is_file():
|
||||||
|
if child.suffix == ".ipynb" and not jupyter_dependencies_are_installed(
|
||||||
|
warn=verbose or not quiet
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
include_match = include.search(root_relative_path) if include else True
|
||||||
|
if include_match:
|
||||||
|
yield child
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_stream_for_windows(
|
||||||
|
f: io.TextIOWrapper,
|
||||||
|
) -> Union[io.TextIOWrapper, "colorama.AnsiToWin32"]:
|
||||||
|
"""
|
||||||
|
Wrap stream with colorama's wrap_stream so colors are shown on Windows.
|
||||||
|
|
||||||
|
If `colorama` is unavailable, the original stream is returned unmodified.
|
||||||
|
Otherwise, the `wrap_stream()` function determines whether the stream needs
|
||||||
|
to be wrapped for a Windows environment and will accordingly either return
|
||||||
|
an `AnsiToWin32` wrapper or the original stream.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from colorama.initialise import wrap_stream
|
||||||
|
except ImportError:
|
||||||
|
return f
|
||||||
|
else:
|
||||||
|
# Set `strip=False` to avoid needing to modify test_express_diff_with_color.
|
||||||
|
return wrap_stream(f, convert=None, strip=False, autoreset=False, wrap=True)
|
||||||
515
.venv_codegen/Lib/site-packages/black/handle_ipynb_magics.py
Normal file
515
.venv_codegen/Lib/site-packages/black/handle_ipynb_magics.py
Normal file
|
|
@ -0,0 +1,515 @@
|
||||||
|
"""Functions to process IPython magics with."""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import collections
|
||||||
|
import dataclasses
|
||||||
|
import re
|
||||||
|
import secrets
|
||||||
|
import string
|
||||||
|
from collections.abc import Collection
|
||||||
|
from functools import lru_cache
|
||||||
|
from importlib.util import find_spec
|
||||||
|
from typing import TypeGuard
|
||||||
|
|
||||||
|
from black.mode import Mode
|
||||||
|
from black.output import out
|
||||||
|
from black.report import NothingChanged
|
||||||
|
|
||||||
|
TRANSFORMED_MAGICS = frozenset((
|
||||||
|
"get_ipython().run_cell_magic",
|
||||||
|
"get_ipython().system",
|
||||||
|
"get_ipython().getoutput",
|
||||||
|
"get_ipython().run_line_magic",
|
||||||
|
))
|
||||||
|
TOKENS_TO_IGNORE = frozenset((
|
||||||
|
"ENDMARKER",
|
||||||
|
"NL",
|
||||||
|
"NEWLINE",
|
||||||
|
"COMMENT",
|
||||||
|
"DEDENT",
|
||||||
|
"UNIMPORTANT_WS",
|
||||||
|
"ESCAPED_NL",
|
||||||
|
))
|
||||||
|
PYTHON_CELL_MAGICS = frozenset((
|
||||||
|
"capture",
|
||||||
|
"prun",
|
||||||
|
"pypy",
|
||||||
|
"python",
|
||||||
|
"python3",
|
||||||
|
"time",
|
||||||
|
"timeit",
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class Replacement:
|
||||||
|
mask: str
|
||||||
|
src: str
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def jupyter_dependencies_are_installed(*, warn: bool) -> bool:
|
||||||
|
installed = (
|
||||||
|
find_spec("tokenize_rt") is not None and find_spec("IPython") is not None
|
||||||
|
)
|
||||||
|
if not installed and warn:
|
||||||
|
msg = (
|
||||||
|
"Skipping .ipynb files as Jupyter dependencies are not installed.\n"
|
||||||
|
'You can fix this by running ``pip install "black[jupyter]"``'
|
||||||
|
)
|
||||||
|
out(msg)
|
||||||
|
return installed
|
||||||
|
|
||||||
|
|
||||||
|
def validate_cell(src: str, mode: Mode) -> None:
|
||||||
|
r"""Check that cell does not already contain TransformerManager transformations,
|
||||||
|
or non-Python cell magics, which might cause tokenizer_rt to break because of
|
||||||
|
indentations.
|
||||||
|
|
||||||
|
If a cell contains ``!ls``, then it'll be transformed to
|
||||||
|
``get_ipython().system('ls')``. However, if the cell originally contained
|
||||||
|
``get_ipython().system('ls')``, then it would get transformed in the same way:
|
||||||
|
|
||||||
|
>>> TransformerManager().transform_cell("get_ipython().system('ls')")
|
||||||
|
"get_ipython().system('ls')\n"
|
||||||
|
>>> TransformerManager().transform_cell("!ls")
|
||||||
|
"get_ipython().system('ls')\n"
|
||||||
|
|
||||||
|
Due to the impossibility of safely roundtripping in such situations, cells
|
||||||
|
containing transformed magics will be ignored.
|
||||||
|
"""
|
||||||
|
if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS):
|
||||||
|
raise NothingChanged
|
||||||
|
|
||||||
|
line = _get_code_start(src)
|
||||||
|
if line.startswith("%%") and (
|
||||||
|
line.split(maxsplit=1)[0][2:]
|
||||||
|
not in PYTHON_CELL_MAGICS | mode.python_cell_magics
|
||||||
|
):
|
||||||
|
raise NothingChanged
|
||||||
|
|
||||||
|
|
||||||
|
def remove_trailing_semicolon(src: str) -> tuple[str, bool]:
|
||||||
|
"""Remove trailing semicolon from Jupyter notebook cell.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.plot(x_data, y_data); # plot data
|
||||||
|
|
||||||
|
would become
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.plot(x_data, y_data) # plot data
|
||||||
|
|
||||||
|
Mirrors the logic in `quiet` from `IPython.core.displayhook`, but uses
|
||||||
|
``tokenize_rt`` so that round-tripping works fine.
|
||||||
|
"""
|
||||||
|
from tokenize_rt import reversed_enumerate, src_to_tokens, tokens_to_src
|
||||||
|
|
||||||
|
tokens = src_to_tokens(src)
|
||||||
|
trailing_semicolon = False
|
||||||
|
for idx, token in reversed_enumerate(tokens):
|
||||||
|
if token.name in TOKENS_TO_IGNORE:
|
||||||
|
continue
|
||||||
|
if token.name == "OP" and token.src == ";":
|
||||||
|
del tokens[idx]
|
||||||
|
trailing_semicolon = True
|
||||||
|
break
|
||||||
|
if not trailing_semicolon:
|
||||||
|
return src, False
|
||||||
|
return tokens_to_src(tokens), True
|
||||||
|
|
||||||
|
|
||||||
|
def put_trailing_semicolon_back(src: str, has_trailing_semicolon: bool) -> str:
|
||||||
|
"""Put trailing semicolon back if cell originally had it.
|
||||||
|
|
||||||
|
Mirrors the logic in `quiet` from `IPython.core.displayhook`, but uses
|
||||||
|
``tokenize_rt`` so that round-tripping works fine.
|
||||||
|
"""
|
||||||
|
if not has_trailing_semicolon:
|
||||||
|
return src
|
||||||
|
from tokenize_rt import reversed_enumerate, src_to_tokens, tokens_to_src
|
||||||
|
|
||||||
|
tokens = src_to_tokens(src)
|
||||||
|
for idx, token in reversed_enumerate(tokens):
|
||||||
|
if token.name in TOKENS_TO_IGNORE:
|
||||||
|
continue
|
||||||
|
tokens[idx] = token._replace(src=token.src + ";")
|
||||||
|
break
|
||||||
|
else: # pragma: nocover
|
||||||
|
raise AssertionError(
|
||||||
|
"INTERNAL ERROR: Was not able to reinstate trailing semicolon. "
|
||||||
|
"Please report a bug on https://github.com/psf/black/issues. "
|
||||||
|
) from None
|
||||||
|
return str(tokens_to_src(tokens))
|
||||||
|
|
||||||
|
|
||||||
|
def mask_cell(src: str) -> tuple[str, list[Replacement]]:
|
||||||
|
"""Mask IPython magics so content becomes parseable Python code.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
%matplotlib inline
|
||||||
|
'foo'
|
||||||
|
|
||||||
|
becomes
|
||||||
|
|
||||||
|
b"25716f358c32750"
|
||||||
|
'foo'
|
||||||
|
|
||||||
|
The replacements are returned, along with the transformed code.
|
||||||
|
"""
|
||||||
|
replacements: list[Replacement] = []
|
||||||
|
try:
|
||||||
|
ast.parse(src)
|
||||||
|
except SyntaxError:
|
||||||
|
# Might have IPython magics, will process below.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Syntax is fine, nothing to mask, early return.
|
||||||
|
return src, replacements
|
||||||
|
|
||||||
|
from IPython.core.inputtransformer2 import TransformerManager
|
||||||
|
|
||||||
|
transformer_manager = TransformerManager()
|
||||||
|
# A side effect of the following transformation is that it also removes any
|
||||||
|
# empty lines at the beginning of the cell.
|
||||||
|
transformed = transformer_manager.transform_cell(src)
|
||||||
|
transformed, cell_magic_replacements = replace_cell_magics(transformed)
|
||||||
|
replacements += cell_magic_replacements
|
||||||
|
transformed = transformer_manager.transform_cell(transformed)
|
||||||
|
transformed, magic_replacements = replace_magics(transformed)
|
||||||
|
if len(transformed.strip().splitlines()) != len(src.strip().splitlines()):
|
||||||
|
# Multi-line magic, not supported.
|
||||||
|
raise NothingChanged
|
||||||
|
replacements += magic_replacements
|
||||||
|
return transformed, replacements
|
||||||
|
|
||||||
|
|
||||||
|
def create_token(n_chars: int) -> str:
|
||||||
|
"""Create a randomly generated token that is n_chars characters long."""
|
||||||
|
assert n_chars > 0
|
||||||
|
if n_chars == 1:
|
||||||
|
return secrets.choice(string.ascii_letters)
|
||||||
|
if n_chars < 4:
|
||||||
|
return "_" + "".join(
|
||||||
|
secrets.choice(string.ascii_letters + string.digits + "_")
|
||||||
|
for _ in range(n_chars - 1)
|
||||||
|
)
|
||||||
|
n_bytes = max(n_chars // 2 - 1, 1)
|
||||||
|
token = secrets.token_hex(n_bytes)
|
||||||
|
if len(token) + 3 > n_chars:
|
||||||
|
token = token[:-1]
|
||||||
|
# We use a bytestring so that the string does not get interpreted
|
||||||
|
# as a docstring.
|
||||||
|
return f'b"{token}"'
|
||||||
|
|
||||||
|
|
||||||
|
def get_token(src: str, magic: str, existing_tokens: Collection[str] = ()) -> str:
|
||||||
|
"""Return randomly generated token to mask IPython magic with.
|
||||||
|
|
||||||
|
For example, if 'magic' was `%matplotlib inline`, then a possible
|
||||||
|
token to mask it with would be `"43fdd17f7e5ddc83"`. The token
|
||||||
|
will be the same length as the magic, and we make sure that it was
|
||||||
|
not already present anywhere else in the cell.
|
||||||
|
"""
|
||||||
|
assert magic
|
||||||
|
n_chars = len(magic)
|
||||||
|
token = create_token(n_chars)
|
||||||
|
counter = 0
|
||||||
|
while token in src or token in existing_tokens:
|
||||||
|
token = create_token(n_chars)
|
||||||
|
counter += 1
|
||||||
|
if counter > 100:
|
||||||
|
raise AssertionError(
|
||||||
|
"INTERNAL ERROR: Black was not able to replace IPython magic. "
|
||||||
|
"Please report a bug on https://github.com/psf/black/issues. "
|
||||||
|
f"The magic might be helpful: {magic}"
|
||||||
|
) from None
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def replace_cell_magics(src: str) -> tuple[str, list[Replacement]]:
|
||||||
|
r"""Replace cell magic with token.
|
||||||
|
|
||||||
|
Note that 'src' will already have been processed by IPython's
|
||||||
|
TransformerManager().transform_cell.
|
||||||
|
|
||||||
|
Example,
|
||||||
|
|
||||||
|
get_ipython().run_cell_magic('t', '-n1', 'ls =!ls\n')
|
||||||
|
|
||||||
|
becomes
|
||||||
|
|
||||||
|
"a794."
|
||||||
|
ls =!ls
|
||||||
|
|
||||||
|
The replacement, along with the transformed code, is returned.
|
||||||
|
"""
|
||||||
|
replacements: list[Replacement] = []
|
||||||
|
|
||||||
|
tree = ast.parse(src)
|
||||||
|
|
||||||
|
cell_magic_finder = CellMagicFinder()
|
||||||
|
cell_magic_finder.visit(tree)
|
||||||
|
if cell_magic_finder.cell_magic is None:
|
||||||
|
return src, replacements
|
||||||
|
header = cell_magic_finder.cell_magic.header
|
||||||
|
mask = get_token(src, header)
|
||||||
|
replacements.append(Replacement(mask=mask, src=header))
|
||||||
|
return f"{mask}\n{cell_magic_finder.cell_magic.body}", replacements
|
||||||
|
|
||||||
|
|
||||||
|
def replace_magics(src: str) -> tuple[str, list[Replacement]]:
|
||||||
|
"""Replace magics within body of cell.
|
||||||
|
|
||||||
|
Note that 'src' will already have been processed by IPython's
|
||||||
|
TransformerManager().transform_cell.
|
||||||
|
|
||||||
|
Example, this
|
||||||
|
|
||||||
|
get_ipython().run_line_magic('matplotlib', 'inline')
|
||||||
|
'foo'
|
||||||
|
|
||||||
|
becomes
|
||||||
|
|
||||||
|
"5e67db56d490fd39"
|
||||||
|
'foo'
|
||||||
|
|
||||||
|
The replacement, along with the transformed code, are returned.
|
||||||
|
"""
|
||||||
|
replacements = []
|
||||||
|
existing_tokens: set[str] = set()
|
||||||
|
magic_finder = MagicFinder()
|
||||||
|
magic_finder.visit(ast.parse(src))
|
||||||
|
new_srcs = []
|
||||||
|
for i, line in enumerate(src.split("\n"), start=1):
|
||||||
|
if i in magic_finder.magics:
|
||||||
|
offsets_and_magics = magic_finder.magics[i]
|
||||||
|
if len(offsets_and_magics) != 1: # pragma: nocover
|
||||||
|
raise AssertionError(
|
||||||
|
f"Expecting one magic per line, got: {offsets_and_magics}\n"
|
||||||
|
"Please report a bug on https://github.com/psf/black/issues."
|
||||||
|
)
|
||||||
|
col_offset, magic = (
|
||||||
|
offsets_and_magics[0].col_offset,
|
||||||
|
offsets_and_magics[0].magic,
|
||||||
|
)
|
||||||
|
mask = get_token(src, magic, existing_tokens)
|
||||||
|
replacements.append(Replacement(mask=mask, src=magic))
|
||||||
|
existing_tokens.add(mask)
|
||||||
|
line = line[:col_offset] + mask
|
||||||
|
new_srcs.append(line)
|
||||||
|
return "\n".join(new_srcs), replacements
|
||||||
|
|
||||||
|
|
||||||
|
def unmask_cell(src: str, replacements: list[Replacement]) -> str:
|
||||||
|
"""Remove replacements from cell.
|
||||||
|
|
||||||
|
For example
|
||||||
|
|
||||||
|
"9b20"
|
||||||
|
foo = bar
|
||||||
|
|
||||||
|
becomes
|
||||||
|
|
||||||
|
%%time
|
||||||
|
foo = bar
|
||||||
|
"""
|
||||||
|
for replacement in replacements:
|
||||||
|
if src.count(replacement.mask) != 1:
|
||||||
|
raise NothingChanged
|
||||||
|
src = src.replace(replacement.mask, replacement.src, 1)
|
||||||
|
return src
|
||||||
|
|
||||||
|
|
||||||
|
def _get_code_start(src: str) -> str:
|
||||||
|
"""Provides the first line where the code starts.
|
||||||
|
|
||||||
|
Iterates over lines of code until it finds the first line that doesn't
|
||||||
|
contain only empty spaces and comments. It removes any empty spaces at the
|
||||||
|
start of the line and returns it. If such line doesn't exist, it returns an
|
||||||
|
empty string.
|
||||||
|
"""
|
||||||
|
for match in re.finditer(".+", src):
|
||||||
|
line = match.group(0).lstrip()
|
||||||
|
if line and not line.startswith("#"):
|
||||||
|
return line
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _is_ipython_magic(node: ast.expr) -> TypeGuard[ast.Attribute]:
|
||||||
|
"""Check if attribute is IPython magic.
|
||||||
|
|
||||||
|
Note that the source of the abstract syntax tree
|
||||||
|
will already have been processed by IPython's
|
||||||
|
TransformerManager().transform_cell.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
isinstance(node, ast.Attribute)
|
||||||
|
and isinstance(node.value, ast.Call)
|
||||||
|
and isinstance(node.value.func, ast.Name)
|
||||||
|
and node.value.func.id == "get_ipython"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_str_args(args: list[ast.expr]) -> list[str]:
|
||||||
|
str_args = []
|
||||||
|
for arg in args:
|
||||||
|
assert isinstance(arg, ast.Constant) and isinstance(arg.value, str)
|
||||||
|
str_args.append(arg.value)
|
||||||
|
return str_args
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class CellMagic:
|
||||||
|
name: str
|
||||||
|
params: str | None
|
||||||
|
body: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def header(self) -> str:
|
||||||
|
if self.params:
|
||||||
|
return f"%%{self.name} {self.params}"
|
||||||
|
return f"%%{self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
# ast.NodeVisitor + dataclass = breakage under mypyc.
|
||||||
|
class CellMagicFinder(ast.NodeVisitor):
|
||||||
|
r"""Find cell magics.
|
||||||
|
|
||||||
|
Note that the source of the abstract syntax tree
|
||||||
|
will already have been processed by IPython's
|
||||||
|
TransformerManager().transform_cell.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
%%time\n
|
||||||
|
foo()
|
||||||
|
|
||||||
|
would have been transformed to
|
||||||
|
|
||||||
|
get_ipython().run_cell_magic('time', '', 'foo()\n')
|
||||||
|
|
||||||
|
and we look for instances of the latter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cell_magic: CellMagic | None = None) -> None:
|
||||||
|
self.cell_magic = cell_magic
|
||||||
|
|
||||||
|
def visit_Expr(self, node: ast.Expr) -> None:
|
||||||
|
"""Find cell magic, extract header and body."""
|
||||||
|
if (
|
||||||
|
isinstance(node.value, ast.Call)
|
||||||
|
and _is_ipython_magic(node.value.func)
|
||||||
|
and node.value.func.attr == "run_cell_magic"
|
||||||
|
):
|
||||||
|
args = _get_str_args(node.value.args)
|
||||||
|
self.cell_magic = CellMagic(name=args[0], params=args[1], body=args[2])
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class OffsetAndMagic:
|
||||||
|
col_offset: int
|
||||||
|
magic: str
|
||||||
|
|
||||||
|
|
||||||
|
# Unsurprisingly, subclassing ast.NodeVisitor means we can't use dataclasses here
|
||||||
|
# as mypyc will generate broken code.
|
||||||
|
class MagicFinder(ast.NodeVisitor):
|
||||||
|
"""Visit cell to look for get_ipython calls.
|
||||||
|
|
||||||
|
Note that the source of the abstract syntax tree
|
||||||
|
will already have been processed by IPython's
|
||||||
|
TransformerManager().transform_cell.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
%matplotlib inline
|
||||||
|
|
||||||
|
would have been transformed to
|
||||||
|
|
||||||
|
get_ipython().run_line_magic('matplotlib', 'inline')
|
||||||
|
|
||||||
|
and we look for instances of the latter (and likewise for other
|
||||||
|
types of magics).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.magics: dict[int, list[OffsetAndMagic]] = collections.defaultdict(list)
|
||||||
|
|
||||||
|
def visit_Assign(self, node: ast.Assign) -> None:
|
||||||
|
"""Look for system assign magics.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
black_version = !black --version
|
||||||
|
env = %env var
|
||||||
|
|
||||||
|
would have been (respectively) transformed to
|
||||||
|
|
||||||
|
black_version = get_ipython().getoutput('black --version')
|
||||||
|
env = get_ipython().run_line_magic('env', 'var')
|
||||||
|
|
||||||
|
and we look for instances of any of the latter.
|
||||||
|
"""
|
||||||
|
if isinstance(node.value, ast.Call) and _is_ipython_magic(node.value.func):
|
||||||
|
args = _get_str_args(node.value.args)
|
||||||
|
if node.value.func.attr == "getoutput":
|
||||||
|
src = f"!{args[0]}"
|
||||||
|
elif node.value.func.attr == "run_line_magic":
|
||||||
|
src = f"%{args[0]}"
|
||||||
|
if args[1]:
|
||||||
|
src += f" {args[1]}"
|
||||||
|
else:
|
||||||
|
raise AssertionError(
|
||||||
|
f"Unexpected IPython magic {node.value.func.attr!r} found. "
|
||||||
|
"Please report a bug on https://github.com/psf/black/issues."
|
||||||
|
) from None
|
||||||
|
self.magics[node.value.lineno].append(
|
||||||
|
OffsetAndMagic(node.value.col_offset, src)
|
||||||
|
)
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
def visit_Expr(self, node: ast.Expr) -> None:
|
||||||
|
"""Look for magics in body of cell.
|
||||||
|
|
||||||
|
For examples,
|
||||||
|
|
||||||
|
!ls
|
||||||
|
!!ls
|
||||||
|
?ls
|
||||||
|
??ls
|
||||||
|
|
||||||
|
would (respectively) get transformed to
|
||||||
|
|
||||||
|
get_ipython().system('ls')
|
||||||
|
get_ipython().getoutput('ls')
|
||||||
|
get_ipython().run_line_magic('pinfo', 'ls')
|
||||||
|
get_ipython().run_line_magic('pinfo2', 'ls')
|
||||||
|
|
||||||
|
and we look for instances of any of the latter.
|
||||||
|
"""
|
||||||
|
if isinstance(node.value, ast.Call) and _is_ipython_magic(node.value.func):
|
||||||
|
args = _get_str_args(node.value.args)
|
||||||
|
if node.value.func.attr == "run_line_magic":
|
||||||
|
if args[0] == "pinfo":
|
||||||
|
src = f"?{args[1]}"
|
||||||
|
elif args[0] == "pinfo2":
|
||||||
|
src = f"??{args[1]}"
|
||||||
|
else:
|
||||||
|
src = f"%{args[0]}"
|
||||||
|
if args[1]:
|
||||||
|
src += f" {args[1]}"
|
||||||
|
elif node.value.func.attr == "system":
|
||||||
|
src = f"!{args[0]}"
|
||||||
|
elif node.value.func.attr == "getoutput":
|
||||||
|
src = f"!!{args[0]}"
|
||||||
|
else:
|
||||||
|
raise NothingChanged # unsupported magic.
|
||||||
|
self.magics[node.value.lineno].append(
|
||||||
|
OffsetAndMagic(node.value.col_offset, src)
|
||||||
|
)
|
||||||
|
self.generic_visit(node)
|
||||||
2048
.venv_codegen/Lib/site-packages/black/linegen.py
Normal file
2048
.venv_codegen/Lib/site-packages/black/linegen.py
Normal file
File diff suppressed because it is too large
Load diff
1121
.venv_codegen/Lib/site-packages/black/lines.py
Normal file
1121
.venv_codegen/Lib/site-packages/black/lines.py
Normal file
File diff suppressed because it is too large
Load diff
321
.venv_codegen/Lib/site-packages/black/mode.py
Normal file
321
.venv_codegen/Lib/site-packages/black/mode.py
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
"""Data structures configuring Black behavior.
|
||||||
|
|
||||||
|
Mostly around Python language feature support per version and Black configuration
|
||||||
|
chosen by the user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum, auto
|
||||||
|
from hashlib import sha256
|
||||||
|
from operator import attrgetter
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from black.const import DEFAULT_LINE_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
class TargetVersion(Enum):
|
||||||
|
PY33 = 3
|
||||||
|
PY34 = 4
|
||||||
|
PY35 = 5
|
||||||
|
PY36 = 6
|
||||||
|
PY37 = 7
|
||||||
|
PY38 = 8
|
||||||
|
PY39 = 9
|
||||||
|
PY310 = 10
|
||||||
|
PY311 = 11
|
||||||
|
PY312 = 12
|
||||||
|
PY313 = 13
|
||||||
|
PY314 = 14
|
||||||
|
|
||||||
|
def pretty(self) -> str:
|
||||||
|
assert self.name[:2] == "PY"
|
||||||
|
return f"Python {self.name[2]}.{self.name[3:]}"
|
||||||
|
|
||||||
|
|
||||||
|
class Feature(Enum):
|
||||||
|
F_STRINGS = 2
|
||||||
|
NUMERIC_UNDERSCORES = 3
|
||||||
|
TRAILING_COMMA_IN_CALL = 4
|
||||||
|
TRAILING_COMMA_IN_DEF = 5
|
||||||
|
# The following two feature-flags are mutually exclusive, and exactly one should be
|
||||||
|
# set for every version of python.
|
||||||
|
ASYNC_IDENTIFIERS = 6
|
||||||
|
ASYNC_KEYWORDS = 7
|
||||||
|
ASSIGNMENT_EXPRESSIONS = 8
|
||||||
|
POS_ONLY_ARGUMENTS = 9
|
||||||
|
RELAXED_DECORATORS = 10
|
||||||
|
PATTERN_MATCHING = 11
|
||||||
|
UNPACKING_ON_FLOW = 12
|
||||||
|
ANN_ASSIGN_EXTENDED_RHS = 13
|
||||||
|
EXCEPT_STAR = 14
|
||||||
|
VARIADIC_GENERICS = 15
|
||||||
|
DEBUG_F_STRINGS = 16
|
||||||
|
PARENTHESIZED_CONTEXT_MANAGERS = 17
|
||||||
|
TYPE_PARAMS = 18
|
||||||
|
# FSTRING_PARSING = 19 # unused
|
||||||
|
TYPE_PARAM_DEFAULTS = 20
|
||||||
|
UNPARENTHESIZED_EXCEPT_TYPES = 21
|
||||||
|
T_STRINGS = 22
|
||||||
|
FORCE_OPTIONAL_PARENTHESES = 50
|
||||||
|
|
||||||
|
# __future__ flags
|
||||||
|
FUTURE_ANNOTATIONS = 51
|
||||||
|
|
||||||
|
|
||||||
|
FUTURE_FLAG_TO_FEATURE: Final = {
|
||||||
|
"annotations": Feature.FUTURE_ANNOTATIONS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VERSION_TO_FEATURES: dict[TargetVersion, set[Feature]] = {
|
||||||
|
TargetVersion.PY33: {Feature.ASYNC_IDENTIFIERS},
|
||||||
|
TargetVersion.PY34: {Feature.ASYNC_IDENTIFIERS},
|
||||||
|
TargetVersion.PY35: {Feature.TRAILING_COMMA_IN_CALL, Feature.ASYNC_IDENTIFIERS},
|
||||||
|
TargetVersion.PY36: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_IDENTIFIERS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY37: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY38: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY39: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.RELAXED_DECORATORS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
Feature.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY310: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.RELAXED_DECORATORS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
Feature.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
Feature.PATTERN_MATCHING,
|
||||||
|
},
|
||||||
|
TargetVersion.PY311: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.RELAXED_DECORATORS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
Feature.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
Feature.PATTERN_MATCHING,
|
||||||
|
Feature.EXCEPT_STAR,
|
||||||
|
Feature.VARIADIC_GENERICS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY312: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.RELAXED_DECORATORS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
Feature.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
Feature.PATTERN_MATCHING,
|
||||||
|
Feature.EXCEPT_STAR,
|
||||||
|
Feature.VARIADIC_GENERICS,
|
||||||
|
Feature.TYPE_PARAMS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY313: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.RELAXED_DECORATORS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
Feature.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
Feature.PATTERN_MATCHING,
|
||||||
|
Feature.EXCEPT_STAR,
|
||||||
|
Feature.VARIADIC_GENERICS,
|
||||||
|
Feature.TYPE_PARAMS,
|
||||||
|
Feature.TYPE_PARAM_DEFAULTS,
|
||||||
|
},
|
||||||
|
TargetVersion.PY314: {
|
||||||
|
Feature.F_STRINGS,
|
||||||
|
Feature.DEBUG_F_STRINGS,
|
||||||
|
Feature.NUMERIC_UNDERSCORES,
|
||||||
|
Feature.TRAILING_COMMA_IN_CALL,
|
||||||
|
Feature.TRAILING_COMMA_IN_DEF,
|
||||||
|
Feature.ASYNC_KEYWORDS,
|
||||||
|
Feature.FUTURE_ANNOTATIONS,
|
||||||
|
Feature.ASSIGNMENT_EXPRESSIONS,
|
||||||
|
Feature.RELAXED_DECORATORS,
|
||||||
|
Feature.POS_ONLY_ARGUMENTS,
|
||||||
|
Feature.UNPACKING_ON_FLOW,
|
||||||
|
Feature.ANN_ASSIGN_EXTENDED_RHS,
|
||||||
|
Feature.PARENTHESIZED_CONTEXT_MANAGERS,
|
||||||
|
Feature.PATTERN_MATCHING,
|
||||||
|
Feature.EXCEPT_STAR,
|
||||||
|
Feature.VARIADIC_GENERICS,
|
||||||
|
Feature.TYPE_PARAMS,
|
||||||
|
Feature.TYPE_PARAM_DEFAULTS,
|
||||||
|
Feature.UNPARENTHESIZED_EXCEPT_TYPES,
|
||||||
|
Feature.T_STRINGS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def supports_feature(target_versions: set[TargetVersion], feature: Feature) -> bool:
|
||||||
|
if not target_versions:
|
||||||
|
raise ValueError("At least one target Python version must be specified.")
|
||||||
|
|
||||||
|
return all(feature in VERSION_TO_FEATURES[version] for version in target_versions)
|
||||||
|
|
||||||
|
|
||||||
|
class Preview(Enum):
|
||||||
|
"""Individual preview style features."""
|
||||||
|
|
||||||
|
# NOTE: string_processing requires wrap_long_dict_values_in_parens
|
||||||
|
# for https://github.com/psf/black/issues/3117 to be fixed.
|
||||||
|
string_processing = auto()
|
||||||
|
hug_parens_with_braces_and_square_brackets = auto()
|
||||||
|
wrap_comprehension_in = auto()
|
||||||
|
simplify_power_operator_hugging = auto()
|
||||||
|
wrap_long_dict_values_in_parens = auto()
|
||||||
|
fix_if_guard_explosion_in_case_statement = auto()
|
||||||
|
|
||||||
|
|
||||||
|
UNSTABLE_FEATURES: set[Preview] = {
|
||||||
|
# Many issues, see summary in https://github.com/psf/black/issues/4208
|
||||||
|
Preview.string_processing,
|
||||||
|
# See issue #4036 (crash), #4098, #4099 (proposed tweaks)
|
||||||
|
Preview.hug_parens_with_braces_and_square_brackets,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_MAX_CACHE_KEY_PART_LENGTH: Final = 32
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Mode:
|
||||||
|
target_versions: set[TargetVersion] = field(default_factory=set)
|
||||||
|
line_length: int = DEFAULT_LINE_LENGTH
|
||||||
|
string_normalization: bool = True
|
||||||
|
is_pyi: bool = False
|
||||||
|
is_ipynb: bool = False
|
||||||
|
skip_source_first_line: bool = False
|
||||||
|
magic_trailing_comma: bool = True
|
||||||
|
python_cell_magics: set[str] = field(default_factory=set)
|
||||||
|
preview: bool = False
|
||||||
|
unstable: bool = False
|
||||||
|
enabled_features: set[Preview] = field(default_factory=set)
|
||||||
|
|
||||||
|
def __contains__(self, feature: Preview) -> bool:
|
||||||
|
"""
|
||||||
|
Provide `Preview.FEATURE in Mode` syntax that mirrors the ``preview`` flag.
|
||||||
|
|
||||||
|
In unstable mode, all features are enabled. In preview mode, all features
|
||||||
|
except those in UNSTABLE_FEATURES are enabled. Any features in
|
||||||
|
`self.enabled_features` are also enabled.
|
||||||
|
"""
|
||||||
|
if self.unstable:
|
||||||
|
return True
|
||||||
|
if feature in self.enabled_features:
|
||||||
|
return True
|
||||||
|
return self.preview and feature not in UNSTABLE_FEATURES
|
||||||
|
|
||||||
|
def get_cache_key(self) -> str:
|
||||||
|
if self.target_versions:
|
||||||
|
version_str = ",".join(
|
||||||
|
str(version.value)
|
||||||
|
for version in sorted(self.target_versions, key=attrgetter("value"))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
version_str = "-"
|
||||||
|
if len(version_str) > _MAX_CACHE_KEY_PART_LENGTH:
|
||||||
|
version_str = sha256(version_str.encode()).hexdigest()[
|
||||||
|
:_MAX_CACHE_KEY_PART_LENGTH
|
||||||
|
]
|
||||||
|
features_and_magics = (
|
||||||
|
",".join(sorted(f.name for f in self.enabled_features))
|
||||||
|
+ "@"
|
||||||
|
+ ",".join(sorted(self.python_cell_magics))
|
||||||
|
)
|
||||||
|
features_and_magics = sha256(features_and_magics.encode()).hexdigest()[
|
||||||
|
:_MAX_CACHE_KEY_PART_LENGTH
|
||||||
|
]
|
||||||
|
parts = [
|
||||||
|
version_str,
|
||||||
|
str(self.line_length),
|
||||||
|
str(int(self.string_normalization)),
|
||||||
|
str(int(self.is_pyi)),
|
||||||
|
str(int(self.is_ipynb)),
|
||||||
|
str(int(self.skip_source_first_line)),
|
||||||
|
str(int(self.magic_trailing_comma)),
|
||||||
|
str(int(self.preview)),
|
||||||
|
str(int(self.unstable)),
|
||||||
|
features_and_magics,
|
||||||
|
]
|
||||||
|
return ".".join(parts)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((
|
||||||
|
frozenset(self.target_versions),
|
||||||
|
self.line_length,
|
||||||
|
self.string_normalization,
|
||||||
|
self.is_pyi,
|
||||||
|
self.is_ipynb,
|
||||||
|
self.skip_source_first_line,
|
||||||
|
self.magic_trailing_comma,
|
||||||
|
frozenset(self.python_cell_magics),
|
||||||
|
self.preview,
|
||||||
|
self.unstable,
|
||||||
|
frozenset(self.enabled_features),
|
||||||
|
))
|
||||||
1108
.venv_codegen/Lib/site-packages/black/nodes.py
Normal file
1108
.venv_codegen/Lib/site-packages/black/nodes.py
Normal file
File diff suppressed because it is too large
Load diff
61
.venv_codegen/Lib/site-packages/black/numerics.py
Normal file
61
.venv_codegen/Lib/site-packages/black/numerics.py
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
"""
|
||||||
|
Formatting numeric literals.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from blib2to3.pytree import Leaf
|
||||||
|
|
||||||
|
|
||||||
|
def format_hex(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Formats a hexadecimal string like "0x12B3"
|
||||||
|
"""
|
||||||
|
before, after = text[:2], text[2:]
|
||||||
|
return f"{before}{after.upper()}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_scientific_notation(text: str) -> str:
|
||||||
|
"""Formats a numeric string utilizing scientific notation"""
|
||||||
|
before, after = text.split("e")
|
||||||
|
sign = ""
|
||||||
|
if after.startswith("-"):
|
||||||
|
after = after[1:]
|
||||||
|
sign = "-"
|
||||||
|
elif after.startswith("+"):
|
||||||
|
after = after[1:]
|
||||||
|
before = format_float_or_int_string(before)
|
||||||
|
return f"{before}e{sign}{after}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_complex_number(text: str) -> str:
|
||||||
|
"""Formats a complex string like `10j`"""
|
||||||
|
number = text[:-1]
|
||||||
|
suffix = text[-1]
|
||||||
|
return f"{format_float_or_int_string(number)}{suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def format_float_or_int_string(text: str) -> str:
|
||||||
|
"""Formats a float string like "1.0"."""
|
||||||
|
if "." not in text:
|
||||||
|
return text
|
||||||
|
|
||||||
|
before, after = text.split(".")
|
||||||
|
return f"{before or 0}.{after or 0}"
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_numeric_literal(leaf: Leaf) -> None:
|
||||||
|
"""Normalizes numeric (float, int, and complex) literals.
|
||||||
|
|
||||||
|
All letters used in the representation are normalized to lowercase."""
|
||||||
|
text = leaf.value.lower()
|
||||||
|
if text.startswith(("0o", "0b")):
|
||||||
|
# Leave octal and binary literals alone.
|
||||||
|
pass
|
||||||
|
elif text.startswith("0x"):
|
||||||
|
text = format_hex(text)
|
||||||
|
elif "e" in text:
|
||||||
|
text = format_scientific_notation(text)
|
||||||
|
elif text.endswith("j"):
|
||||||
|
text = format_complex_number(text)
|
||||||
|
else:
|
||||||
|
text = format_float_or_int_string(text)
|
||||||
|
leaf.value = text
|
||||||
122
.venv_codegen/Lib/site-packages/black/output.py
Normal file
122
.venv_codegen/Lib/site-packages/black/output.py
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
"""Nice output for Black.
|
||||||
|
|
||||||
|
The double calls are for patching purposes in tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from click import echo, style
|
||||||
|
from mypy_extensions import mypyc_attr
|
||||||
|
|
||||||
|
|
||||||
|
@mypyc_attr(patchable=True)
|
||||||
|
def _out(message: str | None = None, nl: bool = True, **styles: Any) -> None:
|
||||||
|
if message is not None:
|
||||||
|
if "bold" not in styles:
|
||||||
|
styles["bold"] = True
|
||||||
|
message = style(message, **styles)
|
||||||
|
echo(message, nl=nl, err=True)
|
||||||
|
|
||||||
|
|
||||||
|
@mypyc_attr(patchable=True)
|
||||||
|
def _err(message: str | None = None, nl: bool = True, **styles: Any) -> None:
|
||||||
|
if message is not None:
|
||||||
|
if "fg" not in styles:
|
||||||
|
styles["fg"] = "red"
|
||||||
|
message = style(message, **styles)
|
||||||
|
echo(message, nl=nl, err=True)
|
||||||
|
|
||||||
|
|
||||||
|
@mypyc_attr(patchable=True)
|
||||||
|
def out(message: str | None = None, nl: bool = True, **styles: Any) -> None:
|
||||||
|
_out(message, nl=nl, **styles)
|
||||||
|
|
||||||
|
|
||||||
|
def err(message: str | None = None, nl: bool = True, **styles: Any) -> None:
|
||||||
|
_err(message, nl=nl, **styles)
|
||||||
|
|
||||||
|
|
||||||
|
def ipynb_diff(a: str, b: str, a_name: str, b_name: str) -> str:
|
||||||
|
"""Return a unified diff string between each cell in notebooks `a` and `b`."""
|
||||||
|
a_nb = json.loads(a)
|
||||||
|
b_nb = json.loads(b)
|
||||||
|
diff_lines = [
|
||||||
|
diff(
|
||||||
|
"".join(a_nb["cells"][cell_number]["source"]) + "\n",
|
||||||
|
"".join(b_nb["cells"][cell_number]["source"]) + "\n",
|
||||||
|
f"{a_name}:cell_{cell_number}",
|
||||||
|
f"{b_name}:cell_{cell_number}",
|
||||||
|
)
|
||||||
|
for cell_number, cell in enumerate(a_nb["cells"])
|
||||||
|
if cell["cell_type"] == "code"
|
||||||
|
]
|
||||||
|
return "".join(diff_lines)
|
||||||
|
|
||||||
|
|
||||||
|
_line_pattern = re.compile(r"(.*?(?:\r\n|\n|\r|$))")
|
||||||
|
|
||||||
|
|
||||||
|
def _splitlines_no_ff(source: str) -> list[str]:
|
||||||
|
"""Split a string into lines ignoring form feed and other chars.
|
||||||
|
|
||||||
|
This mimics how the Python parser splits source code.
|
||||||
|
|
||||||
|
A simplified version of the function with the same name in Lib/ast.py
|
||||||
|
"""
|
||||||
|
result = [match[0] for match in _line_pattern.finditer(source)]
|
||||||
|
if result[-1] == "":
|
||||||
|
result.pop(-1)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def diff(a: str, b: str, a_name: str, b_name: str) -> str:
|
||||||
|
"""Return a unified diff string between strings `a` and `b`."""
|
||||||
|
import difflib
|
||||||
|
|
||||||
|
a_lines = _splitlines_no_ff(a)
|
||||||
|
b_lines = _splitlines_no_ff(b)
|
||||||
|
diff_lines = []
|
||||||
|
for line in difflib.unified_diff(
|
||||||
|
a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5
|
||||||
|
):
|
||||||
|
# Work around https://bugs.python.org/issue2142
|
||||||
|
# See:
|
||||||
|
# https://www.gnu.org/software/diffutils/manual/html_node/Incomplete-Lines.html
|
||||||
|
if line[-1] == "\n":
|
||||||
|
diff_lines.append(line)
|
||||||
|
else:
|
||||||
|
diff_lines.append(line + "\n")
|
||||||
|
diff_lines.append("\\ No newline at end of file\n")
|
||||||
|
return "".join(diff_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def color_diff(contents: str) -> str:
|
||||||
|
"""Inject the ANSI color codes to the diff."""
|
||||||
|
lines = contents.split("\n")
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith("+++") or line.startswith("---"):
|
||||||
|
line = "\033[1m" + line + "\033[0m" # bold, reset
|
||||||
|
elif line.startswith("@@"):
|
||||||
|
line = "\033[36m" + line + "\033[0m" # cyan, reset
|
||||||
|
elif line.startswith("+"):
|
||||||
|
line = "\033[32m" + line + "\033[0m" # green, reset
|
||||||
|
elif line.startswith("-"):
|
||||||
|
line = "\033[31m" + line + "\033[0m" # red, reset
|
||||||
|
lines[i] = line
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@mypyc_attr(patchable=True)
|
||||||
|
def dump_to_file(*output: str, ensure_final_newline: bool = True) -> str:
|
||||||
|
"""Dump `output` to a temporary file. Return path to the file."""
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
mode="w", prefix="blk_", suffix=".log", delete=False, encoding="utf8"
|
||||||
|
) as f:
|
||||||
|
for lines in output:
|
||||||
|
f.write(lines)
|
||||||
|
if ensure_final_newline and lines and lines[-1] != "\n":
|
||||||
|
f.write("\n")
|
||||||
|
return f.name
|
||||||
244
.venv_codegen/Lib/site-packages/black/parsing.py
Normal file
244
.venv_codegen/Lib/site-packages/black/parsing.py
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
"""
|
||||||
|
Parse Python code and perform AST validation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
from collections.abc import Collection, Iterator
|
||||||
|
|
||||||
|
from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature
|
||||||
|
from black.nodes import syms
|
||||||
|
from blib2to3 import pygram
|
||||||
|
from blib2to3.pgen2 import driver
|
||||||
|
from blib2to3.pgen2.grammar import Grammar
|
||||||
|
from blib2to3.pgen2.parse import ParseError
|
||||||
|
from blib2to3.pgen2.tokenize import TokenError
|
||||||
|
from blib2to3.pytree import Leaf, Node
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInput(ValueError):
|
||||||
|
"""Raised when input source code fails all parse attempts."""
|
||||||
|
|
||||||
|
|
||||||
|
def get_grammars(target_versions: set[TargetVersion]) -> list[Grammar]:
|
||||||
|
if not target_versions:
|
||||||
|
# No target_version specified, so try all grammars.
|
||||||
|
return [
|
||||||
|
# Python 3.7-3.9
|
||||||
|
pygram.python_grammar_async_keywords,
|
||||||
|
# Python 3.0-3.6
|
||||||
|
pygram.python_grammar,
|
||||||
|
# Python 3.10+
|
||||||
|
pygram.python_grammar_soft_keywords,
|
||||||
|
]
|
||||||
|
|
||||||
|
grammars = []
|
||||||
|
# If we have to parse both, try to parse async as a keyword first
|
||||||
|
if not supports_feature(
|
||||||
|
target_versions, Feature.ASYNC_IDENTIFIERS
|
||||||
|
) and not supports_feature(target_versions, Feature.PATTERN_MATCHING):
|
||||||
|
# Python 3.7-3.9
|
||||||
|
grammars.append(pygram.python_grammar_async_keywords)
|
||||||
|
if not supports_feature(target_versions, Feature.ASYNC_KEYWORDS):
|
||||||
|
# Python 3.0-3.6
|
||||||
|
grammars.append(pygram.python_grammar)
|
||||||
|
if any(Feature.PATTERN_MATCHING in VERSION_TO_FEATURES[v] for v in target_versions):
|
||||||
|
# Python 3.10+
|
||||||
|
grammars.append(pygram.python_grammar_soft_keywords)
|
||||||
|
|
||||||
|
# At least one of the above branches must have been taken, because every Python
|
||||||
|
# version has exactly one of the two 'ASYNC_*' flags
|
||||||
|
return grammars
|
||||||
|
|
||||||
|
|
||||||
|
def lib2to3_parse(
|
||||||
|
src_txt: str, target_versions: Collection[TargetVersion] = ()
|
||||||
|
) -> Node:
|
||||||
|
"""Given a string with source, return the lib2to3 Node."""
|
||||||
|
if not src_txt.endswith("\n"):
|
||||||
|
src_txt += "\n"
|
||||||
|
|
||||||
|
grammars = get_grammars(set(target_versions))
|
||||||
|
if target_versions:
|
||||||
|
max_tv = max(target_versions, key=lambda tv: tv.value)
|
||||||
|
tv_str = f" for target version {max_tv.pretty()}"
|
||||||
|
else:
|
||||||
|
tv_str = ""
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
for grammar in grammars:
|
||||||
|
drv = driver.Driver(grammar)
|
||||||
|
try:
|
||||||
|
result = drv.parse_string(src_txt, False)
|
||||||
|
break
|
||||||
|
|
||||||
|
except ParseError as pe:
|
||||||
|
lineno, column = pe.context[1]
|
||||||
|
lines = src_txt.splitlines()
|
||||||
|
try:
|
||||||
|
faulty_line = lines[lineno - 1]
|
||||||
|
except IndexError:
|
||||||
|
faulty_line = "<line number missing in source>"
|
||||||
|
errors[grammar.version] = InvalidInput(
|
||||||
|
f"Cannot parse{tv_str}: {lineno}:{column}: {faulty_line}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except TokenError as te:
|
||||||
|
# In edge cases these are raised; and typically don't have a "faulty_line".
|
||||||
|
lineno, column = te.args[1]
|
||||||
|
errors[grammar.version] = InvalidInput(
|
||||||
|
f"Cannot parse{tv_str}: {lineno}:{column}: {te.args[0]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Choose the latest version when raising the actual parsing error.
|
||||||
|
assert len(errors) >= 1
|
||||||
|
exc = errors[max(errors)]
|
||||||
|
raise exc from None
|
||||||
|
|
||||||
|
if isinstance(result, Leaf):
|
||||||
|
result = Node(syms.file_input, [result])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ASTSafetyError(Exception):
|
||||||
|
"""Raised when Black's generated code is not equivalent to the old AST."""
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_single_version(
|
||||||
|
src: str, version: tuple[int, int], *, type_comments: bool
|
||||||
|
) -> ast.AST:
|
||||||
|
filename = "<unknown>"
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore", SyntaxWarning)
|
||||||
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
|
return ast.parse(
|
||||||
|
src, filename, feature_version=version, type_comments=type_comments
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ast(src: str) -> ast.AST:
|
||||||
|
# TODO: support Python 4+ ;)
|
||||||
|
versions = [(3, minor) for minor in range(3, sys.version_info[1] + 1)]
|
||||||
|
|
||||||
|
first_error = ""
|
||||||
|
for version in sorted(versions, reverse=True):
|
||||||
|
try:
|
||||||
|
return _parse_single_version(src, version, type_comments=True)
|
||||||
|
except SyntaxError as e:
|
||||||
|
if not first_error:
|
||||||
|
first_error = str(e)
|
||||||
|
|
||||||
|
# Try to parse without type comments
|
||||||
|
for version in sorted(versions, reverse=True):
|
||||||
|
try:
|
||||||
|
return _parse_single_version(src, version, type_comments=False)
|
||||||
|
except SyntaxError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise SyntaxError(first_error)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize(lineend: str, value: str) -> str:
|
||||||
|
# To normalize, we strip any leading and trailing space from
|
||||||
|
# each line...
|
||||||
|
stripped: list[str] = [i.strip() for i in value.splitlines()]
|
||||||
|
normalized = lineend.join(stripped)
|
||||||
|
# ...and remove any blank lines at the beginning and end of
|
||||||
|
# the whole string
|
||||||
|
return normalized.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def stringify_ast(node: ast.AST) -> Iterator[str]:
|
||||||
|
"""Simple visitor generating strings to compare ASTs by content."""
|
||||||
|
return _stringify_ast(node, [])
|
||||||
|
|
||||||
|
|
||||||
|
def _stringify_ast_with_new_parent(
|
||||||
|
node: ast.AST, parent_stack: list[ast.AST], new_parent: ast.AST
|
||||||
|
) -> Iterator[str]:
|
||||||
|
parent_stack.append(new_parent)
|
||||||
|
yield from _stringify_ast(node, parent_stack)
|
||||||
|
parent_stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def _stringify_ast(node: ast.AST, parent_stack: list[ast.AST]) -> Iterator[str]:
|
||||||
|
if (
|
||||||
|
isinstance(node, ast.Constant)
|
||||||
|
and isinstance(node.value, str)
|
||||||
|
and node.kind == "u"
|
||||||
|
):
|
||||||
|
# It's a quirk of history that we strip the u prefix over here. We used to
|
||||||
|
# rewrite the AST nodes for Python version compatibility and we never copied
|
||||||
|
# over the kind
|
||||||
|
node.kind = None
|
||||||
|
|
||||||
|
yield f"{' ' * len(parent_stack)}{node.__class__.__name__}("
|
||||||
|
|
||||||
|
for field in sorted(node._fields):
|
||||||
|
# TypeIgnore has only one field 'lineno' which breaks this comparison
|
||||||
|
if isinstance(node, ast.TypeIgnore):
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
value: object = getattr(node, field)
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield f"{' ' * (len(parent_stack) + 1)}{field}="
|
||||||
|
|
||||||
|
if isinstance(value, list):
|
||||||
|
for item in value:
|
||||||
|
# Ignore nested tuples within del statements, because we may insert
|
||||||
|
# parentheses and they change the AST.
|
||||||
|
if (
|
||||||
|
field == "targets"
|
||||||
|
and isinstance(node, ast.Delete)
|
||||||
|
and isinstance(item, ast.Tuple)
|
||||||
|
):
|
||||||
|
for elt in _unwrap_tuples(item):
|
||||||
|
yield from _stringify_ast_with_new_parent(
|
||||||
|
elt, parent_stack, node
|
||||||
|
)
|
||||||
|
|
||||||
|
elif isinstance(item, ast.AST):
|
||||||
|
yield from _stringify_ast_with_new_parent(item, parent_stack, node)
|
||||||
|
|
||||||
|
elif isinstance(value, ast.AST):
|
||||||
|
yield from _stringify_ast_with_new_parent(value, parent_stack, node)
|
||||||
|
|
||||||
|
else:
|
||||||
|
normalized: object
|
||||||
|
if (
|
||||||
|
isinstance(node, ast.Constant)
|
||||||
|
and field == "value"
|
||||||
|
and isinstance(value, str)
|
||||||
|
and len(parent_stack) >= 2
|
||||||
|
# Any standalone string, ideally this would
|
||||||
|
# exactly match black.nodes.is_docstring
|
||||||
|
and isinstance(parent_stack[-1], ast.Expr)
|
||||||
|
):
|
||||||
|
# Constant strings may be indented across newlines, if they are
|
||||||
|
# docstrings; fold spaces after newlines when comparing. Similarly,
|
||||||
|
# trailing and leading space may be removed.
|
||||||
|
normalized = _normalize("\n", value)
|
||||||
|
elif field == "type_comment" and isinstance(value, str):
|
||||||
|
# Trailing whitespace in type comments is removed.
|
||||||
|
normalized = value.rstrip()
|
||||||
|
else:
|
||||||
|
normalized = value
|
||||||
|
yield (
|
||||||
|
f"{' ' * (len(parent_stack) + 1)}{normalized!r}, #"
|
||||||
|
f" {value.__class__.__name__}"
|
||||||
|
)
|
||||||
|
|
||||||
|
yield f"{' ' * len(parent_stack)}) # /{node.__class__.__name__}"
|
||||||
|
|
||||||
|
|
||||||
|
def _unwrap_tuples(node: ast.Tuple) -> Iterator[ast.AST]:
|
||||||
|
for elt in node.elts:
|
||||||
|
if isinstance(elt, ast.Tuple):
|
||||||
|
yield from _unwrap_tuples(elt)
|
||||||
|
else:
|
||||||
|
yield elt
|
||||||
0
.venv_codegen/Lib/site-packages/black/py.typed
Normal file
0
.venv_codegen/Lib/site-packages/black/py.typed
Normal file
534
.venv_codegen/Lib/site-packages/black/ranges.py
Normal file
534
.venv_codegen/Lib/site-packages/black/ranges.py
Normal file
|
|
@ -0,0 +1,534 @@
|
||||||
|
"""Functions related to Black's formatting by line ranges feature."""
|
||||||
|
|
||||||
|
import difflib
|
||||||
|
from collections.abc import Collection, Iterator, Sequence
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from black.nodes import (
|
||||||
|
LN,
|
||||||
|
STANDALONE_COMMENT,
|
||||||
|
Leaf,
|
||||||
|
Node,
|
||||||
|
Visitor,
|
||||||
|
first_leaf,
|
||||||
|
furthest_ancestor_with_last_leaf,
|
||||||
|
last_leaf,
|
||||||
|
syms,
|
||||||
|
)
|
||||||
|
from blib2to3.pgen2.token import ASYNC, NEWLINE
|
||||||
|
|
||||||
|
|
||||||
|
def parse_line_ranges(line_ranges: Sequence[str]) -> list[tuple[int, int]]:
|
||||||
|
lines: list[tuple[int, int]] = []
|
||||||
|
for lines_str in line_ranges:
|
||||||
|
parts = lines_str.split("-")
|
||||||
|
if len(parts) != 2:
|
||||||
|
raise ValueError(
|
||||||
|
"Incorrect --line-ranges format, expect 'START-END', found"
|
||||||
|
f" {lines_str!r}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
start = int(parts[0])
|
||||||
|
end = int(parts[1])
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
"Incorrect --line-ranges value, expect integer ranges, found"
|
||||||
|
f" {lines_str!r}"
|
||||||
|
) from None
|
||||||
|
else:
|
||||||
|
lines.append((start, end))
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_line_range(lines: tuple[int, int]) -> bool:
|
||||||
|
"""Returns whether the line range is valid."""
|
||||||
|
return not lines or lines[0] <= lines[1]
|
||||||
|
|
||||||
|
|
||||||
|
def sanitized_lines(
|
||||||
|
lines: Collection[tuple[int, int]], src_contents: str
|
||||||
|
) -> Collection[tuple[int, int]]:
|
||||||
|
"""Returns the valid line ranges for the given source.
|
||||||
|
|
||||||
|
This removes ranges that are entirely outside the valid lines.
|
||||||
|
|
||||||
|
Other ranges are normalized so that the start values are at least 1 and the
|
||||||
|
end values are at most the (1-based) index of the last source line.
|
||||||
|
"""
|
||||||
|
if not src_contents:
|
||||||
|
return []
|
||||||
|
good_lines = []
|
||||||
|
src_line_count = src_contents.count("\n")
|
||||||
|
if not src_contents.endswith("\n"):
|
||||||
|
src_line_count += 1
|
||||||
|
for start, end in lines:
|
||||||
|
if start > src_line_count:
|
||||||
|
continue
|
||||||
|
# line-ranges are 1-based
|
||||||
|
start = max(start, 1)
|
||||||
|
if end < start:
|
||||||
|
continue
|
||||||
|
end = min(end, src_line_count)
|
||||||
|
good_lines.append((start, end))
|
||||||
|
return good_lines
|
||||||
|
|
||||||
|
|
||||||
|
def adjusted_lines(
|
||||||
|
lines: Collection[tuple[int, int]],
|
||||||
|
original_source: str,
|
||||||
|
modified_source: str,
|
||||||
|
) -> list[tuple[int, int]]:
|
||||||
|
"""Returns the adjusted line ranges based on edits from the original code.
|
||||||
|
|
||||||
|
This computes the new line ranges by diffing original_source and
|
||||||
|
modified_source, and adjust each range based on how the range overlaps with
|
||||||
|
the diffs.
|
||||||
|
|
||||||
|
Note the diff can contain lines outside of the original line ranges. This can
|
||||||
|
happen when the formatting has to be done in adjacent to maintain consistent
|
||||||
|
local results. For example:
|
||||||
|
|
||||||
|
1. def my_func(arg1, arg2,
|
||||||
|
2. arg3,):
|
||||||
|
3. pass
|
||||||
|
|
||||||
|
If it restricts to line 2-2, it can't simply reformat line 2, it also has
|
||||||
|
to reformat line 1:
|
||||||
|
|
||||||
|
1. def my_func(
|
||||||
|
2. arg1,
|
||||||
|
3. arg2,
|
||||||
|
4. arg3,
|
||||||
|
5. ):
|
||||||
|
6. pass
|
||||||
|
|
||||||
|
In this case, we will expand the line ranges to also include the whole diff
|
||||||
|
block.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lines: a collection of line ranges.
|
||||||
|
original_source: the original source.
|
||||||
|
modified_source: the modified source.
|
||||||
|
"""
|
||||||
|
lines_mappings = _calculate_lines_mappings(original_source, modified_source)
|
||||||
|
|
||||||
|
new_lines = []
|
||||||
|
# Keep an index of the current search. Since the lines and lines_mappings are
|
||||||
|
# sorted, this makes the search complexity linear.
|
||||||
|
current_mapping_index = 0
|
||||||
|
for start, end in sorted(lines):
|
||||||
|
start_mapping_index = _find_lines_mapping_index(
|
||||||
|
start,
|
||||||
|
lines_mappings,
|
||||||
|
current_mapping_index,
|
||||||
|
)
|
||||||
|
end_mapping_index = _find_lines_mapping_index(
|
||||||
|
end,
|
||||||
|
lines_mappings,
|
||||||
|
start_mapping_index,
|
||||||
|
)
|
||||||
|
current_mapping_index = start_mapping_index
|
||||||
|
if start_mapping_index >= len(lines_mappings) or end_mapping_index >= len(
|
||||||
|
lines_mappings
|
||||||
|
):
|
||||||
|
# Protect against invalid inputs.
|
||||||
|
continue
|
||||||
|
start_mapping = lines_mappings[start_mapping_index]
|
||||||
|
end_mapping = lines_mappings[end_mapping_index]
|
||||||
|
if start_mapping.is_changed_block:
|
||||||
|
# When the line falls into a changed block, expands to the whole block.
|
||||||
|
new_start = start_mapping.modified_start
|
||||||
|
else:
|
||||||
|
new_start = (
|
||||||
|
start - start_mapping.original_start + start_mapping.modified_start
|
||||||
|
)
|
||||||
|
if end_mapping.is_changed_block:
|
||||||
|
# When the line falls into a changed block, expands to the whole block.
|
||||||
|
new_end = end_mapping.modified_end
|
||||||
|
else:
|
||||||
|
new_end = end - end_mapping.original_start + end_mapping.modified_start
|
||||||
|
new_range = (new_start, new_end)
|
||||||
|
if is_valid_line_range(new_range):
|
||||||
|
new_lines.append(new_range)
|
||||||
|
return new_lines
|
||||||
|
|
||||||
|
|
||||||
|
def convert_unchanged_lines(src_node: Node, lines: Collection[tuple[int, int]]) -> None:
|
||||||
|
r"""Converts unchanged lines to STANDALONE_COMMENT.
|
||||||
|
|
||||||
|
The idea is similar to how `# fmt: on/off` is implemented. It also converts the
|
||||||
|
nodes between those markers as a single `STANDALONE_COMMENT` leaf node with
|
||||||
|
the unformatted code as its value. `STANDALONE_COMMENT` is a "fake" token
|
||||||
|
that will be formatted as-is with its prefix normalized.
|
||||||
|
|
||||||
|
Here we perform two passes:
|
||||||
|
|
||||||
|
1. Visit the top-level statements, and convert them to a single
|
||||||
|
`STANDALONE_COMMENT` when unchanged. This speeds up formatting when some
|
||||||
|
of the top-level statements aren't changed.
|
||||||
|
2. Convert unchanged "unwrapped lines" to `STANDALONE_COMMENT` nodes line by
|
||||||
|
line. "unwrapped lines" are divided by the `NEWLINE` token. e.g. a
|
||||||
|
multi-line statement is *one* "unwrapped line" that ends with `NEWLINE`,
|
||||||
|
even though this statement itself can span multiple lines, and the
|
||||||
|
tokenizer only sees the last '\n' as the `NEWLINE` token.
|
||||||
|
|
||||||
|
NOTE: During pass (2), comment prefixes and indentations are ALWAYS
|
||||||
|
normalized even when the lines aren't changed. This is fixable by moving
|
||||||
|
more formatting to pass (1). However, it's hard to get it correct when
|
||||||
|
incorrect indentations are used. So we defer this to future optimizations.
|
||||||
|
"""
|
||||||
|
lines_set: set[int] = set()
|
||||||
|
for start, end in lines:
|
||||||
|
lines_set.update(range(start, end + 1))
|
||||||
|
visitor = _TopLevelStatementsVisitor(lines_set)
|
||||||
|
_ = list(visitor.visit(src_node)) # Consume all results.
|
||||||
|
_convert_unchanged_line_by_line(src_node, lines_set)
|
||||||
|
|
||||||
|
|
||||||
|
def _contains_standalone_comment(node: LN) -> bool:
|
||||||
|
if isinstance(node, Leaf):
|
||||||
|
return node.type == STANDALONE_COMMENT
|
||||||
|
else:
|
||||||
|
for child in node.children:
|
||||||
|
if _contains_standalone_comment(child):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class _TopLevelStatementsVisitor(Visitor[None]):
|
||||||
|
"""
|
||||||
|
A node visitor that converts unchanged top-level statements to
|
||||||
|
STANDALONE_COMMENT.
|
||||||
|
|
||||||
|
This is used in addition to _convert_unchanged_line_by_line, to
|
||||||
|
speed up formatting when there are unchanged top-level
|
||||||
|
classes/functions/statements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, lines_set: set[int]):
|
||||||
|
self._lines_set = lines_set
|
||||||
|
|
||||||
|
def visit_simple_stmt(self, node: Node) -> Iterator[None]:
|
||||||
|
# This is only called for top-level statements, since `visit_suite`
|
||||||
|
# won't visit its children nodes.
|
||||||
|
yield from []
|
||||||
|
newline_leaf = last_leaf(node)
|
||||||
|
if not newline_leaf:
|
||||||
|
return
|
||||||
|
assert (
|
||||||
|
newline_leaf.type == NEWLINE
|
||||||
|
), f"Unexpectedly found leaf.type={newline_leaf.type}"
|
||||||
|
# We need to find the furthest ancestor with the NEWLINE as the last
|
||||||
|
# leaf, since a `suite` can simply be a `simple_stmt` when it puts
|
||||||
|
# its body on the same line. Example: `if cond: pass`.
|
||||||
|
ancestor = furthest_ancestor_with_last_leaf(newline_leaf)
|
||||||
|
if not _get_line_range(ancestor).intersection(self._lines_set):
|
||||||
|
_convert_node_to_standalone_comment(ancestor)
|
||||||
|
|
||||||
|
def visit_suite(self, node: Node) -> Iterator[None]:
|
||||||
|
yield from []
|
||||||
|
# If there is a STANDALONE_COMMENT node, it means parts of the node tree
|
||||||
|
# have fmt on/off/skip markers. Those STANDALONE_COMMENT nodes can't
|
||||||
|
# be simply converted by calling str(node). So we just don't convert
|
||||||
|
# here.
|
||||||
|
if _contains_standalone_comment(node):
|
||||||
|
return
|
||||||
|
# Find the semantic parent of this suite. For `async_stmt` and
|
||||||
|
# `async_funcdef`, the ASYNC token is defined on a separate level by the
|
||||||
|
# grammar.
|
||||||
|
semantic_parent = node.parent
|
||||||
|
if semantic_parent is not None:
|
||||||
|
if (
|
||||||
|
semantic_parent.prev_sibling is not None
|
||||||
|
and semantic_parent.prev_sibling.type == ASYNC
|
||||||
|
):
|
||||||
|
semantic_parent = semantic_parent.parent
|
||||||
|
if semantic_parent is not None and not _get_line_range(
|
||||||
|
semantic_parent
|
||||||
|
).intersection(self._lines_set):
|
||||||
|
_convert_node_to_standalone_comment(semantic_parent)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_unchanged_line_by_line(node: Node, lines_set: set[int]) -> None:
|
||||||
|
"""Converts unchanged to STANDALONE_COMMENT line by line."""
|
||||||
|
for leaf in node.leaves():
|
||||||
|
if leaf.type != NEWLINE:
|
||||||
|
# We only consider "unwrapped lines", which are divided by the NEWLINE
|
||||||
|
# token.
|
||||||
|
continue
|
||||||
|
if leaf.parent and leaf.parent.type == syms.match_stmt:
|
||||||
|
# The `suite` node is defined as:
|
||||||
|
# match_stmt: "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
|
||||||
|
# Here we need to check `subject_expr`. The `case_block+` will be
|
||||||
|
# checked by their own NEWLINEs.
|
||||||
|
nodes_to_ignore: list[LN] = []
|
||||||
|
prev_sibling = leaf.prev_sibling
|
||||||
|
while prev_sibling:
|
||||||
|
nodes_to_ignore.insert(0, prev_sibling)
|
||||||
|
prev_sibling = prev_sibling.prev_sibling
|
||||||
|
if not _get_line_range(nodes_to_ignore).intersection(lines_set):
|
||||||
|
_convert_nodes_to_standalone_comment(nodes_to_ignore, newline=leaf)
|
||||||
|
elif leaf.parent and leaf.parent.type == syms.suite:
|
||||||
|
# The `suite` node is defined as:
|
||||||
|
# suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
|
||||||
|
# We will check `simple_stmt` and `stmt+` separately against the lines set
|
||||||
|
parent_sibling = leaf.parent.prev_sibling
|
||||||
|
nodes_to_ignore = []
|
||||||
|
while parent_sibling and parent_sibling.type != syms.suite:
|
||||||
|
# NOTE: Multiple suite nodes can exist as siblings in e.g. `if_stmt`.
|
||||||
|
nodes_to_ignore.insert(0, parent_sibling)
|
||||||
|
parent_sibling = parent_sibling.prev_sibling
|
||||||
|
# Special case for `async_stmt` and `async_funcdef` where the ASYNC
|
||||||
|
# token is on the grandparent node.
|
||||||
|
grandparent = leaf.parent.parent
|
||||||
|
if (
|
||||||
|
grandparent is not None
|
||||||
|
and grandparent.prev_sibling is not None
|
||||||
|
and grandparent.prev_sibling.type == ASYNC
|
||||||
|
):
|
||||||
|
nodes_to_ignore.insert(0, grandparent.prev_sibling)
|
||||||
|
if not _get_line_range(nodes_to_ignore).intersection(lines_set):
|
||||||
|
_convert_nodes_to_standalone_comment(nodes_to_ignore, newline=leaf)
|
||||||
|
else:
|
||||||
|
ancestor = furthest_ancestor_with_last_leaf(leaf)
|
||||||
|
# Consider multiple decorators as a whole block, as their
|
||||||
|
# newlines have different behaviors than the rest of the grammar.
|
||||||
|
if (
|
||||||
|
ancestor.type == syms.decorator
|
||||||
|
and ancestor.parent
|
||||||
|
and ancestor.parent.type == syms.decorators
|
||||||
|
):
|
||||||
|
ancestor = ancestor.parent
|
||||||
|
if not _get_line_range(ancestor).intersection(lines_set):
|
||||||
|
_convert_node_to_standalone_comment(ancestor)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_node_to_standalone_comment(node: LN) -> None:
|
||||||
|
"""Convert node to STANDALONE_COMMENT by modifying the tree inline."""
|
||||||
|
parent = node.parent
|
||||||
|
if not parent:
|
||||||
|
return
|
||||||
|
first = first_leaf(node)
|
||||||
|
last = last_leaf(node)
|
||||||
|
if not first or not last:
|
||||||
|
return
|
||||||
|
if first is last:
|
||||||
|
# This can happen on the following edge cases:
|
||||||
|
# 1. A block of `# fmt: off/on` code except the `# fmt: on` is placed
|
||||||
|
# on the end of the last line instead of on a new line.
|
||||||
|
# 2. A single backslash on its own line followed by a comment line.
|
||||||
|
# Ideally we don't want to format them when not requested, but fixing
|
||||||
|
# isn't easy. These cases are also badly formatted code, so it isn't
|
||||||
|
# too bad we reformat them.
|
||||||
|
return
|
||||||
|
# The prefix contains comments and indentation whitespaces. They are
|
||||||
|
# reformatted accordingly to the correct indentation level.
|
||||||
|
# This also means the indentation will be changed on the unchanged lines, and
|
||||||
|
# this is actually required to not break incremental reformatting.
|
||||||
|
prefix = first.prefix
|
||||||
|
first.prefix = ""
|
||||||
|
index = node.remove()
|
||||||
|
if index is not None:
|
||||||
|
# Because of the special handling of multiple decorators, if the decorated
|
||||||
|
# item is a single line then there will be a missing newline between the
|
||||||
|
# decorator and item, so add it back. This doesn't affect any other case
|
||||||
|
# since a decorated item with a newline would hit the earlier suite case
|
||||||
|
# in _convert_unchanged_line_by_line that correctly handles the newlines.
|
||||||
|
if node.type == syms.decorated:
|
||||||
|
# A leaf of type decorated wouldn't make sense, since it should always
|
||||||
|
# have at least the decorator + the decorated item, so if this assert
|
||||||
|
# hits that means there's a problem in the parser.
|
||||||
|
assert isinstance(node, Node)
|
||||||
|
# 1 will always be the correct index since before this function is
|
||||||
|
# called all the decorators are collapsed into a single leaf
|
||||||
|
node.insert_child(1, Leaf(NEWLINE, "\n"))
|
||||||
|
# Remove the '\n', as STANDALONE_COMMENT will have '\n' appended when
|
||||||
|
# generating the formatted code.
|
||||||
|
value = str(node)[:-1]
|
||||||
|
parent.insert_child(
|
||||||
|
index,
|
||||||
|
Leaf(
|
||||||
|
STANDALONE_COMMENT,
|
||||||
|
value,
|
||||||
|
prefix=prefix,
|
||||||
|
fmt_pass_converted_first_leaf=first,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_nodes_to_standalone_comment(nodes: Sequence[LN], *, newline: Leaf) -> None:
|
||||||
|
"""Convert nodes to STANDALONE_COMMENT by modifying the tree inline."""
|
||||||
|
if not nodes:
|
||||||
|
return
|
||||||
|
parent = nodes[0].parent
|
||||||
|
first = first_leaf(nodes[0])
|
||||||
|
if not parent or not first:
|
||||||
|
return
|
||||||
|
prefix = first.prefix
|
||||||
|
first.prefix = ""
|
||||||
|
value = "".join(str(node) for node in nodes)
|
||||||
|
# The prefix comment on the NEWLINE leaf is the trailing comment of the statement.
|
||||||
|
if newline.prefix:
|
||||||
|
value += newline.prefix
|
||||||
|
newline.prefix = ""
|
||||||
|
index = nodes[0].remove()
|
||||||
|
for node in nodes[1:]:
|
||||||
|
node.remove()
|
||||||
|
if index is not None:
|
||||||
|
parent.insert_child(
|
||||||
|
index,
|
||||||
|
Leaf(
|
||||||
|
STANDALONE_COMMENT,
|
||||||
|
value,
|
||||||
|
prefix=prefix,
|
||||||
|
fmt_pass_converted_first_leaf=first,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _leaf_line_end(leaf: Leaf) -> int:
|
||||||
|
"""Returns the line number of the leaf node's last line."""
|
||||||
|
if leaf.type == NEWLINE:
|
||||||
|
return leaf.lineno
|
||||||
|
else:
|
||||||
|
# Leaf nodes like multiline strings can occupy multiple lines.
|
||||||
|
return leaf.lineno + str(leaf).count("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_line_range(node_or_nodes: LN | list[LN]) -> set[int]:
|
||||||
|
"""Returns the line range of this node or list of nodes."""
|
||||||
|
if isinstance(node_or_nodes, list):
|
||||||
|
nodes = node_or_nodes
|
||||||
|
if not nodes:
|
||||||
|
return set()
|
||||||
|
first = first_leaf(nodes[0])
|
||||||
|
last = last_leaf(nodes[-1])
|
||||||
|
if first and last:
|
||||||
|
line_start = first.lineno
|
||||||
|
line_end = _leaf_line_end(last)
|
||||||
|
return set(range(line_start, line_end + 1))
|
||||||
|
else:
|
||||||
|
return set()
|
||||||
|
else:
|
||||||
|
node = node_or_nodes
|
||||||
|
if isinstance(node, Leaf):
|
||||||
|
return set(range(node.lineno, _leaf_line_end(node) + 1))
|
||||||
|
else:
|
||||||
|
first = first_leaf(node)
|
||||||
|
last = last_leaf(node)
|
||||||
|
if first and last:
|
||||||
|
return set(range(first.lineno, _leaf_line_end(last) + 1))
|
||||||
|
else:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _LinesMapping:
|
||||||
|
"""1-based lines mapping from original source to modified source.
|
||||||
|
|
||||||
|
Lines [original_start, original_end] from original source
|
||||||
|
are mapped to [modified_start, modified_end].
|
||||||
|
|
||||||
|
The ranges are inclusive on both ends.
|
||||||
|
"""
|
||||||
|
|
||||||
|
original_start: int
|
||||||
|
original_end: int
|
||||||
|
modified_start: int
|
||||||
|
modified_end: int
|
||||||
|
# Whether this range corresponds to a changed block, or an unchanged block.
|
||||||
|
is_changed_block: bool
|
||||||
|
|
||||||
|
|
||||||
|
def _calculate_lines_mappings(
|
||||||
|
original_source: str,
|
||||||
|
modified_source: str,
|
||||||
|
) -> Sequence[_LinesMapping]:
|
||||||
|
"""Returns a sequence of _LinesMapping by diffing the sources.
|
||||||
|
|
||||||
|
For example, given the following diff:
|
||||||
|
import re
|
||||||
|
- def func(arg1,
|
||||||
|
- arg2, arg3):
|
||||||
|
+ def func(arg1, arg2, arg3):
|
||||||
|
pass
|
||||||
|
It returns the following mappings:
|
||||||
|
original -> modified
|
||||||
|
(1, 1) -> (1, 1), is_changed_block=False (the "import re" line)
|
||||||
|
(2, 3) -> (2, 2), is_changed_block=True (the diff)
|
||||||
|
(4, 4) -> (3, 3), is_changed_block=False (the "pass" line)
|
||||||
|
|
||||||
|
You can think of this visually as if it brings up a side-by-side diff, and tries
|
||||||
|
to map the line ranges from the left side to the right side:
|
||||||
|
|
||||||
|
(1, 1)->(1, 1) 1. import re 1. import re
|
||||||
|
(2, 3)->(2, 2) 2. def func(arg1, 2. def func(arg1, arg2, arg3):
|
||||||
|
3. arg2, arg3):
|
||||||
|
(4, 4)->(3, 3) 4. pass 3. pass
|
||||||
|
|
||||||
|
Args:
|
||||||
|
original_source: the original source.
|
||||||
|
modified_source: the modified source.
|
||||||
|
"""
|
||||||
|
matcher = difflib.SequenceMatcher(
|
||||||
|
None,
|
||||||
|
original_source.splitlines(keepends=True),
|
||||||
|
modified_source.splitlines(keepends=True),
|
||||||
|
)
|
||||||
|
matching_blocks = matcher.get_matching_blocks()
|
||||||
|
lines_mappings: list[_LinesMapping] = []
|
||||||
|
# matching_blocks is a sequence of "same block of code ranges", see
|
||||||
|
# https://docs.python.org/3/library/difflib.html#difflib.SequenceMatcher.get_matching_blocks
|
||||||
|
# Each block corresponds to a _LinesMapping with is_changed_block=False,
|
||||||
|
# and the ranges between two blocks corresponds to a _LinesMapping with
|
||||||
|
# is_changed_block=True,
|
||||||
|
# NOTE: matching_blocks is 0-based, but _LinesMapping is 1-based.
|
||||||
|
for i, block in enumerate(matching_blocks):
|
||||||
|
if i == 0:
|
||||||
|
if block.a != 0 or block.b != 0:
|
||||||
|
lines_mappings.append(
|
||||||
|
_LinesMapping(
|
||||||
|
original_start=1,
|
||||||
|
original_end=block.a,
|
||||||
|
modified_start=1,
|
||||||
|
modified_end=block.b,
|
||||||
|
is_changed_block=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
previous_block = matching_blocks[i - 1]
|
||||||
|
lines_mappings.append(
|
||||||
|
_LinesMapping(
|
||||||
|
original_start=previous_block.a + previous_block.size + 1,
|
||||||
|
original_end=block.a,
|
||||||
|
modified_start=previous_block.b + previous_block.size + 1,
|
||||||
|
modified_end=block.b,
|
||||||
|
is_changed_block=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if i < len(matching_blocks) - 1:
|
||||||
|
lines_mappings.append(
|
||||||
|
_LinesMapping(
|
||||||
|
original_start=block.a + 1,
|
||||||
|
original_end=block.a + block.size,
|
||||||
|
modified_start=block.b + 1,
|
||||||
|
modified_end=block.b + block.size,
|
||||||
|
is_changed_block=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return lines_mappings
|
||||||
|
|
||||||
|
|
||||||
|
def _find_lines_mapping_index(
|
||||||
|
original_line: int,
|
||||||
|
lines_mappings: Sequence[_LinesMapping],
|
||||||
|
start_index: int,
|
||||||
|
) -> int:
|
||||||
|
"""Returns the original index of the lines mappings for the original line."""
|
||||||
|
index = start_index
|
||||||
|
while index < len(lines_mappings):
|
||||||
|
mapping = lines_mappings[index]
|
||||||
|
if mapping.original_start <= original_line <= mapping.original_end:
|
||||||
|
return index
|
||||||
|
index += 1
|
||||||
|
return index
|
||||||
107
.venv_codegen/Lib/site-packages/black/report.py
Normal file
107
.venv_codegen/Lib/site-packages/black/report.py
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
"""
|
||||||
|
Summarize Black runs to users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from click import style
|
||||||
|
|
||||||
|
from black.output import err, out
|
||||||
|
|
||||||
|
|
||||||
|
class Changed(Enum):
|
||||||
|
NO = 0
|
||||||
|
CACHED = 1
|
||||||
|
YES = 2
|
||||||
|
|
||||||
|
|
||||||
|
class NothingChanged(UserWarning):
|
||||||
|
"""Raised when reformatted code is the same as source."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Report:
|
||||||
|
"""Provides a reformatting counter. Can be rendered with `str(report)`."""
|
||||||
|
|
||||||
|
check: bool = False
|
||||||
|
diff: bool = False
|
||||||
|
quiet: bool = False
|
||||||
|
verbose: bool = False
|
||||||
|
change_count: int = 0
|
||||||
|
same_count: int = 0
|
||||||
|
failure_count: int = 0
|
||||||
|
|
||||||
|
def done(self, src: Path, changed: Changed) -> None:
|
||||||
|
"""Increment the counter for successful reformatting. Write out a message."""
|
||||||
|
if changed is Changed.YES:
|
||||||
|
reformatted = "would reformat" if self.check or self.diff else "reformatted"
|
||||||
|
if self.verbose or not self.quiet:
|
||||||
|
out(f"{reformatted} {src}")
|
||||||
|
self.change_count += 1
|
||||||
|
else:
|
||||||
|
if self.verbose:
|
||||||
|
if changed is Changed.NO:
|
||||||
|
msg = f"{src} already well formatted, good job."
|
||||||
|
else:
|
||||||
|
msg = f"{src} wasn't modified on disk since last run."
|
||||||
|
out(msg, bold=False)
|
||||||
|
self.same_count += 1
|
||||||
|
|
||||||
|
def failed(self, src: Path, message: str) -> None:
|
||||||
|
"""Increment the counter for failed reformatting. Write out a message."""
|
||||||
|
err(f"error: cannot format {src}: {message}")
|
||||||
|
self.failure_count += 1
|
||||||
|
|
||||||
|
def path_ignored(self, path: Path, message: str) -> None:
|
||||||
|
if self.verbose:
|
||||||
|
out(f"{path} ignored: {message}", bold=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def return_code(self) -> int:
|
||||||
|
"""Return the exit code that the app should use.
|
||||||
|
|
||||||
|
This considers the current state of changed files and failures:
|
||||||
|
- if there were any failures, return 123;
|
||||||
|
- if any files were changed and --check is being used, return 1;
|
||||||
|
- otherwise return 0.
|
||||||
|
"""
|
||||||
|
# According to http://tldp.org/LDP/abs/html/exitcodes.html starting with
|
||||||
|
# 126 we have special return codes reserved by the shell.
|
||||||
|
if self.failure_count:
|
||||||
|
return 123
|
||||||
|
|
||||||
|
elif self.change_count and self.check:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""Render a color report of the current state.
|
||||||
|
|
||||||
|
Use `click.unstyle` to remove colors.
|
||||||
|
"""
|
||||||
|
if self.check or self.diff:
|
||||||
|
reformatted = "would be reformatted"
|
||||||
|
unchanged = "would be left unchanged"
|
||||||
|
failed = "would fail to reformat"
|
||||||
|
else:
|
||||||
|
reformatted = "reformatted"
|
||||||
|
unchanged = "left unchanged"
|
||||||
|
failed = "failed to reformat"
|
||||||
|
report = []
|
||||||
|
if self.change_count:
|
||||||
|
s = "s" if self.change_count > 1 else ""
|
||||||
|
report.append(
|
||||||
|
style(f"{self.change_count} file{s} ", bold=True, fg="blue")
|
||||||
|
+ style(f"{reformatted}", bold=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.same_count:
|
||||||
|
s = "s" if self.same_count > 1 else ""
|
||||||
|
report.append(style(f"{self.same_count} file{s} ", fg="blue") + unchanged)
|
||||||
|
if self.failure_count:
|
||||||
|
s = "s" if self.failure_count > 1 else ""
|
||||||
|
report.append(style(f"{self.failure_count} file{s} {failed}", fg="red"))
|
||||||
|
return ", ".join(report) + "."
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://github.com/psf/black/blob/main/src/black/resources/black.schema.json",
|
||||||
|
"$comment": "tool.black table in pyproject.toml",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Format the code passed in as a string."
|
||||||
|
},
|
||||||
|
"line-length": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "How many characters per line to allow.",
|
||||||
|
"default": 88
|
||||||
|
},
|
||||||
|
"target-version": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"py33",
|
||||||
|
"py34",
|
||||||
|
"py35",
|
||||||
|
"py36",
|
||||||
|
"py37",
|
||||||
|
"py38",
|
||||||
|
"py39",
|
||||||
|
"py310",
|
||||||
|
"py311",
|
||||||
|
"py312",
|
||||||
|
"py313",
|
||||||
|
"py314"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Python versions that should be supported by Black's output. You should include all versions that your code supports. By default, Black will infer target versions from the project metadata in pyproject.toml. If this does not yield conclusive results, Black will use per-file auto-detection."
|
||||||
|
},
|
||||||
|
"pyi": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Format all input files like typing stubs regardless of file extension. This is useful when piping source on standard input.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"ipynb": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Format all input files like Jupyter Notebooks regardless of file extension. This is useful when piping source on standard input.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"python-cell-magics": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "When processing Jupyter Notebooks, add the given magic to the list of known python-magics (capture, prun, pypy, python, python3, time, timeit). Useful for formatting cells with custom python magics."
|
||||||
|
},
|
||||||
|
"skip-source-first-line": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip the first line of the source code.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skip-string-normalization": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Don't normalize string quotes or prefixes.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skip-magic-trailing-comma": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Don't use trailing commas as a reason to split lines.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable potentially disruptive style changes that may be added to Black's main functionality in the next major release.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"unstable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable potentially disruptive style changes that have known bugs or are not currently expected to make it into the stable style Black's next major release. Implies --preview.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"enable-unstable-feature": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"string_processing",
|
||||||
|
"hug_parens_with_braces_and_square_brackets",
|
||||||
|
"wrap_comprehension_in",
|
||||||
|
"simplify_power_operator_hugging",
|
||||||
|
"wrap_long_dict_values_in_parens",
|
||||||
|
"fix_if_guard_explosion_in_case_statement"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Don't write the files back, just return the status. Return code 0 means nothing would change. Return code 1 means some files would be reformatted. Return code 123 means there was an internal error.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"diff": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Don't write the files back, just output a diff to indicate what changes Black would've made. They are printed to stdout so capturing them is simple.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Show (or do not show) colored diff. Only applies when --diff is given.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"fast": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "By default, Black performs an AST safety check after formatting your code. The --fast flag turns off this check and the --safe flag explicitly enables it. [default: --safe]",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"required-version": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Require a specific version of Black to be running. This is useful for ensuring that all contributors to your project are using the same version, because different versions of Black may format code a little differently. This option can be set in a configuration file for consistent results across environments."
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A regular expression that matches files and directories that should be excluded on recursive searches. An empty value means no paths are excluded. Use forward slashes for directories on all platforms (Windows, too). By default, Black also ignores all paths listed in .gitignore. Changing this value will override all default exclusions. [default: /(\\.direnv|\\.eggs|\\.git|\\.hg|\\.ipynb_checkpoints|\\.mypy_cache|\\.nox|\\.pytest_cache|\\.ruff_cache|\\.tox|\\.svn|\\.venv|\\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/]"
|
||||||
|
},
|
||||||
|
"extend-exclude": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Like --exclude, but adds additional files and directories on top of the default values instead of overriding them."
|
||||||
|
},
|
||||||
|
"force-exclude": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Like --exclude, but files and directories matching this regex will be excluded even when they are passed explicitly as arguments. This is useful when invoking Black programmatically on changed files, such as in a pre-commit hook or editor plugin."
|
||||||
|
},
|
||||||
|
"include": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A regular expression that matches files and directories that should be included on recursive searches. An empty value means all files are included regardless of the name. Use forward slashes for directories on all platforms (Windows, too). Overrides all exclusions, including from .gitignore and command line options.",
|
||||||
|
"default": "(\\.pyi?|\\.ipynb)$"
|
||||||
|
},
|
||||||
|
"workers": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "When Black formats multiple files, it may use a process pool to speed up formatting. This option controls the number of parallel workers. This can also be specified via the BLACK_NUM_WORKERS environment variable. Defaults to the number of CPUs in the system."
|
||||||
|
},
|
||||||
|
"quiet": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Stop emitting all non-critical output. Error messages will still be emitted (which can silenced by 2>/dev/null).",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"verbose": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Emit messages about files that were not changed or were ignored due to exclusion patterns. If Black is using a configuration file, a message detailing which one it is using will be emitted.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"no-cache": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Skip reading and writing the cache, forcing Black to reformat all included files.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
.venv_codegen/Lib/site-packages/black/rusty.py
Normal file
28
.venv_codegen/Lib/site-packages/black/rusty.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
"""An error-handling model influenced by that used by the Rust programming language
|
||||||
|
|
||||||
|
See https://doc.rust-lang.org/book/ch09-00-error-handling.html.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Generic, TypeVar, Union
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
E = TypeVar("E", bound=Exception)
|
||||||
|
|
||||||
|
|
||||||
|
class Ok(Generic[T]):
|
||||||
|
def __init__(self, value: T) -> None:
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
def ok(self) -> T:
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
|
||||||
|
class Err(Generic[E]):
|
||||||
|
def __init__(self, e: E) -> None:
|
||||||
|
self._e = e
|
||||||
|
|
||||||
|
def err(self) -> E:
|
||||||
|
return self._e
|
||||||
|
|
||||||
|
|
||||||
|
Result = Union[Ok[T], Err[E]]
|
||||||
15
.venv_codegen/Lib/site-packages/black/schema.py
Normal file
15
.venv_codegen/Lib/site-packages/black/schema.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import importlib.resources
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def get_schema(tool_name: str = "black") -> Any:
|
||||||
|
"""Get the stored complete schema for black's settings."""
|
||||||
|
assert tool_name == "black", "Only black is supported."
|
||||||
|
|
||||||
|
pkg = "black.resources"
|
||||||
|
fname = "black.schema.json"
|
||||||
|
|
||||||
|
schema = importlib.resources.files(pkg).joinpath(fname)
|
||||||
|
with schema.open(encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
391
.venv_codegen/Lib/site-packages/black/strings.py
Normal file
391
.venv_codegen/Lib/site-packages/black/strings.py
Normal file
|
|
@ -0,0 +1,391 @@
|
||||||
|
"""
|
||||||
|
Simple formatting on strings. Further string formatting code is in trans.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from functools import lru_cache
|
||||||
|
from re import Match, Pattern
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from black._width_table import WIDTH_TABLE
|
||||||
|
from blib2to3.pytree import Leaf
|
||||||
|
|
||||||
|
STRING_PREFIX_CHARS: Final = "fturbFTURB" # All possible string prefix characters.
|
||||||
|
STRING_PREFIX_RE: Final = re.compile(
|
||||||
|
r"^([" + STRING_PREFIX_CHARS + r"]*)(.*)$", re.DOTALL
|
||||||
|
)
|
||||||
|
UNICODE_ESCAPE_RE: Final = re.compile(
|
||||||
|
r"(?P<backslashes>\\+)(?P<body>"
|
||||||
|
r"(u(?P<u>[a-fA-F0-9]{4}))" # Character with 16-bit hex value xxxx
|
||||||
|
r"|(U(?P<U>[a-fA-F0-9]{8}))" # Character with 32-bit hex value xxxxxxxx
|
||||||
|
r"|(x(?P<x>[a-fA-F0-9]{2}))" # Character with hex value hh
|
||||||
|
r"|(N\{(?P<N>[a-zA-Z0-9 \-]{2,})\})" # Character named name in the Unicode database
|
||||||
|
r")",
|
||||||
|
re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:
|
||||||
|
"""Replace `regex` with `replacement` twice on `original`.
|
||||||
|
|
||||||
|
This is used by string normalization to perform replaces on
|
||||||
|
overlapping matches.
|
||||||
|
"""
|
||||||
|
return regex.sub(replacement, regex.sub(replacement, original))
|
||||||
|
|
||||||
|
|
||||||
|
def has_triple_quotes(string: str) -> bool:
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
True iff @string starts with three quotation characters.
|
||||||
|
"""
|
||||||
|
raw_string = string.lstrip(STRING_PREFIX_CHARS)
|
||||||
|
return raw_string[:3] in {'"""', "'''"}
|
||||||
|
|
||||||
|
|
||||||
|
def lines_with_leading_tabs_expanded(s: str) -> list[str]:
|
||||||
|
"""
|
||||||
|
Splits string into lines and expands only leading tabs (following the normal
|
||||||
|
Python rules)
|
||||||
|
"""
|
||||||
|
lines = []
|
||||||
|
for line in s.splitlines():
|
||||||
|
stripped_line = line.lstrip()
|
||||||
|
if not stripped_line or stripped_line == line:
|
||||||
|
lines.append(line)
|
||||||
|
else:
|
||||||
|
prefix_length = len(line) - len(stripped_line)
|
||||||
|
prefix = line[:prefix_length].expandtabs()
|
||||||
|
lines.append(prefix + stripped_line)
|
||||||
|
if s.endswith("\n"):
|
||||||
|
lines.append("")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def fix_multiline_docstring(docstring: str, prefix: str) -> str:
|
||||||
|
# https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation
|
||||||
|
assert docstring, "INTERNAL ERROR: Multiline docstrings cannot be empty"
|
||||||
|
lines = lines_with_leading_tabs_expanded(docstring)
|
||||||
|
# Determine minimum indentation (first line doesn't count):
|
||||||
|
indent = sys.maxsize
|
||||||
|
for line in lines[1:]:
|
||||||
|
stripped = line.lstrip()
|
||||||
|
if stripped:
|
||||||
|
indent = min(indent, len(line) - len(stripped))
|
||||||
|
# Remove indentation (first line is special):
|
||||||
|
trimmed = [lines[0].strip()]
|
||||||
|
if indent < sys.maxsize:
|
||||||
|
last_line_idx = len(lines) - 2
|
||||||
|
for i, line in enumerate(lines[1:]):
|
||||||
|
stripped_line = line[indent:].rstrip()
|
||||||
|
if stripped_line or i == last_line_idx:
|
||||||
|
trimmed.append(prefix + stripped_line)
|
||||||
|
else:
|
||||||
|
trimmed.append("")
|
||||||
|
return "\n".join(trimmed)
|
||||||
|
|
||||||
|
|
||||||
|
def get_string_prefix(string: str) -> str:
|
||||||
|
"""
|
||||||
|
Pre-conditions:
|
||||||
|
* assert_is_leaf_string(@string)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
@string's prefix (e.g. '', 'r', 'f', or 'rf').
|
||||||
|
"""
|
||||||
|
assert_is_leaf_string(string)
|
||||||
|
|
||||||
|
prefix = []
|
||||||
|
for char in string:
|
||||||
|
if char in STRING_PREFIX_CHARS:
|
||||||
|
prefix.append(char)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return "".join(prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_is_leaf_string(string: str) -> None:
|
||||||
|
"""
|
||||||
|
Checks the pre-condition that @string has the format that you would expect
|
||||||
|
of `leaf.value` where `leaf` is some Leaf such that `leaf.type ==
|
||||||
|
token.STRING`. A more precise description of the pre-conditions that are
|
||||||
|
checked are listed below.
|
||||||
|
|
||||||
|
Pre-conditions:
|
||||||
|
* @string starts with either ', ", <prefix>', or <prefix>" where
|
||||||
|
`set(<prefix>)` is some subset of `set(STRING_PREFIX_CHARS)`.
|
||||||
|
* @string ends with a quote character (' or ").
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError(...) if the pre-conditions listed above are not
|
||||||
|
satisfied.
|
||||||
|
"""
|
||||||
|
dquote_idx = string.find('"')
|
||||||
|
squote_idx = string.find("'")
|
||||||
|
if -1 in [dquote_idx, squote_idx]:
|
||||||
|
quote_idx = max(dquote_idx, squote_idx)
|
||||||
|
else:
|
||||||
|
quote_idx = min(squote_idx, dquote_idx)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
0 <= quote_idx < len(string) - 1
|
||||||
|
), f"{string!r} is missing a starting quote character (' or \")."
|
||||||
|
assert string[-1] in (
|
||||||
|
"'",
|
||||||
|
'"',
|
||||||
|
), f"{string!r} is missing an ending quote character (' or \")."
|
||||||
|
assert set(string[:quote_idx]).issubset(
|
||||||
|
set(STRING_PREFIX_CHARS)
|
||||||
|
), f"{set(string[:quote_idx])} is NOT a subset of {set(STRING_PREFIX_CHARS)}."
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_string_prefix(s: str) -> str:
|
||||||
|
"""Make all string prefixes lowercase."""
|
||||||
|
match = STRING_PREFIX_RE.match(s)
|
||||||
|
assert match is not None, f"failed to match string {s!r}"
|
||||||
|
orig_prefix = match.group(1)
|
||||||
|
new_prefix = (
|
||||||
|
orig_prefix.replace("F", "f")
|
||||||
|
.replace("B", "b")
|
||||||
|
.replace("U", "")
|
||||||
|
.replace("u", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Python syntax guarantees max 2 prefixes and that one of them is "r"
|
||||||
|
if len(new_prefix) == 2 and new_prefix[0].lower() != "r":
|
||||||
|
new_prefix = new_prefix[::-1]
|
||||||
|
return f"{new_prefix}{match.group(2)}"
|
||||||
|
|
||||||
|
|
||||||
|
# Re(gex) does actually cache patterns internally but this still improves
|
||||||
|
# performance on a long list literal of strings by 5-9% since lru_cache's
|
||||||
|
# caching overhead is much lower.
|
||||||
|
@lru_cache(maxsize=64)
|
||||||
|
def _cached_compile(pattern: str) -> Pattern[str]:
|
||||||
|
return re.compile(pattern)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_string_quotes(s: str) -> str:
|
||||||
|
"""Prefer double quotes but only if it doesn't cause more escaping.
|
||||||
|
|
||||||
|
Adds or removes backslashes as appropriate.
|
||||||
|
"""
|
||||||
|
value = s.lstrip(STRING_PREFIX_CHARS)
|
||||||
|
if value[:3] == '"""':
|
||||||
|
return s
|
||||||
|
|
||||||
|
elif value[:3] == "'''":
|
||||||
|
orig_quote = "'''"
|
||||||
|
new_quote = '"""'
|
||||||
|
elif value[0] == '"':
|
||||||
|
orig_quote = '"'
|
||||||
|
new_quote = "'"
|
||||||
|
else:
|
||||||
|
orig_quote = "'"
|
||||||
|
new_quote = '"'
|
||||||
|
first_quote_pos = s.find(orig_quote)
|
||||||
|
assert first_quote_pos != -1, f"INTERNAL ERROR: Malformed string {s!r}"
|
||||||
|
|
||||||
|
prefix = s[:first_quote_pos]
|
||||||
|
unescaped_new_quote = _cached_compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
|
||||||
|
escaped_new_quote = _cached_compile(rf"([^\\]|^)\\((?:\\\\)*){new_quote}")
|
||||||
|
escaped_orig_quote = _cached_compile(rf"([^\\]|^)\\((?:\\\\)*){orig_quote}")
|
||||||
|
body = s[first_quote_pos + len(orig_quote) : -len(orig_quote)]
|
||||||
|
if "r" in prefix.casefold():
|
||||||
|
if unescaped_new_quote.search(body):
|
||||||
|
# There's at least one unescaped new_quote in this raw string
|
||||||
|
# so converting is impossible
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Do not introduce or remove backslashes in raw strings
|
||||||
|
new_body = body
|
||||||
|
else:
|
||||||
|
# remove unnecessary escapes
|
||||||
|
new_body = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", body)
|
||||||
|
if body != new_body:
|
||||||
|
# Consider the string without unnecessary escapes as the original
|
||||||
|
body = new_body
|
||||||
|
s = f"{prefix}{orig_quote}{body}{orig_quote}"
|
||||||
|
new_body = sub_twice(escaped_orig_quote, rf"\1\2{orig_quote}", new_body)
|
||||||
|
new_body = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_body)
|
||||||
|
|
||||||
|
if "f" in prefix.casefold():
|
||||||
|
matches = re.findall(
|
||||||
|
r"""
|
||||||
|
(?:(?<!\{)|^)\{ # start of the string or a non-{ followed by a single {
|
||||||
|
([^{].*?) # contents of the brackets except if begins with {{
|
||||||
|
\}(?:(?!\})|$) # A } followed by end of the string or a non-}
|
||||||
|
""",
|
||||||
|
new_body,
|
||||||
|
re.VERBOSE,
|
||||||
|
)
|
||||||
|
for m in matches:
|
||||||
|
if "\\" in str(m):
|
||||||
|
# Do not introduce backslashes in interpolated expressions
|
||||||
|
return s
|
||||||
|
|
||||||
|
if new_quote == '"""' and new_body[-1:] == '"':
|
||||||
|
# edge case:
|
||||||
|
new_body = new_body[:-1] + '\\"'
|
||||||
|
orig_escape_count = body.count("\\")
|
||||||
|
new_escape_count = new_body.count("\\")
|
||||||
|
if new_escape_count > orig_escape_count:
|
||||||
|
return s # Do not introduce more escaping
|
||||||
|
|
||||||
|
if new_escape_count == orig_escape_count and orig_quote == '"':
|
||||||
|
return s # Prefer double quotes
|
||||||
|
|
||||||
|
return f"{prefix}{new_quote}{new_body}{new_quote}"
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_fstring_quotes(
|
||||||
|
quote: str,
|
||||||
|
middles: list[Leaf],
|
||||||
|
is_raw_fstring: bool,
|
||||||
|
) -> tuple[list[Leaf], str]:
|
||||||
|
"""Prefer double quotes but only if it doesn't cause more escaping.
|
||||||
|
|
||||||
|
Adds or removes backslashes as appropriate.
|
||||||
|
"""
|
||||||
|
if quote == '"""':
|
||||||
|
return middles, quote
|
||||||
|
|
||||||
|
elif quote == "'''":
|
||||||
|
new_quote = '"""'
|
||||||
|
elif quote == '"':
|
||||||
|
new_quote = "'"
|
||||||
|
else:
|
||||||
|
new_quote = '"'
|
||||||
|
|
||||||
|
unescaped_new_quote = _cached_compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
|
||||||
|
escaped_new_quote = _cached_compile(rf"([^\\]|^)\\((?:\\\\)*){new_quote}")
|
||||||
|
escaped_orig_quote = _cached_compile(rf"([^\\]|^)\\((?:\\\\)*){quote}")
|
||||||
|
if is_raw_fstring:
|
||||||
|
for middle in middles:
|
||||||
|
if unescaped_new_quote.search(middle.value):
|
||||||
|
# There's at least one unescaped new_quote in this raw string
|
||||||
|
# so converting is impossible
|
||||||
|
return middles, quote
|
||||||
|
|
||||||
|
# Do not introduce or remove backslashes in raw strings, just use double quote
|
||||||
|
return middles, '"'
|
||||||
|
|
||||||
|
new_segments = []
|
||||||
|
for middle in middles:
|
||||||
|
segment = middle.value
|
||||||
|
# remove unnecessary escapes
|
||||||
|
new_segment = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", segment)
|
||||||
|
if segment != new_segment:
|
||||||
|
# Consider the string without unnecessary escapes as the original
|
||||||
|
middle.value = new_segment
|
||||||
|
|
||||||
|
new_segment = sub_twice(escaped_orig_quote, rf"\1\2{quote}", new_segment)
|
||||||
|
new_segment = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_segment)
|
||||||
|
new_segments.append(new_segment)
|
||||||
|
|
||||||
|
if new_quote == '"""' and new_segments[-1].endswith('"'):
|
||||||
|
# edge case:
|
||||||
|
new_segments[-1] = new_segments[-1][:-1] + '\\"'
|
||||||
|
|
||||||
|
orig_escape_count = 0
|
||||||
|
new_escape_count = 0
|
||||||
|
for middle, new_segment in zip(middles, new_segments, strict=True):
|
||||||
|
orig_escape_count += middle.value.count("\\")
|
||||||
|
new_escape_count += new_segment.count("\\")
|
||||||
|
|
||||||
|
if new_escape_count > orig_escape_count:
|
||||||
|
return middles, quote # Do not introduce more escaping
|
||||||
|
|
||||||
|
if new_escape_count == orig_escape_count and quote == '"':
|
||||||
|
return middles, quote # Prefer double quotes
|
||||||
|
|
||||||
|
for middle, new_segment in zip(middles, new_segments, strict=True):
|
||||||
|
middle.value = new_segment
|
||||||
|
|
||||||
|
return middles, new_quote
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_unicode_escape_sequences(leaf: Leaf) -> None:
|
||||||
|
"""Replace hex codes in Unicode escape sequences with lowercase representation."""
|
||||||
|
text = leaf.value
|
||||||
|
prefix = get_string_prefix(text)
|
||||||
|
if "r" in prefix.lower():
|
||||||
|
return
|
||||||
|
|
||||||
|
def replace(m: Match[str]) -> str:
|
||||||
|
groups = m.groupdict()
|
||||||
|
back_slashes = groups["backslashes"]
|
||||||
|
|
||||||
|
if len(back_slashes) % 2 == 0:
|
||||||
|
return back_slashes + groups["body"]
|
||||||
|
|
||||||
|
if groups["u"]:
|
||||||
|
# \u
|
||||||
|
return back_slashes + "u" + groups["u"].lower()
|
||||||
|
elif groups["U"]:
|
||||||
|
# \U
|
||||||
|
return back_slashes + "U" + groups["U"].lower()
|
||||||
|
elif groups["x"]:
|
||||||
|
# \x
|
||||||
|
return back_slashes + "x" + groups["x"].lower()
|
||||||
|
else:
|
||||||
|
assert groups["N"], f"Unexpected match: {m}"
|
||||||
|
# \N{}
|
||||||
|
return back_slashes + "N{" + groups["N"].upper() + "}"
|
||||||
|
|
||||||
|
leaf.value = re.sub(UNICODE_ESCAPE_RE, replace, text)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=4096)
|
||||||
|
def char_width(char: str) -> int:
|
||||||
|
"""Return the width of a single character as it would be displayed in a
|
||||||
|
terminal or editor (which respects Unicode East Asian Width).
|
||||||
|
|
||||||
|
Full width characters are counted as 2, while half width characters are
|
||||||
|
counted as 1. Also control characters are counted as 0.
|
||||||
|
"""
|
||||||
|
table = WIDTH_TABLE
|
||||||
|
codepoint = ord(char)
|
||||||
|
highest = len(table) - 1
|
||||||
|
lowest = 0
|
||||||
|
idx = highest // 2
|
||||||
|
while True:
|
||||||
|
start_codepoint, end_codepoint, width = table[idx]
|
||||||
|
if codepoint < start_codepoint:
|
||||||
|
highest = idx - 1
|
||||||
|
elif codepoint > end_codepoint:
|
||||||
|
lowest = idx + 1
|
||||||
|
else:
|
||||||
|
return 0 if width < 0 else width
|
||||||
|
if highest < lowest:
|
||||||
|
break
|
||||||
|
idx = (highest + lowest) // 2
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def str_width(line_str: str) -> int:
|
||||||
|
"""Return the width of `line_str` as it would be displayed in a terminal
|
||||||
|
or editor (which respects Unicode East Asian Width).
|
||||||
|
|
||||||
|
You could utilize this function to determine, for example, if a string
|
||||||
|
is too wide to display in a terminal or editor.
|
||||||
|
"""
|
||||||
|
if line_str.isascii():
|
||||||
|
# Fast path for a line consisting of only ASCII characters
|
||||||
|
return len(line_str)
|
||||||
|
return sum(map(char_width, line_str))
|
||||||
|
|
||||||
|
|
||||||
|
def count_chars_in_width(line_str: str, max_width: int) -> int:
|
||||||
|
"""Count the number of characters in `line_str` that would fit in a
|
||||||
|
terminal or editor of `max_width` (which respects Unicode East Asian
|
||||||
|
Width).
|
||||||
|
"""
|
||||||
|
total_width = 0
|
||||||
|
for i, char in enumerate(line_str):
|
||||||
|
width = char_width(char)
|
||||||
|
if width + total_width > max_width:
|
||||||
|
return i
|
||||||
|
total_width += width
|
||||||
|
return len(line_str)
|
||||||
2560
.venv_codegen/Lib/site-packages/black/trans.py
Normal file
2560
.venv_codegen/Lib/site-packages/black/trans.py
Normal file
File diff suppressed because it is too large
Load diff
334
.venv_codegen/Lib/site-packages/blackd/__init__.py
Normal file
334
.venv_codegen/Lib/site-packages/blackd/__init__.py
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from concurrent.futures import Executor, ProcessPoolExecutor
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from functools import cache, partial
|
||||||
|
from multiprocessing import freeze_support
|
||||||
|
|
||||||
|
try:
|
||||||
|
from aiohttp import web
|
||||||
|
from multidict import MultiMapping
|
||||||
|
|
||||||
|
from .middlewares import cors
|
||||||
|
except ImportError as ie:
|
||||||
|
raise ImportError(
|
||||||
|
f"aiohttp dependency is not installed: {ie}. "
|
||||||
|
+ "Please re-install black with the '[d]' extra install "
|
||||||
|
+ "to obtain aiohttp_cors: `pip install black[d]`"
|
||||||
|
) from None
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
import black
|
||||||
|
from _black_version import version as __version__
|
||||||
|
from black.concurrency import maybe_use_uvloop
|
||||||
|
|
||||||
|
# This is used internally by tests to shut down the server prematurely
|
||||||
|
_stop_signal = asyncio.Event()
|
||||||
|
|
||||||
|
# Request headers
|
||||||
|
PROTOCOL_VERSION_HEADER = "X-Protocol-Version"
|
||||||
|
LINE_LENGTH_HEADER = "X-Line-Length"
|
||||||
|
PYTHON_VARIANT_HEADER = "X-Python-Variant"
|
||||||
|
SKIP_SOURCE_FIRST_LINE = "X-Skip-Source-First-Line"
|
||||||
|
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
|
||||||
|
SKIP_MAGIC_TRAILING_COMMA = "X-Skip-Magic-Trailing-Comma"
|
||||||
|
PREVIEW = "X-Preview"
|
||||||
|
UNSTABLE = "X-Unstable"
|
||||||
|
ENABLE_UNSTABLE_FEATURE = "X-Enable-Unstable-Feature"
|
||||||
|
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
|
||||||
|
DIFF_HEADER = "X-Diff"
|
||||||
|
|
||||||
|
BLACK_HEADERS = [
|
||||||
|
PROTOCOL_VERSION_HEADER,
|
||||||
|
LINE_LENGTH_HEADER,
|
||||||
|
PYTHON_VARIANT_HEADER,
|
||||||
|
SKIP_SOURCE_FIRST_LINE,
|
||||||
|
SKIP_STRING_NORMALIZATION_HEADER,
|
||||||
|
SKIP_MAGIC_TRAILING_COMMA,
|
||||||
|
PREVIEW,
|
||||||
|
UNSTABLE,
|
||||||
|
ENABLE_UNSTABLE_FEATURE,
|
||||||
|
FAST_OR_SAFE_HEADER,
|
||||||
|
DIFF_HEADER,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Response headers
|
||||||
|
BLACK_VERSION_HEADER = "X-Black-Version"
|
||||||
|
DEFAULT_MAX_BODY_SIZE = 5 * 1024 * 1024
|
||||||
|
DEFAULT_WORKERS = os.cpu_count() or 1
|
||||||
|
|
||||||
|
|
||||||
|
class HeaderError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidVariantHeader(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
|
||||||
|
@click.option(
|
||||||
|
"--bind-host",
|
||||||
|
type=str,
|
||||||
|
help="Address to bind the server to.",
|
||||||
|
default="localhost",
|
||||||
|
show_default=True,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--bind-port", type=int, help="Port to listen on", default=45484, show_default=True
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--cors-allow-origin",
|
||||||
|
"cors_allow_origins",
|
||||||
|
multiple=True,
|
||||||
|
help="Origin allowed to access blackd over CORS. Can be passed multiple times.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--max-body-size",
|
||||||
|
type=click.IntRange(min=1),
|
||||||
|
default=DEFAULT_MAX_BODY_SIZE,
|
||||||
|
show_default=True,
|
||||||
|
help="Maximum request body size in bytes.",
|
||||||
|
)
|
||||||
|
@click.version_option(version=black.__version__)
|
||||||
|
def main(
|
||||||
|
bind_host: str,
|
||||||
|
bind_port: int,
|
||||||
|
cors_allow_origins: tuple[str, ...],
|
||||||
|
max_body_size: int,
|
||||||
|
) -> None:
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
app = make_app(cors_allow_origins=cors_allow_origins, max_body_size=max_body_size)
|
||||||
|
ver = black.__version__
|
||||||
|
black.out(f"blackd version {ver} listening on {bind_host} port {bind_port}")
|
||||||
|
loop = maybe_use_uvloop()
|
||||||
|
try:
|
||||||
|
web.run_app(
|
||||||
|
app,
|
||||||
|
host=bind_host,
|
||||||
|
port=bind_port,
|
||||||
|
handle_signals=True,
|
||||||
|
print=None,
|
||||||
|
loop=loop,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if not loop.is_closed():
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def executor() -> Executor:
|
||||||
|
return ProcessPoolExecutor(max_workers=DEFAULT_WORKERS)
|
||||||
|
|
||||||
|
|
||||||
|
def make_app(
|
||||||
|
*,
|
||||||
|
cors_allow_origins: tuple[str, ...] = (),
|
||||||
|
max_body_size: int = DEFAULT_MAX_BODY_SIZE,
|
||||||
|
) -> web.Application:
|
||||||
|
app = web.Application(
|
||||||
|
client_max_size=max_body_size,
|
||||||
|
middlewares=[
|
||||||
|
cors(
|
||||||
|
allow_headers=(*BLACK_HEADERS, "Content-Type"),
|
||||||
|
allow_origins=frozenset(cors_allow_origins),
|
||||||
|
expose_headers=(BLACK_VERSION_HEADER,),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
app.add_routes([
|
||||||
|
web.post(
|
||||||
|
"/",
|
||||||
|
partial(
|
||||||
|
handle,
|
||||||
|
executor=executor(),
|
||||||
|
executor_semaphore=asyncio.BoundedSemaphore(DEFAULT_WORKERS),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
])
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
async def handle(
|
||||||
|
request: web.Request,
|
||||||
|
executor: Executor,
|
||||||
|
executor_semaphore: asyncio.BoundedSemaphore,
|
||||||
|
) -> web.Response:
|
||||||
|
headers = {BLACK_VERSION_HEADER: __version__}
|
||||||
|
try:
|
||||||
|
if request.headers.get(PROTOCOL_VERSION_HEADER, "1") != "1":
|
||||||
|
return web.Response(
|
||||||
|
status=501, text="This server only supports protocol version 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
fast = False
|
||||||
|
if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast":
|
||||||
|
fast = True
|
||||||
|
try:
|
||||||
|
mode = parse_mode(request.headers)
|
||||||
|
except HeaderError as e:
|
||||||
|
return web.Response(status=400, text=e.args[0])
|
||||||
|
req_bytes = await request.read()
|
||||||
|
charset = request.charset if request.charset is not None else "utf8"
|
||||||
|
req_str = req_bytes.decode(charset)
|
||||||
|
then = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
header = ""
|
||||||
|
if mode.skip_source_first_line:
|
||||||
|
first_newline_position: int = req_str.find("\n") + 1
|
||||||
|
header = req_str[:first_newline_position]
|
||||||
|
req_str = req_str[first_newline_position:]
|
||||||
|
|
||||||
|
only_diff = bool(request.headers.get(DIFF_HEADER, False))
|
||||||
|
formatted_str = await format_code(
|
||||||
|
req_str=req_str,
|
||||||
|
fast=fast,
|
||||||
|
mode=mode,
|
||||||
|
then=then,
|
||||||
|
only_diff=only_diff,
|
||||||
|
executor=executor,
|
||||||
|
executor_semaphore=executor_semaphore,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Put the source first line back
|
||||||
|
req_str = header + req_str
|
||||||
|
formatted_str = header + formatted_str
|
||||||
|
|
||||||
|
return web.Response(
|
||||||
|
content_type=request.content_type,
|
||||||
|
charset=charset,
|
||||||
|
headers=headers,
|
||||||
|
text=formatted_str,
|
||||||
|
)
|
||||||
|
except black.NothingChanged:
|
||||||
|
return web.Response(status=204, headers=headers)
|
||||||
|
except black.InvalidInput as e:
|
||||||
|
return web.Response(status=400, headers=headers, text=str(e))
|
||||||
|
except web.HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("Exception during handling a request")
|
||||||
|
return web.Response(status=500, headers=headers, text=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
async def format_code(
|
||||||
|
*,
|
||||||
|
req_str: str,
|
||||||
|
fast: bool,
|
||||||
|
mode: black.FileMode,
|
||||||
|
then: datetime,
|
||||||
|
only_diff: bool,
|
||||||
|
executor: Executor,
|
||||||
|
executor_semaphore: asyncio.BoundedSemaphore,
|
||||||
|
) -> str:
|
||||||
|
async with executor_semaphore:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
formatted_str = await loop.run_in_executor(
|
||||||
|
executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not only_diff:
|
||||||
|
return formatted_str
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
src_name = f"In\t{then}"
|
||||||
|
dst_name = f"Out\t{now}"
|
||||||
|
return await loop.run_in_executor(
|
||||||
|
executor,
|
||||||
|
partial(black.diff, req_str, formatted_str, src_name, dst_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_mode(headers: MultiMapping[str]) -> black.Mode:
|
||||||
|
try:
|
||||||
|
line_length = int(headers.get(LINE_LENGTH_HEADER, black.DEFAULT_LINE_LENGTH))
|
||||||
|
except ValueError:
|
||||||
|
raise HeaderError("Invalid line length header value") from None
|
||||||
|
|
||||||
|
if PYTHON_VARIANT_HEADER in headers:
|
||||||
|
value = headers[PYTHON_VARIANT_HEADER]
|
||||||
|
try:
|
||||||
|
pyi, versions = parse_python_variant_header(value)
|
||||||
|
except InvalidVariantHeader as e:
|
||||||
|
raise HeaderError(
|
||||||
|
f"Invalid value for {PYTHON_VARIANT_HEADER}: {e.args[0]}",
|
||||||
|
) from None
|
||||||
|
else:
|
||||||
|
pyi = False
|
||||||
|
versions = set()
|
||||||
|
|
||||||
|
skip_string_normalization = bool(
|
||||||
|
headers.get(SKIP_STRING_NORMALIZATION_HEADER, False)
|
||||||
|
)
|
||||||
|
skip_magic_trailing_comma = bool(headers.get(SKIP_MAGIC_TRAILING_COMMA, False))
|
||||||
|
skip_source_first_line = bool(headers.get(SKIP_SOURCE_FIRST_LINE, False))
|
||||||
|
|
||||||
|
preview = bool(headers.get(PREVIEW, False))
|
||||||
|
unstable = bool(headers.get(UNSTABLE, False))
|
||||||
|
enable_features: set[black.Preview] = set()
|
||||||
|
enable_unstable_features = headers.get(ENABLE_UNSTABLE_FEATURE, "").split(",")
|
||||||
|
for piece in enable_unstable_features:
|
||||||
|
piece = piece.strip()
|
||||||
|
if piece:
|
||||||
|
try:
|
||||||
|
enable_features.add(black.Preview[piece])
|
||||||
|
except KeyError:
|
||||||
|
raise HeaderError(
|
||||||
|
f"Invalid value for {ENABLE_UNSTABLE_FEATURE}: {piece}",
|
||||||
|
) from None
|
||||||
|
|
||||||
|
return black.FileMode(
|
||||||
|
target_versions=versions,
|
||||||
|
is_pyi=pyi,
|
||||||
|
line_length=line_length,
|
||||||
|
skip_source_first_line=skip_source_first_line,
|
||||||
|
string_normalization=not skip_string_normalization,
|
||||||
|
magic_trailing_comma=not skip_magic_trailing_comma,
|
||||||
|
preview=preview,
|
||||||
|
unstable=unstable,
|
||||||
|
enabled_features=enable_features,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_python_variant_header(value: str) -> tuple[bool, set[black.TargetVersion]]:
|
||||||
|
if value == "pyi":
|
||||||
|
return True, set()
|
||||||
|
else:
|
||||||
|
versions = set()
|
||||||
|
for version in value.split(","):
|
||||||
|
if version.startswith("py"):
|
||||||
|
version = version[len("py") :]
|
||||||
|
if "." in version:
|
||||||
|
major_str, *rest = version.split(".")
|
||||||
|
else:
|
||||||
|
major_str = version[0]
|
||||||
|
rest = [version[1:]] if len(version) > 1 else []
|
||||||
|
try:
|
||||||
|
major = int(major_str)
|
||||||
|
if major not in (2, 3):
|
||||||
|
raise InvalidVariantHeader("major version must be 2 or 3")
|
||||||
|
if len(rest) > 0:
|
||||||
|
minor = int(rest[0])
|
||||||
|
if major == 2:
|
||||||
|
raise InvalidVariantHeader("Python 2 is not supported")
|
||||||
|
else:
|
||||||
|
# Default to lowest supported minor version.
|
||||||
|
minor = 7 if major == 2 else 3
|
||||||
|
version_str = f"PY{major}{minor}"
|
||||||
|
if major == 3 and not hasattr(black.TargetVersion, version_str):
|
||||||
|
raise InvalidVariantHeader(f"3.{minor} is not supported")
|
||||||
|
versions.add(black.TargetVersion[version_str])
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
raise InvalidVariantHeader("expected e.g. '3.7', 'py3.5'") from None
|
||||||
|
return False, versions
|
||||||
|
|
||||||
|
|
||||||
|
def patched_main() -> None:
|
||||||
|
freeze_support()
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
patched_main()
|
||||||
3
.venv_codegen/Lib/site-packages/blackd/__main__.py
Normal file
3
.venv_codegen/Lib/site-packages/blackd/__main__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import blackd
|
||||||
|
|
||||||
|
blackd.patched_main()
|
||||||
92
.venv_codegen/Lib/site-packages/blackd/client.py
Normal file
92
.venv_codegen/Lib/site-packages/blackd/client.py
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp.typedefs import StrOrURL
|
||||||
|
|
||||||
|
import black
|
||||||
|
|
||||||
|
_DEFAULT_HEADERS = {"Content-Type": "text/plain; charset=utf-8"}
|
||||||
|
|
||||||
|
|
||||||
|
class BlackDClient:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
url: StrOrURL = "http://localhost:9090",
|
||||||
|
line_length: int | None = None,
|
||||||
|
skip_source_first_line: bool = False,
|
||||||
|
skip_string_normalization: bool = False,
|
||||||
|
skip_magic_trailing_comma: bool = False,
|
||||||
|
preview: bool = False,
|
||||||
|
fast: bool = False,
|
||||||
|
python_variant: str | None = None,
|
||||||
|
diff: bool = False,
|
||||||
|
headers: dict[str, str] | None = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize a BlackDClient object.
|
||||||
|
:param url: The URL of the BlackD server.
|
||||||
|
:param line_length: The maximum line length.
|
||||||
|
Corresponds to the ``--line-length`` CLI option.
|
||||||
|
:param skip_source_first_line: True to skip the first line of the source.
|
||||||
|
Corresponds to the ``--skip-source-first-line`` CLI option.
|
||||||
|
:param skip_string_normalization: True to skip string normalization.
|
||||||
|
Corresponds to the ``--skip-string-normalization`` CLI option.
|
||||||
|
:param skip_magic_trailing_comma: True to skip magic trailing comma.
|
||||||
|
Corresponds to the ``--skip-magic-trailing-comma`` CLI option.
|
||||||
|
:param preview: True to enable experimental preview mode.
|
||||||
|
Corresponds to the ``--preview`` CLI option.
|
||||||
|
:param fast: True to enable fast mode.
|
||||||
|
Corresponds to the ``--fast`` CLI option.
|
||||||
|
:param python_variant: The Python variant to use.
|
||||||
|
Corresponds to the ``--pyi`` CLI option if this is "pyi".
|
||||||
|
Otherwise, corresponds to the ``--target-version`` CLI option.
|
||||||
|
:param diff: True to enable diff mode.
|
||||||
|
Corresponds to the ``--diff`` CLI option.
|
||||||
|
:param headers: A dictionary of additional custom headers to send with
|
||||||
|
the request.
|
||||||
|
"""
|
||||||
|
self.url = url
|
||||||
|
self.headers = _DEFAULT_HEADERS.copy()
|
||||||
|
|
||||||
|
if line_length is not None:
|
||||||
|
self.headers["X-Line-Length"] = str(line_length)
|
||||||
|
if skip_source_first_line:
|
||||||
|
self.headers["X-Skip-Source-First-Line"] = "yes"
|
||||||
|
if skip_string_normalization:
|
||||||
|
self.headers["X-Skip-String-Normalization"] = "yes"
|
||||||
|
if skip_magic_trailing_comma:
|
||||||
|
self.headers["X-Skip-Magic-Trailing-Comma"] = "yes"
|
||||||
|
if preview:
|
||||||
|
self.headers["X-Preview"] = "yes"
|
||||||
|
if fast:
|
||||||
|
self.headers["X-Fast-Or-Safe"] = "fast"
|
||||||
|
if python_variant is not None:
|
||||||
|
self.headers["X-Python-Variant"] = python_variant
|
||||||
|
if diff:
|
||||||
|
self.headers["X-Diff"] = "yes"
|
||||||
|
|
||||||
|
if headers is not None:
|
||||||
|
self.headers.update(headers)
|
||||||
|
|
||||||
|
async def format_code(self, unformatted_code: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
self.url, headers=self.headers, data=unformatted_code.encode("utf-8")
|
||||||
|
) as response:
|
||||||
|
if response.status == 204:
|
||||||
|
# Input is already well-formatted
|
||||||
|
return unformatted_code
|
||||||
|
elif response.status == 200:
|
||||||
|
# Formatting was needed
|
||||||
|
return await response.text()
|
||||||
|
elif response.status == 400:
|
||||||
|
# Input contains a syntax error
|
||||||
|
error_message = await response.text()
|
||||||
|
raise black.InvalidInput(error_message)
|
||||||
|
elif response.status == 500:
|
||||||
|
# Other kind of error while formatting
|
||||||
|
error_message = await response.text()
|
||||||
|
raise RuntimeError(f"Error while formatting: {error_message}")
|
||||||
|
else:
|
||||||
|
# Unexpected response status code
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unexpected response status code: {response.status}"
|
||||||
|
)
|
||||||
45
.venv_codegen/Lib/site-packages/blackd/middlewares.py
Normal file
45
.venv_codegen/Lib/site-packages/blackd/middlewares.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
from collections.abc import Awaitable, Callable, Collection, Iterable
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp.typedefs import Middleware
|
||||||
|
from aiohttp.web_middlewares import middleware
|
||||||
|
from aiohttp.web_request import Request
|
||||||
|
from aiohttp.web_response import StreamResponse
|
||||||
|
|
||||||
|
Handler = Callable[[Request], Awaitable[StreamResponse]]
|
||||||
|
|
||||||
|
|
||||||
|
def cors(
|
||||||
|
*,
|
||||||
|
allow_headers: Iterable[str],
|
||||||
|
allow_origins: Collection[str],
|
||||||
|
expose_headers: Iterable[str],
|
||||||
|
) -> Middleware:
|
||||||
|
@middleware
|
||||||
|
async def impl(request: Request, handler: Handler) -> StreamResponse:
|
||||||
|
origin = request.headers.get("Origin")
|
||||||
|
if not origin:
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
|
if origin not in allow_origins:
|
||||||
|
return web.Response(status=403, text="CORS origin is not allowed")
|
||||||
|
|
||||||
|
is_options = request.method == "OPTIONS"
|
||||||
|
is_preflight = is_options and "Access-Control-Request-Method" in request.headers
|
||||||
|
if is_preflight:
|
||||||
|
resp = StreamResponse()
|
||||||
|
else:
|
||||||
|
resp = await handler(request)
|
||||||
|
|
||||||
|
resp.headers["Access-Control-Allow-Origin"] = origin
|
||||||
|
if expose_headers:
|
||||||
|
resp.headers["Access-Control-Expose-Headers"] = ", ".join(expose_headers)
|
||||||
|
if is_options:
|
||||||
|
resp.headers["Access-Control-Allow-Headers"] = ", ".join(allow_headers)
|
||||||
|
resp.headers["Access-Control-Allow-Methods"] = ", ".join(
|
||||||
|
("OPTIONS", "POST")
|
||||||
|
)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
return impl
|
||||||
266
.venv_codegen/Lib/site-packages/blib2to3/Grammar.txt
Normal file
266
.venv_codegen/Lib/site-packages/blib2to3/Grammar.txt
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
# Grammar for 2to3. This grammar supports Python 2.x and 3.x.
|
||||||
|
|
||||||
|
# NOTE WELL: You should also follow all the steps listed at
|
||||||
|
# https://devguide.python.org/grammar/
|
||||||
|
|
||||||
|
# Start symbols for the grammar:
|
||||||
|
# file_input is a module or sequence of commands read from an input file;
|
||||||
|
# single_input is a single interactive statement;
|
||||||
|
# eval_input is the input for the eval() and input() functions.
|
||||||
|
# NB: compound_stmt in single_input is followed by extra NEWLINE!
|
||||||
|
file_input: (NEWLINE | stmt)* ENDMARKER
|
||||||
|
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
||||||
|
eval_input: testlist NEWLINE* ENDMARKER
|
||||||
|
|
||||||
|
typevar: NAME [':' test] ['=' test]
|
||||||
|
paramspec: '**' NAME ['=' test]
|
||||||
|
typevartuple: '*' NAME ['=' (test|star_expr)]
|
||||||
|
typeparam: typevar | paramspec | typevartuple
|
||||||
|
typeparams: '[' typeparam (',' typeparam)* [','] ']'
|
||||||
|
|
||||||
|
decorator: '@' namedexpr_test NEWLINE
|
||||||
|
decorators: decorator+
|
||||||
|
decorated: decorators (classdef | funcdef | async_funcdef)
|
||||||
|
async_funcdef: ASYNC funcdef
|
||||||
|
funcdef: 'def' NAME [typeparams] parameters ['->' test] ':' suite
|
||||||
|
parameters: '(' [typedargslist] ')'
|
||||||
|
|
||||||
|
# The following definition for typedarglist is equivalent to this set of rules:
|
||||||
|
#
|
||||||
|
# arguments = argument (',' argument)*
|
||||||
|
# argument = tfpdef ['=' test]
|
||||||
|
# kwargs = '**' tname [',']
|
||||||
|
# args = '*' [tname_star]
|
||||||
|
# kwonly_kwargs = (',' argument)* [',' [kwargs]]
|
||||||
|
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
|
||||||
|
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
|
||||||
|
# typedargslist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
|
||||||
|
# typedarglist = arguments ',' '/' [',' [typedargslist_no_posonly]])|(typedargslist_no_posonly)"
|
||||||
|
#
|
||||||
|
# It needs to be fully expanded to allow our LL(1) parser to work on it.
|
||||||
|
|
||||||
|
typedargslist: tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [
|
||||||
|
',' [((tfpdef ['=' test] ',')* ('*' [tname_star] (',' tname ['=' test])*
|
||||||
|
[',' ['**' tname [',']]] | '**' tname [','])
|
||||||
|
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])]
|
||||||
|
] | ((tfpdef ['=' test] ',')* ('*' [tname_star] (',' tname ['=' test])*
|
||||||
|
[',' ['**' tname [',']]] | '**' tname [','])
|
||||||
|
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
|
||||||
|
|
||||||
|
tname: NAME [':' test]
|
||||||
|
tname_star: NAME [':' (test|star_expr)]
|
||||||
|
tfpdef: tname | '(' tfplist ')'
|
||||||
|
tfplist: tfpdef (',' tfpdef)* [',']
|
||||||
|
|
||||||
|
# The following definition for varargslist is equivalent to this set of rules:
|
||||||
|
#
|
||||||
|
# arguments = argument (',' argument )*
|
||||||
|
# argument = vfpdef ['=' test]
|
||||||
|
# kwargs = '**' vname [',']
|
||||||
|
# args = '*' [vname]
|
||||||
|
# kwonly_kwargs = (',' argument )* [',' [kwargs]]
|
||||||
|
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
|
||||||
|
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
|
||||||
|
# vararglist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
|
||||||
|
# varargslist = arguments ',' '/' [','[(vararglist_no_posonly)]] | (vararglist_no_posonly)
|
||||||
|
#
|
||||||
|
# It needs to be fully expanded to allow our LL(1) parser to work on it.
|
||||||
|
|
||||||
|
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [
|
||||||
|
((vfpdef ['=' test] ',')* ('*' [vname] (',' vname ['=' test])*
|
||||||
|
[',' ['**' vname [',']]] | '**' vname [','])
|
||||||
|
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
|
||||||
|
]] | ((vfpdef ['=' test] ',')*
|
||||||
|
('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]]| '**' vname [','])
|
||||||
|
| vfpdef ['=' test] (',' vfpdef ['=' test])* [','])
|
||||||
|
|
||||||
|
vname: NAME
|
||||||
|
vfpdef: vname | '(' vfplist ')'
|
||||||
|
vfplist: vfpdef (',' vfpdef)* [',']
|
||||||
|
|
||||||
|
stmt: simple_stmt | compound_stmt
|
||||||
|
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
|
||||||
|
small_stmt: (type_stmt | expr_stmt | del_stmt | pass_stmt | flow_stmt |
|
||||||
|
import_stmt | global_stmt | assert_stmt)
|
||||||
|
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
|
||||||
|
('=' (yield_expr|testlist_star_expr))*)
|
||||||
|
annassign: ':' test ['=' (yield_expr|testlist_star_expr)]
|
||||||
|
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
|
||||||
|
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
|
||||||
|
'<<=' | '>>=' | '**=' | '//=')
|
||||||
|
# For normal and annotated assignments, additional restrictions enforced by the interpreter
|
||||||
|
del_stmt: 'del' exprlist
|
||||||
|
pass_stmt: 'pass'
|
||||||
|
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
|
||||||
|
break_stmt: 'break'
|
||||||
|
continue_stmt: 'continue'
|
||||||
|
return_stmt: 'return' [testlist_star_expr]
|
||||||
|
yield_stmt: yield_expr
|
||||||
|
raise_stmt: 'raise' [test ['from' test | ',' test [',' test]]]
|
||||||
|
import_stmt: import_name | import_from
|
||||||
|
import_name: 'import' dotted_as_names
|
||||||
|
import_from: ('from' ('.'* dotted_name | '.'+)
|
||||||
|
'import' ('*' | '(' import_as_names ')' | import_as_names))
|
||||||
|
import_as_name: NAME ['as' NAME]
|
||||||
|
dotted_as_name: dotted_name ['as' NAME]
|
||||||
|
import_as_names: import_as_name (',' import_as_name)* [',']
|
||||||
|
dotted_as_names: dotted_as_name (',' dotted_as_name)*
|
||||||
|
dotted_name: NAME ('.' NAME)*
|
||||||
|
global_stmt: ('global' | 'nonlocal') NAME (',' NAME)*
|
||||||
|
assert_stmt: 'assert' test [',' test]
|
||||||
|
type_stmt: "type" NAME [typeparams] '=' test
|
||||||
|
|
||||||
|
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt | match_stmt
|
||||||
|
async_stmt: ASYNC (funcdef | with_stmt | for_stmt)
|
||||||
|
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
|
||||||
|
while_stmt: 'while' namedexpr_test ':' suite ['else' ':' suite]
|
||||||
|
for_stmt: 'for' exprlist 'in' testlist_star_expr ':' suite ['else' ':' suite]
|
||||||
|
try_stmt: ('try' ':' suite
|
||||||
|
((except_clause ':' suite)+
|
||||||
|
['else' ':' suite]
|
||||||
|
['finally' ':' suite] |
|
||||||
|
'finally' ':' suite))
|
||||||
|
with_stmt: 'with' asexpr_test (',' asexpr_test)* ':' suite
|
||||||
|
|
||||||
|
# NB compile.c makes sure that the default except clause is last
|
||||||
|
except_clause: 'except' ['*'] [testlist ['as' test]]
|
||||||
|
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
|
||||||
|
|
||||||
|
# Backward compatibility cruft to support:
|
||||||
|
# [ x for x in lambda: True, lambda: False if x() ]
|
||||||
|
# even while also allowing:
|
||||||
|
# lambda x: 5 if x else 2
|
||||||
|
# (But not a mix of the two)
|
||||||
|
testlist_safe: old_test [(',' old_test)+ [',']]
|
||||||
|
old_test: or_test | old_lambdef
|
||||||
|
old_lambdef: 'lambda' [varargslist] ':' old_test
|
||||||
|
|
||||||
|
namedexpr_test: asexpr_test [':=' asexpr_test]
|
||||||
|
|
||||||
|
# This is actually not a real rule, though since the parser is very
|
||||||
|
# limited in terms of the strategy about match/case rules, we are inserting
|
||||||
|
# a virtual case (<expr> as <expr>) as a valid expression. Unless a better
|
||||||
|
# approach is thought, the only side effect of this seem to be just allowing
|
||||||
|
# more stuff to be parser (which would fail on the ast).
|
||||||
|
asexpr_test: test ['as' test]
|
||||||
|
|
||||||
|
test: or_test ['if' or_test 'else' test] | lambdef
|
||||||
|
or_test: and_test ('or' and_test)*
|
||||||
|
and_test: not_test ('and' not_test)*
|
||||||
|
not_test: 'not' not_test | comparison
|
||||||
|
comparison: expr (comp_op expr)*
|
||||||
|
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
|
||||||
|
star_expr: '*' expr
|
||||||
|
expr: xor_expr ('|' xor_expr)*
|
||||||
|
xor_expr: and_expr ('^' and_expr)*
|
||||||
|
and_expr: shift_expr ('&' shift_expr)*
|
||||||
|
shift_expr: arith_expr (('<<'|'>>') arith_expr)*
|
||||||
|
arith_expr: term (('+'|'-') term)*
|
||||||
|
term: factor (('*'|'@'|'/'|'%'|'//') factor)*
|
||||||
|
factor: ('+'|'-'|'~') factor | power
|
||||||
|
power: [AWAIT] atom trailer* ['**' factor]
|
||||||
|
atom: ('(' [yield_expr|testlist_gexp] ')' |
|
||||||
|
'[' [listmaker] ']' |
|
||||||
|
'{' [dictsetmaker] '}' |
|
||||||
|
'`' testlist1 '`' |
|
||||||
|
NAME | NUMBER | (STRING | fstring | tstring)+ | '.' '.' '.')
|
||||||
|
listmaker: (namedexpr_test|star_expr) ( old_comp_for | (',' (namedexpr_test|star_expr))* [','] )
|
||||||
|
testlist_gexp: (namedexpr_test|star_expr) ( old_comp_for | (',' (namedexpr_test|star_expr))* [','] )
|
||||||
|
lambdef: 'lambda' [varargslist] ':' test
|
||||||
|
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
|
||||||
|
subscriptlist: (subscript|star_expr) (',' (subscript|star_expr))* [',']
|
||||||
|
subscript: test [':=' test] | [test] ':' [test] [sliceop]
|
||||||
|
sliceop: ':' [test]
|
||||||
|
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
|
||||||
|
testlist: test (',' test)* [',']
|
||||||
|
dictsetmaker: ( ((test ':' asexpr_test | '**' expr)
|
||||||
|
(comp_for | (',' (test ':' asexpr_test | '**' expr))* [','])) |
|
||||||
|
((test [':=' test] | star_expr)
|
||||||
|
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
|
||||||
|
|
||||||
|
classdef: 'class' NAME [typeparams] ['(' [arglist] ')'] ':' suite
|
||||||
|
|
||||||
|
arglist: argument (',' argument)* [',']
|
||||||
|
|
||||||
|
# "test '=' test" is really "keyword '=' test", but we have no such token.
|
||||||
|
# These need to be in a single rule to avoid grammar that is ambiguous
|
||||||
|
# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr,
|
||||||
|
# we explicitly match '*' here, too, to give it proper precedence.
|
||||||
|
# Illegal combinations and orderings are blocked in ast.c:
|
||||||
|
# multiple (test comp_for) arguments are blocked; keyword unpackings
|
||||||
|
# that precede iterable unpackings are blocked; etc.
|
||||||
|
argument: ( test [comp_for] |
|
||||||
|
test ':=' test [comp_for] |
|
||||||
|
test 'as' test |
|
||||||
|
test '=' asexpr_test |
|
||||||
|
'**' test |
|
||||||
|
'*' test )
|
||||||
|
|
||||||
|
comp_iter: comp_for | comp_if
|
||||||
|
comp_for: [ASYNC] 'for' exprlist 'in' or_test [comp_iter]
|
||||||
|
comp_if: 'if' old_test [comp_iter]
|
||||||
|
|
||||||
|
# As noted above, testlist_safe extends the syntax allowed in list
|
||||||
|
# comprehensions and generators. We can't use it indiscriminately in all
|
||||||
|
# derivations using a comp_for-like pattern because the testlist_safe derivation
|
||||||
|
# contains comma which clashes with trailing comma in arglist.
|
||||||
|
#
|
||||||
|
# This was an issue because the parser would not follow the correct derivation
|
||||||
|
# when parsing syntactically valid Python code. Since testlist_safe was created
|
||||||
|
# specifically to handle list comprehensions and generator expressions enclosed
|
||||||
|
# with parentheses, it's safe to only use it in those. That avoids the issue; we
|
||||||
|
# can parse code like set(x for x in [],).
|
||||||
|
#
|
||||||
|
# The syntax supported by this set of rules is not a valid Python 3 syntax,
|
||||||
|
# hence the prefix "old".
|
||||||
|
#
|
||||||
|
# See https://bugs.python.org/issue27494
|
||||||
|
old_comp_iter: old_comp_for | old_comp_if
|
||||||
|
old_comp_for: [ASYNC] 'for' exprlist 'in' testlist_safe [old_comp_iter]
|
||||||
|
old_comp_if: 'if' old_test [old_comp_iter]
|
||||||
|
|
||||||
|
testlist1: test (',' test)*
|
||||||
|
|
||||||
|
# not used in grammar, but may appear in "node" passed from Parser to Compiler
|
||||||
|
encoding_decl: NAME
|
||||||
|
|
||||||
|
yield_expr: 'yield' [yield_arg]
|
||||||
|
yield_arg: 'from' test | testlist_star_expr
|
||||||
|
|
||||||
|
|
||||||
|
# 3.10 match statement definition
|
||||||
|
|
||||||
|
# PS: normally the grammar is much much more restricted, but
|
||||||
|
# at this moment for not trying to bother much with encoding the
|
||||||
|
# exact same DSL in a LL(1) parser, we will just accept an expression
|
||||||
|
# and let the ast.parse() step of the safe mode to reject invalid
|
||||||
|
# grammar.
|
||||||
|
|
||||||
|
# The reason why it is more restricted is that, patterns are some
|
||||||
|
# sort of a DSL (more advanced than our LHS on assignments, but
|
||||||
|
# still in a very limited python subset). They are not really
|
||||||
|
# expressions, but who cares. If we can parse them, that is enough
|
||||||
|
# to reformat them.
|
||||||
|
|
||||||
|
match_stmt: "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
|
||||||
|
|
||||||
|
# This is more permissive than the actual version. For example it
|
||||||
|
# accepts `match *something:`, even though single-item starred expressions
|
||||||
|
# are forbidden.
|
||||||
|
subject_expr: (namedexpr_test|star_expr) (',' (namedexpr_test|star_expr))* [',']
|
||||||
|
|
||||||
|
# cases
|
||||||
|
case_block: "case" patterns [guard] ':' suite
|
||||||
|
guard: 'if' namedexpr_test
|
||||||
|
patterns: pattern (',' pattern)* [',']
|
||||||
|
pattern: (expr|star_expr) ['as' expr]
|
||||||
|
|
||||||
|
fstring: FSTRING_START fstring_middle* FSTRING_END
|
||||||
|
fstring_middle: fstring_replacement_field | FSTRING_MIDDLE
|
||||||
|
fstring_replacement_field: '{' (yield_expr | testlist_star_expr) ['='] [ "!" NAME ] [ ':' fstring_format_spec* ] '}'
|
||||||
|
fstring_format_spec: FSTRING_MIDDLE | fstring_replacement_field
|
||||||
|
|
||||||
|
tstring: TSTRING_START tstring_middle* TSTRING_END
|
||||||
|
tstring_middle: tstring_replacement_field | TSTRING_MIDDLE
|
||||||
|
tstring_replacement_field: '{' (yield_expr | testlist_star_expr) ['='] [ "!" NAME ] [ ':' tstring_format_spec* ] '}'
|
||||||
|
tstring_format_spec: TSTRING_MIDDLE | tstring_replacement_field
|
||||||
254
.venv_codegen/Lib/site-packages/blib2to3/LICENSE
Normal file
254
.venv_codegen/Lib/site-packages/blib2to3/LICENSE
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
A. HISTORY OF THE SOFTWARE
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||||
|
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
|
||||||
|
as a successor of a language called ABC. Guido remains Python's
|
||||||
|
principal author, although it includes many contributions from others.
|
||||||
|
|
||||||
|
In 1995, Guido continued his work on Python at the Corporation for
|
||||||
|
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
|
||||||
|
in Reston, Virginia where he released several versions of the
|
||||||
|
software.
|
||||||
|
|
||||||
|
In May 2000, Guido and the Python core development team moved to
|
||||||
|
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||||
|
year, the PythonLabs team moved to Digital Creations, which became
|
||||||
|
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||||
|
https://www.python.org/psf/) was formed, a non-profit organization
|
||||||
|
created specifically to own Python-related Intellectual Property.
|
||||||
|
Zope Corporation was a sponsoring member of the PSF.
|
||||||
|
|
||||||
|
All Python releases are Open Source (see https://opensource.org for
|
||||||
|
the Open Source Definition). Historically, most, but not all, Python
|
||||||
|
releases have also been GPL-compatible; the table below summarizes
|
||||||
|
the various releases.
|
||||||
|
|
||||||
|
Release Derived Year Owner GPL-
|
||||||
|
from compatible? (1)
|
||||||
|
|
||||||
|
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||||
|
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||||
|
1.6 1.5.2 2000 CNRI no
|
||||||
|
2.0 1.6 2000 BeOpen.com no
|
||||||
|
1.6.1 1.6 2001 CNRI yes (2)
|
||||||
|
2.1 2.0+1.6.1 2001 PSF no
|
||||||
|
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||||
|
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||||
|
2.1.2 2.1.1 2002 PSF yes
|
||||||
|
2.1.3 2.1.2 2002 PSF yes
|
||||||
|
2.2 and above 2.1.1 2001-now PSF yes
|
||||||
|
|
||||||
|
Footnotes:
|
||||||
|
|
||||||
|
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||||
|
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||||
|
a modified version without making your changes open source. The
|
||||||
|
GPL-compatible licenses make it possible to combine Python with
|
||||||
|
other software that is released under the GPL; the others don't.
|
||||||
|
|
||||||
|
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||||
|
because its license has a choice of law clause. According to
|
||||||
|
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||||
|
is "not incompatible" with the GPL.
|
||||||
|
|
||||||
|
Thanks to the many outside volunteers who have worked under Guido's
|
||||||
|
direction to make these releases possible.
|
||||||
|
|
||||||
|
|
||||||
|
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||||
|
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||||
|
otherwise using this software ("Python") in source or binary form and
|
||||||
|
its associated documentation.
|
||||||
|
|
||||||
|
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||||
|
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||||
|
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||||
|
distribute, and otherwise use Python alone or in any derivative version,
|
||||||
|
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||||
|
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||||
|
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All
|
||||||
|
Rights Reserved" are retained in Python alone or in any derivative version
|
||||||
|
prepared by Licensee.
|
||||||
|
|
||||||
|
3. In the event Licensee prepares a derivative work that is based on
|
||||||
|
or incorporates Python or any part thereof, and wants to make
|
||||||
|
the derivative work available to others as provided herein, then
|
||||||
|
Licensee hereby agrees to include in any such work a brief summary of
|
||||||
|
the changes made to Python.
|
||||||
|
|
||||||
|
4. PSF is making Python available to Licensee on an "AS IS"
|
||||||
|
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||||
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||||
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||||
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||||
|
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||||
|
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||||
|
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||||
|
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||||
|
|
||||||
|
6. This License Agreement will automatically terminate upon a material
|
||||||
|
breach of its terms and conditions.
|
||||||
|
|
||||||
|
7. Nothing in this License Agreement shall be deemed to create any
|
||||||
|
relationship of agency, partnership, or joint venture between PSF and
|
||||||
|
Licensee. This License Agreement does not grant permission to use PSF
|
||||||
|
trademarks or trade name in a trademark sense to endorse or promote
|
||||||
|
products or services of Licensee, or any third party.
|
||||||
|
|
||||||
|
8. By copying, installing or otherwise using Python, Licensee
|
||||||
|
agrees to be bound by the terms and conditions of this License
|
||||||
|
Agreement.
|
||||||
|
|
||||||
|
|
||||||
|
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||||
|
|
||||||
|
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||||
|
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||||
|
Individual or Organization ("Licensee") accessing and otherwise using
|
||||||
|
this software in source or binary form and its associated
|
||||||
|
documentation ("the Software").
|
||||||
|
|
||||||
|
2. Subject to the terms and conditions of this BeOpen Python License
|
||||||
|
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||||
|
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||||
|
and/or display publicly, prepare derivative works, distribute, and
|
||||||
|
otherwise use the Software alone or in any derivative version,
|
||||||
|
provided, however, that the BeOpen Python License is retained in the
|
||||||
|
Software, alone or in any derivative version prepared by Licensee.
|
||||||
|
|
||||||
|
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||||
|
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||||
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||||
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||||
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||||
|
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||||
|
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||||
|
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||||
|
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||||
|
|
||||||
|
5. This License Agreement will automatically terminate upon a material
|
||||||
|
breach of its terms and conditions.
|
||||||
|
|
||||||
|
6. This License Agreement shall be governed by and interpreted in all
|
||||||
|
respects by the law of the State of California, excluding conflict of
|
||||||
|
law provisions. Nothing in this License Agreement shall be deemed to
|
||||||
|
create any relationship of agency, partnership, or joint venture
|
||||||
|
between BeOpen and Licensee. This License Agreement does not grant
|
||||||
|
permission to use BeOpen trademarks or trade names in a trademark
|
||||||
|
sense to endorse or promote products or services of Licensee, or any
|
||||||
|
third party. As an exception, the "BeOpen Python" logos available at
|
||||||
|
http://www.pythonlabs.com/logos.html may be used according to the
|
||||||
|
permissions granted on that web page.
|
||||||
|
|
||||||
|
7. By copying, installing or otherwise using the software, Licensee
|
||||||
|
agrees to be bound by the terms and conditions of this License
|
||||||
|
Agreement.
|
||||||
|
|
||||||
|
|
||||||
|
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||||
|
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||||
|
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||||
|
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||||
|
source or binary form and its associated documentation.
|
||||||
|
|
||||||
|
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||||
|
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||||
|
license to reproduce, analyze, test, perform and/or display publicly,
|
||||||
|
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||||
|
alone or in any derivative version, provided, however, that CNRI's
|
||||||
|
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||||
|
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||||
|
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||||
|
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||||
|
Agreement, Licensee may substitute the following text (omitting the
|
||||||
|
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||||
|
conditions in CNRI's License Agreement. This Agreement together with
|
||||||
|
Python 1.6.1 may be located on the Internet using the following
|
||||||
|
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||||
|
Agreement may also be obtained from a proxy server on the Internet
|
||||||
|
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||||
|
|
||||||
|
3. In the event Licensee prepares a derivative work that is based on
|
||||||
|
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||||
|
the derivative work available to others as provided herein, then
|
||||||
|
Licensee hereby agrees to include in any such work a brief summary of
|
||||||
|
the changes made to Python 1.6.1.
|
||||||
|
|
||||||
|
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||||
|
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||||
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||||
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||||
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||||
|
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||||
|
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||||
|
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||||
|
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||||
|
|
||||||
|
6. This License Agreement will automatically terminate upon a material
|
||||||
|
breach of its terms and conditions.
|
||||||
|
|
||||||
|
7. This License Agreement shall be governed by the federal
|
||||||
|
intellectual property law of the United States, including without
|
||||||
|
limitation the federal copyright law, and, to the extent such
|
||||||
|
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||||
|
Virginia, excluding Virginia's conflict of law provisions.
|
||||||
|
Notwithstanding the foregoing, with regard to derivative works based
|
||||||
|
on Python 1.6.1 that incorporate non-separable material that was
|
||||||
|
previously distributed under the GNU General Public License (GPL), the
|
||||||
|
law of the Commonwealth of Virginia shall govern this License
|
||||||
|
Agreement only as to issues arising under or with respect to
|
||||||
|
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||||
|
License Agreement shall be deemed to create any relationship of
|
||||||
|
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||||
|
License Agreement does not grant permission to use CNRI trademarks or
|
||||||
|
trade name in a trademark sense to endorse or promote products or
|
||||||
|
services of Licensee, or any third party.
|
||||||
|
|
||||||
|
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||||
|
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||||
|
bound by the terms and conditions of this License Agreement.
|
||||||
|
|
||||||
|
ACCEPT
|
||||||
|
|
||||||
|
|
||||||
|
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||||
|
The Netherlands. All rights reserved.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software and its
|
||||||
|
documentation for any purpose and without fee is hereby granted,
|
||||||
|
provided that the above copyright notice appear in all copies and that
|
||||||
|
both that copyright notice and this permission notice appear in
|
||||||
|
supporting documentation, and that the name of Stichting Mathematisch
|
||||||
|
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||||
|
distribution of the software without specific, written prior
|
||||||
|
permission.
|
||||||
|
|
||||||
|
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||||
|
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||||
|
FOR ANY SPECIAL, 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.
|
||||||
28
.venv_codegen/Lib/site-packages/blib2to3/PatternGrammar.txt
Normal file
28
.venv_codegen/Lib/site-packages/blib2to3/PatternGrammar.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Copyright 2006 Google, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
# A grammar to describe tree matching patterns.
|
||||||
|
# Not shown here:
|
||||||
|
# - 'TOKEN' stands for any token (leaf node)
|
||||||
|
# - 'any' stands for any node (leaf or interior)
|
||||||
|
# With 'any' we can still specify the sub-structure.
|
||||||
|
|
||||||
|
# The start symbol is 'Matcher'.
|
||||||
|
|
||||||
|
Matcher: Alternatives ENDMARKER
|
||||||
|
|
||||||
|
Alternatives: Alternative ('|' Alternative)*
|
||||||
|
|
||||||
|
Alternative: (Unit | NegatedUnit)+
|
||||||
|
|
||||||
|
Unit: [NAME '='] ( STRING [Repeater]
|
||||||
|
| NAME [Details] [Repeater]
|
||||||
|
| '(' Alternatives ')' [Repeater]
|
||||||
|
| '[' Alternatives ']'
|
||||||
|
)
|
||||||
|
|
||||||
|
NegatedUnit: 'not' (STRING | NAME [Details] | '(' Alternatives ')')
|
||||||
|
|
||||||
|
Repeater: '*' | '+' | '{' NUMBER [',' NUMBER] '}'
|
||||||
|
|
||||||
|
Details: '<' Alternatives '>'
|
||||||
24
.venv_codegen/Lib/site-packages/blib2to3/README
Normal file
24
.venv_codegen/Lib/site-packages/blib2to3/README
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
A subset of lib2to3 taken from Python 3.7.0b2. Commit hash:
|
||||||
|
9c17e3a1987004b8bcfbe423953aad84493a7984
|
||||||
|
|
||||||
|
Reasons for forking:
|
||||||
|
|
||||||
|
- consistent handling of f-strings for users of Python < 3.6.2
|
||||||
|
- backport of BPO-33064 that fixes parsing files with trailing commas after \*args and
|
||||||
|
\*\*kwargs
|
||||||
|
- backport of GH-6143 that restores the ability to reformat legacy usage of `async`
|
||||||
|
- support all types of string literals
|
||||||
|
- better ability to debug (better reprs)
|
||||||
|
- INDENT and DEDENT don't hold whitespace and comment prefixes
|
||||||
|
- ability to Cythonize
|
||||||
|
|
||||||
|
Change Log:
|
||||||
|
|
||||||
|
- Changes default logger used by Driver
|
||||||
|
- Backported the following upstream parser changes:
|
||||||
|
- "bpo-42381: Allow walrus in set literals and set comprehensions (GH-23332)"
|
||||||
|
https://github.com/python/cpython/commit/cae60187cf7a7b26281d012e1952fafe4e2e97e9
|
||||||
|
- "bpo-42316: Allow unparenthesized walrus operator in indexes (GH-23317)"
|
||||||
|
https://github.com/python/cpython/commit/b0aba1fcdc3da952698d99aec2334faa79a8b68c
|
||||||
|
- Tweaks to help mypyc compile faster code (including inlining type information,
|
||||||
|
"Final-ing", etc.)
|
||||||
1
.venv_codegen/Lib/site-packages/blib2to3/__init__.py
Normal file
1
.venv_codegen/Lib/site-packages/blib2to3/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# empty
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""The pgen2 package."""
|
||||||
256
.venv_codegen/Lib/site-packages/blib2to3/pgen2/conv.py
Normal file
256
.venv_codegen/Lib/site-packages/blib2to3/pgen2/conv.py
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
# mypy: ignore-errors
|
||||||
|
|
||||||
|
"""Convert graminit.[ch] spit out by pgen to Python code.
|
||||||
|
|
||||||
|
Pgen is the Python parser generator. It is useful to quickly create a
|
||||||
|
parser from a grammar file in Python's grammar notation. But I don't
|
||||||
|
want my parsers to be written in C (yet), so I'm translating the
|
||||||
|
parsing tables to Python data structures and writing a Python parse
|
||||||
|
engine.
|
||||||
|
|
||||||
|
Note that the token numbers are constants determined by the standard
|
||||||
|
Python tokenizer. The standard token module defines these numbers and
|
||||||
|
their names (the names are not used much). The token numbers are
|
||||||
|
hardcoded into the Python tokenizer and into pgen. A Python
|
||||||
|
implementation of the Python tokenizer is also available, in the
|
||||||
|
standard tokenize module.
|
||||||
|
|
||||||
|
On the other hand, symbol numbers (representing the grammar's
|
||||||
|
non-terminals) are assigned by pgen based on the actual grammar
|
||||||
|
input.
|
||||||
|
|
||||||
|
Note: this module is pretty much obsolete; the pgen module generates
|
||||||
|
equivalent grammar tables directly from the Grammar.txt input file
|
||||||
|
without having to invoke the Python pgen C program.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Python imports
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Local imports
|
||||||
|
from blib2to3.pgen2 import grammar, token
|
||||||
|
|
||||||
|
|
||||||
|
class Converter(grammar.Grammar):
|
||||||
|
"""Grammar subclass that reads classic pgen output files.
|
||||||
|
|
||||||
|
The run() method reads the tables as produced by the pgen parser
|
||||||
|
generator, typically contained in two C files, graminit.h and
|
||||||
|
graminit.c. The other methods are for internal use only.
|
||||||
|
|
||||||
|
See the base class for more documentation.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, graminit_h, graminit_c):
|
||||||
|
"""Load the grammar tables from the text files written by pgen."""
|
||||||
|
self.parse_graminit_h(graminit_h)
|
||||||
|
self.parse_graminit_c(graminit_c)
|
||||||
|
self.finish_off()
|
||||||
|
|
||||||
|
def parse_graminit_h(self, filename):
|
||||||
|
"""Parse the .h file written by pgen. (Internal)
|
||||||
|
|
||||||
|
This file is a sequence of #define statements defining the
|
||||||
|
nonterminals of the grammar as numbers. We build two tables
|
||||||
|
mapping the numbers to names and back.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
except OSError as err:
|
||||||
|
print(f"Can't open {filename}: {err}")
|
||||||
|
return False
|
||||||
|
self.symbol2number = {}
|
||||||
|
self.number2symbol = {}
|
||||||
|
lineno = 0
|
||||||
|
for line in f:
|
||||||
|
lineno += 1
|
||||||
|
mo = re.match(r"^#define\s+(\w+)\s+(\d+)$", line)
|
||||||
|
if not mo and line.strip():
|
||||||
|
print(f"{filename}({lineno}): can't parse {line.strip()}")
|
||||||
|
else:
|
||||||
|
symbol, number = mo.groups()
|
||||||
|
number = int(number)
|
||||||
|
assert symbol not in self.symbol2number
|
||||||
|
assert number not in self.number2symbol
|
||||||
|
self.symbol2number[symbol] = number
|
||||||
|
self.number2symbol[number] = symbol
|
||||||
|
return True
|
||||||
|
|
||||||
|
def parse_graminit_c(self, filename):
|
||||||
|
"""Parse the .c file written by pgen. (Internal)
|
||||||
|
|
||||||
|
The file looks as follows. The first two lines are always this:
|
||||||
|
|
||||||
|
#include "pgenheaders.h"
|
||||||
|
#include "grammar.h"
|
||||||
|
|
||||||
|
After that come four blocks:
|
||||||
|
|
||||||
|
1) one or more state definitions
|
||||||
|
2) a table defining dfas
|
||||||
|
3) a table defining labels
|
||||||
|
4) a struct defining the grammar
|
||||||
|
|
||||||
|
A state definition has the following form:
|
||||||
|
- one or more arc arrays, each of the form:
|
||||||
|
static arc arcs_<n>_<m>[<k>] = {
|
||||||
|
{<i>, <j>},
|
||||||
|
...
|
||||||
|
};
|
||||||
|
- followed by a state array, of the form:
|
||||||
|
static state states_<s>[<t>] = {
|
||||||
|
{<k>, arcs_<n>_<m>},
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
except OSError as err:
|
||||||
|
print(f"Can't open {filename}: {err}")
|
||||||
|
return False
|
||||||
|
# The code below essentially uses f's iterator-ness!
|
||||||
|
lineno = 0
|
||||||
|
|
||||||
|
# Expect the two #include lines
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == '#include "pgenheaders.h"\n', (lineno, line)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == '#include "grammar.h"\n', (lineno, line)
|
||||||
|
|
||||||
|
# Parse the state definitions
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
allarcs = {}
|
||||||
|
states = []
|
||||||
|
while line.startswith("static arc "):
|
||||||
|
while line.startswith("static arc "):
|
||||||
|
mo = re.match(r"static arc arcs_(\d+)_(\d+)\[(\d+)\] = {$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
n, m, k = list(map(int, mo.groups()))
|
||||||
|
arcs = []
|
||||||
|
for _ in range(k):
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"\s+{(\d+), (\d+)},$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
i, j = list(map(int, mo.groups()))
|
||||||
|
arcs.append((i, j))
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "};\n", (lineno, line)
|
||||||
|
allarcs[(n, m)] = arcs
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"static state states_(\d+)\[(\d+)\] = {$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
s, t = list(map(int, mo.groups()))
|
||||||
|
assert s == len(states), (lineno, line)
|
||||||
|
state = []
|
||||||
|
for _ in range(t):
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"\s+{(\d+), arcs_(\d+)_(\d+)},$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
k, n, m = list(map(int, mo.groups()))
|
||||||
|
arcs = allarcs[n, m]
|
||||||
|
assert k == len(arcs), (lineno, line)
|
||||||
|
state.append(arcs)
|
||||||
|
states.append(state)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "};\n", (lineno, line)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
self.states = states
|
||||||
|
|
||||||
|
# Parse the dfas
|
||||||
|
dfas = {}
|
||||||
|
mo = re.match(r"static dfa dfas\[(\d+)\] = {$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
ndfas = int(mo.group(1))
|
||||||
|
for i in range(ndfas):
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r'\s+{(\d+), "(\w+)", (\d+), (\d+), states_(\d+),$', line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
symbol = mo.group(2)
|
||||||
|
number, x, y, z = list(map(int, mo.group(1, 3, 4, 5)))
|
||||||
|
assert self.symbol2number[symbol] == number, (lineno, line)
|
||||||
|
assert self.number2symbol[number] == symbol, (lineno, line)
|
||||||
|
assert x == 0, (lineno, line)
|
||||||
|
state = states[z]
|
||||||
|
assert y == len(state), (lineno, line)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r'\s+("(?:\\\d\d\d)*")},$', line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
first = {}
|
||||||
|
rawbitset = eval(mo.group(1))
|
||||||
|
for i, c in enumerate(rawbitset):
|
||||||
|
byte = ord(c)
|
||||||
|
for j in range(8):
|
||||||
|
if byte & (1 << j):
|
||||||
|
first[i * 8 + j] = 1
|
||||||
|
dfas[number] = (state, first)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "};\n", (lineno, line)
|
||||||
|
self.dfas = dfas
|
||||||
|
|
||||||
|
# Parse the labels
|
||||||
|
labels = []
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"static label labels\[(\d+)\] = {$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
nlabels = int(mo.group(1))
|
||||||
|
for i in range(nlabels):
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r'\s+{(\d+), (0|"\w+")},$', line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
x, y = mo.groups()
|
||||||
|
x = int(x)
|
||||||
|
if y == "0":
|
||||||
|
y = None
|
||||||
|
else:
|
||||||
|
y = eval(y)
|
||||||
|
labels.append((x, y))
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "};\n", (lineno, line)
|
||||||
|
self.labels = labels
|
||||||
|
|
||||||
|
# Parse the grammar struct
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "grammar _PyParser_Grammar = {\n", (lineno, line)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"\s+(\d+),$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
ndfas = int(mo.group(1))
|
||||||
|
assert ndfas == len(self.dfas)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "\tdfas,\n", (lineno, line)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"\s+{(\d+), labels},$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
nlabels = int(mo.group(1))
|
||||||
|
assert nlabels == len(self.labels), (lineno, line)
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
mo = re.match(r"\s+(\d+)$", line)
|
||||||
|
assert mo, (lineno, line)
|
||||||
|
start = int(mo.group(1))
|
||||||
|
assert start in self.number2symbol, (lineno, line)
|
||||||
|
self.start = start
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
assert line == "};\n", (lineno, line)
|
||||||
|
try:
|
||||||
|
lineno, line = lineno + 1, next(f)
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert 0, (lineno, line)
|
||||||
|
|
||||||
|
def finish_off(self):
|
||||||
|
"""Create additional useful structures. (Internal)."""
|
||||||
|
self.keywords = {} # map from keyword strings to arc labels
|
||||||
|
self.tokens = {} # map from numeric token values to arc labels
|
||||||
|
for ilabel, (type, value) in enumerate(self.labels):
|
||||||
|
if type == token.NAME and value is not None:
|
||||||
|
self.keywords[value] = ilabel
|
||||||
|
elif value is None:
|
||||||
|
self.tokens[type] = ilabel
|
||||||
313
.venv_codegen/Lib/site-packages/blib2to3/pgen2/driver.py
Normal file
313
.venv_codegen/Lib/site-packages/blib2to3/pgen2/driver.py
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
# Modifications:
|
||||||
|
# Copyright 2006 Google, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""Parser driver.
|
||||||
|
|
||||||
|
This provides a high-level interface to parse a file into a syntax tree.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Guido van Rossum <guido@python.org>"
|
||||||
|
|
||||||
|
__all__ = ["Driver", "load_grammar"]
|
||||||
|
|
||||||
|
# Python imports
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pkgutil
|
||||||
|
import sys
|
||||||
|
from collections.abc import Iterable, Iterator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from logging import Logger
|
||||||
|
from typing import Any, Union, cast
|
||||||
|
|
||||||
|
from blib2to3.pgen2.grammar import Grammar
|
||||||
|
from blib2to3.pgen2.tokenize import TokenInfo
|
||||||
|
from blib2to3.pytree import NL
|
||||||
|
|
||||||
|
# Pgen imports
|
||||||
|
from . import grammar, parse, pgen, token, tokenize
|
||||||
|
|
||||||
|
Path = Union[str, "os.PathLike[str]"]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReleaseRange:
|
||||||
|
start: int
|
||||||
|
end: int | None = None
|
||||||
|
tokens: list[Any] = field(default_factory=list)
|
||||||
|
|
||||||
|
def lock(self) -> None:
|
||||||
|
total_eaten = len(self.tokens)
|
||||||
|
self.end = self.start + total_eaten
|
||||||
|
|
||||||
|
|
||||||
|
class TokenProxy:
|
||||||
|
def __init__(self, generator: Any) -> None:
|
||||||
|
self._tokens = generator
|
||||||
|
self._counter = 0
|
||||||
|
self._release_ranges: list[ReleaseRange] = []
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def release(self) -> Iterator["TokenProxy"]:
|
||||||
|
release_range = ReleaseRange(self._counter)
|
||||||
|
self._release_ranges.append(release_range)
|
||||||
|
try:
|
||||||
|
yield self
|
||||||
|
finally:
|
||||||
|
# Lock the last release range to the final position that
|
||||||
|
# has been eaten.
|
||||||
|
release_range.lock()
|
||||||
|
|
||||||
|
def eat(self, point: int) -> Any:
|
||||||
|
eaten_tokens = self._release_ranges[-1].tokens
|
||||||
|
if point < len(eaten_tokens):
|
||||||
|
return eaten_tokens[point]
|
||||||
|
else:
|
||||||
|
while point >= len(eaten_tokens):
|
||||||
|
token = next(self._tokens)
|
||||||
|
eaten_tokens.append(token)
|
||||||
|
return token
|
||||||
|
|
||||||
|
def __iter__(self) -> "TokenProxy":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self) -> Any:
|
||||||
|
# If the current position is already compromised (looked up)
|
||||||
|
# return the eaten token, if not just go further on the given
|
||||||
|
# token producer.
|
||||||
|
for release_range in self._release_ranges:
|
||||||
|
assert release_range.end is not None
|
||||||
|
|
||||||
|
start, end = release_range.start, release_range.end
|
||||||
|
if start <= self._counter < end:
|
||||||
|
token = release_range.tokens[self._counter - start]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
token = next(self._tokens)
|
||||||
|
self._counter += 1
|
||||||
|
return token
|
||||||
|
|
||||||
|
def can_advance(self, to: int) -> bool:
|
||||||
|
# Try to eat, fail if it can't. The eat operation is cached
|
||||||
|
# so there won't be any additional cost of eating here
|
||||||
|
try:
|
||||||
|
self.eat(to)
|
||||||
|
except StopIteration:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Driver:
|
||||||
|
def __init__(self, grammar: Grammar, logger: Logger | None = None) -> None:
|
||||||
|
self.grammar = grammar
|
||||||
|
if logger is None:
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
def parse_tokens(self, tokens: Iterable[TokenInfo], debug: bool = False) -> NL:
|
||||||
|
"""Parse a series of tokens and return the syntax tree."""
|
||||||
|
# XXX Move the prefix computation into a wrapper around tokenize.
|
||||||
|
proxy = TokenProxy(tokens)
|
||||||
|
|
||||||
|
p = parse.Parser(self.grammar)
|
||||||
|
p.setup(proxy=proxy)
|
||||||
|
|
||||||
|
lineno = 1
|
||||||
|
column = 0
|
||||||
|
indent_columns: list[int] = []
|
||||||
|
type = value = start = end = line_text = None
|
||||||
|
prefix = ""
|
||||||
|
|
||||||
|
for quintuple in proxy:
|
||||||
|
type, value, start, end, line_text = quintuple
|
||||||
|
if start != (lineno, column):
|
||||||
|
assert (lineno, column) <= start, ((lineno, column), start)
|
||||||
|
s_lineno, s_column = start
|
||||||
|
if lineno < s_lineno:
|
||||||
|
prefix += "\n" * (s_lineno - lineno)
|
||||||
|
lineno = s_lineno
|
||||||
|
column = 0
|
||||||
|
if column < s_column:
|
||||||
|
prefix += line_text[column:s_column]
|
||||||
|
column = s_column
|
||||||
|
if type in (tokenize.COMMENT, tokenize.NL):
|
||||||
|
prefix += value
|
||||||
|
lineno, column = end
|
||||||
|
if value.endswith("\n"):
|
||||||
|
lineno += 1
|
||||||
|
column = 0
|
||||||
|
continue
|
||||||
|
if type == token.OP:
|
||||||
|
type = grammar.opmap[value]
|
||||||
|
if debug:
|
||||||
|
assert type is not None
|
||||||
|
self.logger.debug(
|
||||||
|
"%s %r (prefix=%r)", token.tok_name[type], value, prefix
|
||||||
|
)
|
||||||
|
if type == token.INDENT:
|
||||||
|
indent_columns.append(len(value))
|
||||||
|
_prefix = prefix + value
|
||||||
|
prefix = ""
|
||||||
|
value = ""
|
||||||
|
elif type == token.DEDENT:
|
||||||
|
_indent_col = indent_columns.pop()
|
||||||
|
prefix, _prefix = self._partially_consume_prefix(prefix, _indent_col)
|
||||||
|
if p.addtoken(cast(int, type), value, (prefix, start)):
|
||||||
|
if debug:
|
||||||
|
self.logger.debug("Stop.")
|
||||||
|
break
|
||||||
|
prefix = ""
|
||||||
|
if type in {token.INDENT, token.DEDENT}:
|
||||||
|
prefix = _prefix
|
||||||
|
lineno, column = end
|
||||||
|
# FSTRING_MIDDLE and TSTRING_MIDDLE are the only token that can end with a
|
||||||
|
# newline, and `end` will point to the next line. For that case, don't
|
||||||
|
# increment lineno.
|
||||||
|
if value.endswith("\n") and type not in (
|
||||||
|
token.FSTRING_MIDDLE,
|
||||||
|
token.TSTRING_MIDDLE,
|
||||||
|
):
|
||||||
|
lineno += 1
|
||||||
|
column = 0
|
||||||
|
else:
|
||||||
|
# We never broke out -- EOF is too soon (how can this happen???)
|
||||||
|
assert start is not None
|
||||||
|
raise parse.ParseError("incomplete input", type, value, (prefix, start))
|
||||||
|
assert p.rootnode is not None
|
||||||
|
return p.rootnode
|
||||||
|
|
||||||
|
def parse_file(
|
||||||
|
self, filename: Path, encoding: str | None = None, debug: bool = False
|
||||||
|
) -> NL:
|
||||||
|
"""Parse a file and return the syntax tree."""
|
||||||
|
with open(filename, encoding=encoding) as stream:
|
||||||
|
text = stream.read()
|
||||||
|
return self.parse_string(text, debug)
|
||||||
|
|
||||||
|
def parse_string(self, text: str, debug: bool = False) -> NL:
|
||||||
|
"""Parse a string and return the syntax tree."""
|
||||||
|
tokens = tokenize.tokenize(text, grammar=self.grammar)
|
||||||
|
return self.parse_tokens(tokens, debug)
|
||||||
|
|
||||||
|
def _partially_consume_prefix(self, prefix: str, column: int) -> tuple[str, str]:
|
||||||
|
lines: list[str] = []
|
||||||
|
current_line = ""
|
||||||
|
current_column = 0
|
||||||
|
wait_for_nl = False
|
||||||
|
for char in prefix:
|
||||||
|
current_line += char
|
||||||
|
if wait_for_nl:
|
||||||
|
if char == "\n":
|
||||||
|
if current_line.strip() and current_column < column:
|
||||||
|
res = "".join(lines)
|
||||||
|
return res, prefix[len(res) :]
|
||||||
|
|
||||||
|
lines.append(current_line)
|
||||||
|
current_line = ""
|
||||||
|
current_column = 0
|
||||||
|
wait_for_nl = False
|
||||||
|
elif char in " \t":
|
||||||
|
current_column += 1
|
||||||
|
elif char == "\n":
|
||||||
|
# unexpected empty line
|
||||||
|
current_column = 0
|
||||||
|
elif char == "\f":
|
||||||
|
current_column = 0
|
||||||
|
else:
|
||||||
|
# indent is finished
|
||||||
|
wait_for_nl = True
|
||||||
|
return "".join(lines), current_line
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_pickle_name(gt: Path, cache_dir: Path | None = None) -> str:
|
||||||
|
head, tail = os.path.splitext(gt)
|
||||||
|
if tail == ".txt":
|
||||||
|
tail = ""
|
||||||
|
name = head + tail + ".".join(map(str, sys.version_info)) + ".pickle"
|
||||||
|
if cache_dir:
|
||||||
|
return os.path.join(cache_dir, os.path.basename(name))
|
||||||
|
else:
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def load_grammar(
|
||||||
|
gt: str = "Grammar.txt",
|
||||||
|
gp: str | None = None,
|
||||||
|
save: bool = True,
|
||||||
|
force: bool = False,
|
||||||
|
logger: Logger | None = None,
|
||||||
|
) -> Grammar:
|
||||||
|
"""Load the grammar (maybe from a pickle)."""
|
||||||
|
if logger is None:
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
gp = _generate_pickle_name(gt) if gp is None else gp
|
||||||
|
if force or not _newer(gp, gt):
|
||||||
|
g: grammar.Grammar = pgen.generate_grammar(gt)
|
||||||
|
if save:
|
||||||
|
try:
|
||||||
|
g.dump(gp)
|
||||||
|
except OSError:
|
||||||
|
# Ignore error, caching is not vital.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
g = grammar.Grammar()
|
||||||
|
g.load(gp)
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def _newer(a: str, b: str) -> bool:
|
||||||
|
"""Inquire whether file a was written since file b."""
|
||||||
|
if not os.path.exists(a):
|
||||||
|
return False
|
||||||
|
if not os.path.exists(b):
|
||||||
|
return True
|
||||||
|
return os.path.getmtime(a) >= os.path.getmtime(b)
|
||||||
|
|
||||||
|
|
||||||
|
def load_packaged_grammar(
|
||||||
|
package: str, grammar_source: str, cache_dir: Path | None = None
|
||||||
|
) -> grammar.Grammar:
|
||||||
|
"""Normally, loads a pickled grammar by doing
|
||||||
|
pkgutil.get_data(package, pickled_grammar)
|
||||||
|
where *pickled_grammar* is computed from *grammar_source* by adding the
|
||||||
|
Python version and using a ``.pickle`` extension.
|
||||||
|
|
||||||
|
However, if *grammar_source* is an extant file, load_grammar(grammar_source)
|
||||||
|
is called instead. This facilitates using a packaged grammar file when needed
|
||||||
|
but preserves load_grammar's automatic regeneration behavior when possible.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if os.path.isfile(grammar_source):
|
||||||
|
gp = _generate_pickle_name(grammar_source, cache_dir) if cache_dir else None
|
||||||
|
return load_grammar(grammar_source, gp=gp)
|
||||||
|
pickled_name = _generate_pickle_name(os.path.basename(grammar_source), cache_dir)
|
||||||
|
data = pkgutil.get_data(package, pickled_name)
|
||||||
|
assert data is not None
|
||||||
|
g = grammar.Grammar()
|
||||||
|
g.loads(data)
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def main(*args: str) -> bool:
|
||||||
|
"""Main program, when run as a script: produce grammar pickle files.
|
||||||
|
|
||||||
|
Calls load_grammar for each argument, a path to a grammar text file.
|
||||||
|
"""
|
||||||
|
if not args:
|
||||||
|
args = tuple(sys.argv[1:])
|
||||||
|
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format="%(message)s")
|
||||||
|
for gt in args:
|
||||||
|
load_grammar(gt, save=True, force=True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(int(not main()))
|
||||||
228
.venv_codegen/Lib/site-packages/blib2to3/pgen2/grammar.py
Normal file
228
.venv_codegen/Lib/site-packages/blib2to3/pgen2/grammar.py
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""This module defines the data structures used to represent a grammar.
|
||||||
|
|
||||||
|
These are a bit arcane because they are derived from the data
|
||||||
|
structures used by Python's 'pgen' parser generator.
|
||||||
|
|
||||||
|
There's also a table here mapping operators to their names in the
|
||||||
|
token module; the Python tokenize module reports all operators as the
|
||||||
|
fallback token code OP, but the parser needs the actual token code.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import tempfile
|
||||||
|
from typing import Any, Optional, TypeVar, Union
|
||||||
|
|
||||||
|
# Local imports
|
||||||
|
from . import token
|
||||||
|
|
||||||
|
_P = TypeVar("_P", bound="Grammar")
|
||||||
|
Label = tuple[int, Optional[str]]
|
||||||
|
DFA = list[list[tuple[int, int]]]
|
||||||
|
DFAS = tuple[DFA, dict[int, int]]
|
||||||
|
Path = Union[str, "os.PathLike[str]"]
|
||||||
|
|
||||||
|
|
||||||
|
class Grammar:
|
||||||
|
"""Pgen parsing tables conversion class.
|
||||||
|
|
||||||
|
Once initialized, this class supplies the grammar tables for the
|
||||||
|
parsing engine implemented by parse.py. The parsing engine
|
||||||
|
accesses the instance variables directly. The class here does not
|
||||||
|
provide initialization of the tables; several subclasses exist to
|
||||||
|
do this (see the conv and pgen modules).
|
||||||
|
|
||||||
|
The load() method reads the tables from a pickle file, which is
|
||||||
|
much faster than the other ways offered by subclasses. The pickle
|
||||||
|
file is written by calling dump() (after loading the grammar
|
||||||
|
tables using a subclass). The report() method prints a readable
|
||||||
|
representation of the tables to stdout, for debugging.
|
||||||
|
|
||||||
|
The instance variables are as follows:
|
||||||
|
|
||||||
|
symbol2number -- a dict mapping symbol names to numbers. Symbol
|
||||||
|
numbers are always 256 or higher, to distinguish
|
||||||
|
them from token numbers, which are between 0 and
|
||||||
|
255 (inclusive).
|
||||||
|
|
||||||
|
number2symbol -- a dict mapping numbers to symbol names;
|
||||||
|
these two are each other's inverse.
|
||||||
|
|
||||||
|
states -- a list of DFAs, where each DFA is a list of
|
||||||
|
states, each state is a list of arcs, and each
|
||||||
|
arc is a (i, j) pair where i is a label and j is
|
||||||
|
a state number. The DFA number is the index into
|
||||||
|
this list. (This name is slightly confusing.)
|
||||||
|
Final states are represented by a special arc of
|
||||||
|
the form (0, j) where j is its own state number.
|
||||||
|
|
||||||
|
dfas -- a dict mapping symbol numbers to (DFA, first)
|
||||||
|
pairs, where DFA is an item from the states list
|
||||||
|
above, and first is a set of tokens that can
|
||||||
|
begin this grammar rule (represented by a dict
|
||||||
|
whose values are always 1).
|
||||||
|
|
||||||
|
labels -- a list of (x, y) pairs where x is either a token
|
||||||
|
number or a symbol number, and y is either None
|
||||||
|
or a string; the strings are keywords. The label
|
||||||
|
number is the index in this list; label numbers
|
||||||
|
are used to mark state transitions (arcs) in the
|
||||||
|
DFAs.
|
||||||
|
|
||||||
|
start -- the number of the grammar's start symbol.
|
||||||
|
|
||||||
|
keywords -- a dict mapping keyword strings to arc labels.
|
||||||
|
|
||||||
|
tokens -- a dict mapping token numbers to arc labels.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.symbol2number: dict[str, int] = {}
|
||||||
|
self.number2symbol: dict[int, str] = {}
|
||||||
|
self.states: list[DFA] = []
|
||||||
|
self.dfas: dict[int, DFAS] = {}
|
||||||
|
self.labels: list[Label] = [(0, "EMPTY")]
|
||||||
|
self.keywords: dict[str, int] = {}
|
||||||
|
self.soft_keywords: dict[str, int] = {}
|
||||||
|
self.tokens: dict[int, int] = {}
|
||||||
|
self.symbol2label: dict[str, int] = {}
|
||||||
|
self.version: tuple[int, int] = (0, 0)
|
||||||
|
self.start = 256
|
||||||
|
# Python 3.7+ parses async as a keyword, not an identifier
|
||||||
|
self.async_keywords = False
|
||||||
|
|
||||||
|
def dump(self, filename: Path) -> None:
|
||||||
|
"""Dump the grammar tables to a pickle file."""
|
||||||
|
|
||||||
|
# mypyc generates objects that don't have a __dict__, but they
|
||||||
|
# do have __getstate__ methods that will return an equivalent
|
||||||
|
# dictionary
|
||||||
|
if hasattr(self, "__dict__"):
|
||||||
|
d = self.__dict__
|
||||||
|
else:
|
||||||
|
d = self.__getstate__() # type: ignore
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
dir=os.path.dirname(filename), delete=False
|
||||||
|
) as f:
|
||||||
|
pickle.dump(d, f, pickle.HIGHEST_PROTOCOL)
|
||||||
|
os.replace(f.name, filename)
|
||||||
|
|
||||||
|
def _update(self, attrs: dict[str, Any]) -> None:
|
||||||
|
for k, v in attrs.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def load(self, filename: Path) -> None:
|
||||||
|
"""Load the grammar tables from a pickle file."""
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
d = pickle.load(f)
|
||||||
|
self._update(d)
|
||||||
|
|
||||||
|
def loads(self, pkl: bytes) -> None:
|
||||||
|
"""Load the grammar tables from a pickle bytes object."""
|
||||||
|
self._update(pickle.loads(pkl))
|
||||||
|
|
||||||
|
def copy(self: _P) -> _P:
|
||||||
|
"""
|
||||||
|
Copy the grammar.
|
||||||
|
"""
|
||||||
|
new = self.__class__()
|
||||||
|
for dict_attr in (
|
||||||
|
"symbol2number",
|
||||||
|
"number2symbol",
|
||||||
|
"dfas",
|
||||||
|
"keywords",
|
||||||
|
"soft_keywords",
|
||||||
|
"tokens",
|
||||||
|
"symbol2label",
|
||||||
|
):
|
||||||
|
setattr(new, dict_attr, getattr(self, dict_attr).copy())
|
||||||
|
new.labels = self.labels[:]
|
||||||
|
new.states = self.states[:]
|
||||||
|
new.start = self.start
|
||||||
|
new.version = self.version
|
||||||
|
new.async_keywords = self.async_keywords
|
||||||
|
return new
|
||||||
|
|
||||||
|
def report(self) -> None:
|
||||||
|
"""Dump the grammar tables to standard output, for debugging."""
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
print("s2n")
|
||||||
|
pprint(self.symbol2number)
|
||||||
|
print("n2s")
|
||||||
|
pprint(self.number2symbol)
|
||||||
|
print("states")
|
||||||
|
pprint(self.states)
|
||||||
|
print("dfas")
|
||||||
|
pprint(self.dfas)
|
||||||
|
print("labels")
|
||||||
|
pprint(self.labels)
|
||||||
|
print("start", self.start)
|
||||||
|
|
||||||
|
|
||||||
|
# Map from operator to number (since tokenize doesn't do this)
|
||||||
|
|
||||||
|
opmap_raw = """
|
||||||
|
( LPAR
|
||||||
|
) RPAR
|
||||||
|
[ LSQB
|
||||||
|
] RSQB
|
||||||
|
: COLON
|
||||||
|
, COMMA
|
||||||
|
; SEMI
|
||||||
|
+ PLUS
|
||||||
|
- MINUS
|
||||||
|
* STAR
|
||||||
|
/ SLASH
|
||||||
|
| VBAR
|
||||||
|
& AMPER
|
||||||
|
< LESS
|
||||||
|
> GREATER
|
||||||
|
= EQUAL
|
||||||
|
. DOT
|
||||||
|
% PERCENT
|
||||||
|
` BACKQUOTE
|
||||||
|
{ LBRACE
|
||||||
|
} RBRACE
|
||||||
|
@ AT
|
||||||
|
@= ATEQUAL
|
||||||
|
== EQEQUAL
|
||||||
|
!= NOTEQUAL
|
||||||
|
<> NOTEQUAL
|
||||||
|
<= LESSEQUAL
|
||||||
|
>= GREATEREQUAL
|
||||||
|
~ TILDE
|
||||||
|
^ CIRCUMFLEX
|
||||||
|
<< LEFTSHIFT
|
||||||
|
>> RIGHTSHIFT
|
||||||
|
** DOUBLESTAR
|
||||||
|
+= PLUSEQUAL
|
||||||
|
-= MINEQUAL
|
||||||
|
*= STAREQUAL
|
||||||
|
/= SLASHEQUAL
|
||||||
|
%= PERCENTEQUAL
|
||||||
|
&= AMPEREQUAL
|
||||||
|
|= VBAREQUAL
|
||||||
|
^= CIRCUMFLEXEQUAL
|
||||||
|
<<= LEFTSHIFTEQUAL
|
||||||
|
>>= RIGHTSHIFTEQUAL
|
||||||
|
**= DOUBLESTAREQUAL
|
||||||
|
// DOUBLESLASH
|
||||||
|
//= DOUBLESLASHEQUAL
|
||||||
|
-> RARROW
|
||||||
|
:= COLONEQUAL
|
||||||
|
! BANG
|
||||||
|
"""
|
||||||
|
|
||||||
|
opmap = {}
|
||||||
|
for line in opmap_raw.splitlines():
|
||||||
|
if line:
|
||||||
|
op, name = line.split()
|
||||||
|
opmap[op] = getattr(token, name)
|
||||||
65
.venv_codegen/Lib/site-packages/blib2to3/pgen2/literals.py
Normal file
65
.venv_codegen/Lib/site-packages/blib2to3/pgen2/literals.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""Safely evaluate Python string literals without using eval()."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
simple_escapes: dict[str, str] = {
|
||||||
|
"a": "\a",
|
||||||
|
"b": "\b",
|
||||||
|
"f": "\f",
|
||||||
|
"n": "\n",
|
||||||
|
"r": "\r",
|
||||||
|
"t": "\t",
|
||||||
|
"v": "\v",
|
||||||
|
"'": "'",
|
||||||
|
'"': '"',
|
||||||
|
"\\": "\\",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def escape(m: re.Match[str]) -> str:
|
||||||
|
all, tail = m.group(0, 1)
|
||||||
|
assert all.startswith("\\")
|
||||||
|
esc = simple_escapes.get(tail)
|
||||||
|
if esc is not None:
|
||||||
|
return esc
|
||||||
|
if tail.startswith("x"):
|
||||||
|
hexes = tail[1:]
|
||||||
|
if len(hexes) < 2:
|
||||||
|
raise ValueError(f"invalid hex string escape ('\\{tail}')")
|
||||||
|
try:
|
||||||
|
i = int(hexes, 16)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"invalid hex string escape ('\\{tail}')") from None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
i = int(tail, 8)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"invalid octal string escape ('\\{tail}')") from None
|
||||||
|
return chr(i)
|
||||||
|
|
||||||
|
|
||||||
|
def evalString(s: str) -> str:
|
||||||
|
assert s.startswith("'") or s.startswith('"'), repr(s[:1])
|
||||||
|
q = s[0]
|
||||||
|
if s[:3] == q * 3:
|
||||||
|
q = q * 3
|
||||||
|
assert s.endswith(q), repr(s[-len(q) :])
|
||||||
|
assert len(s) >= 2 * len(q)
|
||||||
|
s = s[len(q) : -len(q)]
|
||||||
|
return re.sub(r"\\(\'|\"|\\|[abfnrtv]|x.{0,2}|[0-7]{1,3})", escape, s)
|
||||||
|
|
||||||
|
|
||||||
|
def test() -> None:
|
||||||
|
for i in range(256):
|
||||||
|
c = chr(i)
|
||||||
|
s = repr(c)
|
||||||
|
e = evalString(s)
|
||||||
|
if e != c:
|
||||||
|
print(i, c, s, e)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test()
|
||||||
395
.venv_codegen/Lib/site-packages/blib2to3/pgen2/parse.py
Normal file
395
.venv_codegen/Lib/site-packages/blib2to3/pgen2/parse.py
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""Parser engine for the grammar tables generated by pgen.
|
||||||
|
|
||||||
|
The grammar table must be loaded first.
|
||||||
|
|
||||||
|
See Parser/parser.c in the Python distribution for additional info on
|
||||||
|
how this parsing engine works.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Callable, Iterator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from typing import TYPE_CHECKING, Union, cast
|
||||||
|
|
||||||
|
from blib2to3.pgen2.grammar import Grammar
|
||||||
|
from blib2to3.pytree import NL, Context, Leaf, Node, RawNode, convert
|
||||||
|
|
||||||
|
# Local imports
|
||||||
|
from . import grammar, token, tokenize
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from blib2to3.pgen2.driver import TokenProxy
|
||||||
|
|
||||||
|
|
||||||
|
Results = dict[str, NL]
|
||||||
|
Convert = Callable[[Grammar, RawNode], Union[Node, Leaf]]
|
||||||
|
DFA = list[list[tuple[int, int]]]
|
||||||
|
DFAS = tuple[DFA, dict[int, int]]
|
||||||
|
|
||||||
|
|
||||||
|
def lam_sub(grammar: Grammar, node: RawNode) -> NL:
|
||||||
|
assert node[3] is not None
|
||||||
|
return Node(type=node[0], children=node[3], context=node[2])
|
||||||
|
|
||||||
|
|
||||||
|
# A placeholder node, used when parser is backtracking.
|
||||||
|
DUMMY_NODE = (-1, None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
def stack_copy(
|
||||||
|
stack: list[tuple[DFAS, int, RawNode]],
|
||||||
|
) -> list[tuple[DFAS, int, RawNode]]:
|
||||||
|
"""Nodeless stack copy."""
|
||||||
|
return [(dfa, label, DUMMY_NODE) for dfa, label, _ in stack]
|
||||||
|
|
||||||
|
|
||||||
|
class Recorder:
|
||||||
|
def __init__(self, parser: "Parser", ilabels: list[int], context: Context) -> None:
|
||||||
|
self.parser = parser
|
||||||
|
self._ilabels = ilabels
|
||||||
|
self.context = context # not really matter
|
||||||
|
|
||||||
|
self._dead_ilabels: set[int] = set()
|
||||||
|
self._start_point = self.parser.stack
|
||||||
|
self._points = {ilabel: stack_copy(self._start_point) for ilabel in ilabels}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ilabels(self) -> set[int]:
|
||||||
|
return self._dead_ilabels.symmetric_difference(self._ilabels)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def switch_to(self, ilabel: int) -> Iterator[None]:
|
||||||
|
with self.backtrack():
|
||||||
|
self.parser.stack = self._points[ilabel]
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except ParseError:
|
||||||
|
self._dead_ilabels.add(ilabel)
|
||||||
|
finally:
|
||||||
|
self.parser.stack = self._start_point
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def backtrack(self) -> Iterator[None]:
|
||||||
|
"""
|
||||||
|
Use the node-level invariant ones for basic parsing operations (push/pop/shift).
|
||||||
|
These still will operate on the stack; but they won't create any new nodes, or
|
||||||
|
modify the contents of any other existing nodes.
|
||||||
|
|
||||||
|
This saves us a ton of time when we are backtracking, since we
|
||||||
|
want to restore to the initial state as quick as possible, which
|
||||||
|
can only be done by having as little mutatations as possible.
|
||||||
|
"""
|
||||||
|
is_backtracking = self.parser.is_backtracking
|
||||||
|
try:
|
||||||
|
self.parser.is_backtracking = True
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.parser.is_backtracking = is_backtracking
|
||||||
|
|
||||||
|
def add_token(self, tok_type: int, tok_val: str, raw: bool = False) -> None:
|
||||||
|
for ilabel in self.ilabels:
|
||||||
|
with self.switch_to(ilabel):
|
||||||
|
if raw:
|
||||||
|
self.parser._addtoken(ilabel, tok_type, tok_val, self.context)
|
||||||
|
else:
|
||||||
|
self.parser.addtoken(tok_type, tok_val, self.context)
|
||||||
|
|
||||||
|
def determine_route(
|
||||||
|
self, value: str | None = None, force: bool = False
|
||||||
|
) -> int | None:
|
||||||
|
alive_ilabels = self.ilabels
|
||||||
|
if len(alive_ilabels) == 0:
|
||||||
|
*_, most_successful_ilabel = self._dead_ilabels
|
||||||
|
raise ParseError("bad input", most_successful_ilabel, value, self.context)
|
||||||
|
|
||||||
|
ilabel, *rest = alive_ilabels
|
||||||
|
if force or not rest:
|
||||||
|
return ilabel
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ParseError(Exception):
|
||||||
|
"""Exception to signal the parser is stuck."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, msg: str, type: int | None, value: str | None, context: Context
|
||||||
|
) -> None:
|
||||||
|
Exception.__init__(
|
||||||
|
self, f"{msg}: type={type!r}, value={value!r}, context={context!r}"
|
||||||
|
)
|
||||||
|
self.msg = msg
|
||||||
|
self.type = type
|
||||||
|
self.value = value
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
|
||||||
|
class Parser:
|
||||||
|
"""Parser engine.
|
||||||
|
|
||||||
|
The proper usage sequence is:
|
||||||
|
|
||||||
|
p = Parser(grammar, [converter]) # create instance
|
||||||
|
p.setup([start]) # prepare for parsing
|
||||||
|
<for each input token>:
|
||||||
|
if p.addtoken(...): # parse a token; may raise ParseError
|
||||||
|
break
|
||||||
|
root = p.rootnode # root of abstract syntax tree
|
||||||
|
|
||||||
|
A Parser instance may be reused by calling setup() repeatedly.
|
||||||
|
|
||||||
|
A Parser instance contains state pertaining to the current token
|
||||||
|
sequence, and should not be used concurrently by different threads
|
||||||
|
to parse separate token sequences.
|
||||||
|
|
||||||
|
See driver.py for how to get input tokens by tokenizing a file or
|
||||||
|
string.
|
||||||
|
|
||||||
|
Parsing is complete when addtoken() returns True; the root of the
|
||||||
|
abstract syntax tree can then be retrieved from the rootnode
|
||||||
|
instance variable. When a syntax error occurs, addtoken() raises
|
||||||
|
the ParseError exception. There is no error recovery; the parser
|
||||||
|
cannot be used after a syntax error was reported (but it can be
|
||||||
|
reinitialized by calling setup()).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, grammar: Grammar, convert: Convert | None = None) -> None:
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
The grammar argument is a grammar.Grammar instance; see the
|
||||||
|
grammar module for more information.
|
||||||
|
|
||||||
|
The parser is not ready yet for parsing; you must call the
|
||||||
|
setup() method to get it started.
|
||||||
|
|
||||||
|
The optional convert argument is a function mapping concrete
|
||||||
|
syntax tree nodes to abstract syntax tree nodes. If not
|
||||||
|
given, no conversion is done and the syntax tree produced is
|
||||||
|
the concrete syntax tree. If given, it must be a function of
|
||||||
|
two arguments, the first being the grammar (a grammar.Grammar
|
||||||
|
instance), and the second being the concrete syntax tree node
|
||||||
|
to be converted. The syntax tree is converted from the bottom
|
||||||
|
up.
|
||||||
|
|
||||||
|
**post-note: the convert argument is ignored since for Black's
|
||||||
|
usage, convert will always be blib2to3.pytree.convert. Allowing
|
||||||
|
this to be dynamic hurts mypyc's ability to use early binding.
|
||||||
|
These docs are left for historical and informational value.
|
||||||
|
|
||||||
|
A concrete syntax tree node is a (type, value, context, nodes)
|
||||||
|
tuple, where type is the node type (a token or symbol number),
|
||||||
|
value is None for symbols and a string for tokens, context is
|
||||||
|
None or an opaque value used for error reporting (typically a
|
||||||
|
(lineno, offset) pair), and nodes is a list of children for
|
||||||
|
symbols, and None for tokens.
|
||||||
|
|
||||||
|
An abstract syntax tree node may be anything; this is entirely
|
||||||
|
up to the converter function.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.grammar = grammar
|
||||||
|
# See note in docstring above. TL;DR this is ignored.
|
||||||
|
self.convert = convert or lam_sub
|
||||||
|
self.is_backtracking = False
|
||||||
|
self.last_token: int | None = None
|
||||||
|
|
||||||
|
def setup(self, proxy: "TokenProxy", start: int | None = None) -> None:
|
||||||
|
"""Prepare for parsing.
|
||||||
|
|
||||||
|
This *must* be called before starting to parse.
|
||||||
|
|
||||||
|
The optional argument is an alternative start symbol; it
|
||||||
|
defaults to the grammar's start symbol.
|
||||||
|
|
||||||
|
You can use a Parser instance to parse any number of programs;
|
||||||
|
each time you call setup() the parser is reset to an initial
|
||||||
|
state determined by the (implicit or explicit) start symbol.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if start is None:
|
||||||
|
start = self.grammar.start
|
||||||
|
# Each stack entry is a tuple: (dfa, state, node).
|
||||||
|
# A node is a tuple: (type, value, context, children),
|
||||||
|
# where children is a list of nodes or None, and context may be None.
|
||||||
|
newnode: RawNode = (start, None, None, [])
|
||||||
|
stackentry = (self.grammar.dfas[start], 0, newnode)
|
||||||
|
self.stack: list[tuple[DFAS, int, RawNode]] = [stackentry]
|
||||||
|
self.rootnode: NL | None = None
|
||||||
|
self.used_names: set[str] = set()
|
||||||
|
self.proxy = proxy
|
||||||
|
self.last_token = None
|
||||||
|
|
||||||
|
def addtoken(self, type: int, value: str, context: Context) -> bool:
|
||||||
|
"""Add a token; return True iff this is the end of the program."""
|
||||||
|
# Map from token to label
|
||||||
|
ilabels = self.classify(type, value, context)
|
||||||
|
assert len(ilabels) >= 1
|
||||||
|
|
||||||
|
# If we have only one state to advance, we'll directly
|
||||||
|
# take it as is.
|
||||||
|
if len(ilabels) == 1:
|
||||||
|
[ilabel] = ilabels
|
||||||
|
return self._addtoken(ilabel, type, value, context)
|
||||||
|
|
||||||
|
# If there are multiple states which we can advance (only
|
||||||
|
# happen under soft-keywords), then we will try all of them
|
||||||
|
# in parallel and as soon as one state can reach further than
|
||||||
|
# the rest, we'll choose that one. This is a pretty hacky
|
||||||
|
# and hopefully temporary algorithm.
|
||||||
|
#
|
||||||
|
# For a more detailed explanation, check out this post:
|
||||||
|
# https://tree.science/what-the-backtracking.html
|
||||||
|
|
||||||
|
with self.proxy.release() as proxy:
|
||||||
|
counter, force = 0, False
|
||||||
|
recorder = Recorder(self, ilabels, context)
|
||||||
|
recorder.add_token(type, value, raw=True)
|
||||||
|
|
||||||
|
next_token_value = value
|
||||||
|
while recorder.determine_route(next_token_value) is None:
|
||||||
|
if not proxy.can_advance(counter):
|
||||||
|
force = True
|
||||||
|
break
|
||||||
|
|
||||||
|
next_token_type, next_token_value, *_ = proxy.eat(counter)
|
||||||
|
if next_token_type in (tokenize.COMMENT, tokenize.NL):
|
||||||
|
counter += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if next_token_type == tokenize.OP:
|
||||||
|
next_token_type = grammar.opmap[next_token_value]
|
||||||
|
|
||||||
|
recorder.add_token(next_token_type, next_token_value)
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
ilabel = cast(int, recorder.determine_route(next_token_value, force=force))
|
||||||
|
assert ilabel is not None
|
||||||
|
|
||||||
|
return self._addtoken(ilabel, type, value, context)
|
||||||
|
|
||||||
|
def _addtoken(self, ilabel: int, type: int, value: str, context: Context) -> bool:
|
||||||
|
# Loop until the token is shifted; may raise exceptions
|
||||||
|
while True:
|
||||||
|
dfa, state, node = self.stack[-1]
|
||||||
|
states, first = dfa
|
||||||
|
arcs = states[state]
|
||||||
|
# Look for a state with this label
|
||||||
|
for i, newstate in arcs:
|
||||||
|
t = self.grammar.labels[i][0]
|
||||||
|
if t >= 256:
|
||||||
|
# See if it's a symbol and if we're in its first set
|
||||||
|
itsdfa = self.grammar.dfas[t]
|
||||||
|
itsstates, itsfirst = itsdfa
|
||||||
|
if ilabel in itsfirst:
|
||||||
|
# Push a symbol
|
||||||
|
self.push(t, itsdfa, newstate, context)
|
||||||
|
break # To continue the outer while loop
|
||||||
|
|
||||||
|
elif ilabel == i:
|
||||||
|
# Look it up in the list of labels
|
||||||
|
# Shift a token; we're done with it
|
||||||
|
self.shift(type, value, newstate, context)
|
||||||
|
# Pop while we are in an accept-only state
|
||||||
|
state = newstate
|
||||||
|
while states[state] == [(0, state)]:
|
||||||
|
self.pop()
|
||||||
|
if not self.stack:
|
||||||
|
# Done parsing!
|
||||||
|
return True
|
||||||
|
dfa, state, node = self.stack[-1]
|
||||||
|
states, first = dfa
|
||||||
|
# Done with this token
|
||||||
|
self.last_token = type
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
if (0, state) in arcs:
|
||||||
|
# An accepting state, pop it and try something else
|
||||||
|
self.pop()
|
||||||
|
if not self.stack:
|
||||||
|
# Done parsing, but another token is input
|
||||||
|
raise ParseError("too much input", type, value, context)
|
||||||
|
else:
|
||||||
|
# No success finding a transition
|
||||||
|
raise ParseError("bad input", type, value, context)
|
||||||
|
|
||||||
|
def classify(self, type: int, value: str, context: Context) -> list[int]:
|
||||||
|
"""Turn a token into a label. (Internal)
|
||||||
|
|
||||||
|
Depending on whether the value is a soft-keyword or not,
|
||||||
|
this function may return multiple labels to choose from."""
|
||||||
|
if type == token.NAME:
|
||||||
|
# Keep a listing of all used names
|
||||||
|
self.used_names.add(value)
|
||||||
|
# Check for reserved words
|
||||||
|
if value in self.grammar.keywords:
|
||||||
|
return [self.grammar.keywords[value]]
|
||||||
|
elif value in self.grammar.soft_keywords:
|
||||||
|
assert type in self.grammar.tokens
|
||||||
|
# Current soft keywords (match, case, type) can only appear at the
|
||||||
|
# beginning of a statement. So as a shortcut, don't try to treat them
|
||||||
|
# like keywords in any other context.
|
||||||
|
# ('_' is also a soft keyword in the real grammar, but for our grammar
|
||||||
|
# it's just an expression, so we don't need to treat it specially.)
|
||||||
|
if self.last_token not in (
|
||||||
|
None,
|
||||||
|
token.INDENT,
|
||||||
|
token.DEDENT,
|
||||||
|
token.NEWLINE,
|
||||||
|
token.SEMI,
|
||||||
|
token.COLON,
|
||||||
|
):
|
||||||
|
return [self.grammar.tokens[type]]
|
||||||
|
return [
|
||||||
|
self.grammar.tokens[type],
|
||||||
|
self.grammar.soft_keywords[value],
|
||||||
|
]
|
||||||
|
|
||||||
|
ilabel = self.grammar.tokens.get(type)
|
||||||
|
if ilabel is None:
|
||||||
|
raise ParseError("bad token", type, value, context)
|
||||||
|
return [ilabel]
|
||||||
|
|
||||||
|
def shift(self, type: int, value: str, newstate: int, context: Context) -> None:
|
||||||
|
"""Shift a token. (Internal)"""
|
||||||
|
if self.is_backtracking:
|
||||||
|
dfa, state, _ = self.stack[-1]
|
||||||
|
self.stack[-1] = (dfa, newstate, DUMMY_NODE)
|
||||||
|
else:
|
||||||
|
dfa, state, node = self.stack[-1]
|
||||||
|
rawnode: RawNode = (type, value, context, None)
|
||||||
|
newnode = convert(self.grammar, rawnode)
|
||||||
|
assert node[-1] is not None
|
||||||
|
node[-1].append(newnode)
|
||||||
|
self.stack[-1] = (dfa, newstate, node)
|
||||||
|
|
||||||
|
def push(self, type: int, newdfa: DFAS, newstate: int, context: Context) -> None:
|
||||||
|
"""Push a nonterminal. (Internal)"""
|
||||||
|
if self.is_backtracking:
|
||||||
|
dfa, state, _ = self.stack[-1]
|
||||||
|
self.stack[-1] = (dfa, newstate, DUMMY_NODE)
|
||||||
|
self.stack.append((newdfa, 0, DUMMY_NODE))
|
||||||
|
else:
|
||||||
|
dfa, state, node = self.stack[-1]
|
||||||
|
newnode: RawNode = (type, None, context, [])
|
||||||
|
self.stack[-1] = (dfa, newstate, node)
|
||||||
|
self.stack.append((newdfa, 0, newnode))
|
||||||
|
|
||||||
|
def pop(self) -> None:
|
||||||
|
"""Pop a nonterminal. (Internal)"""
|
||||||
|
if self.is_backtracking:
|
||||||
|
self.stack.pop()
|
||||||
|
else:
|
||||||
|
popdfa, popstate, popnode = self.stack.pop()
|
||||||
|
newnode = convert(self.grammar, popnode)
|
||||||
|
if self.stack:
|
||||||
|
dfa, state, node = self.stack[-1]
|
||||||
|
assert node[-1] is not None
|
||||||
|
node[-1].append(newnode)
|
||||||
|
else:
|
||||||
|
self.rootnode = newnode
|
||||||
|
self.rootnode.used_names = self.used_names
|
||||||
411
.venv_codegen/Lib/site-packages/blib2to3/pgen2/pgen.py
Normal file
411
.venv_codegen/Lib/site-packages/blib2to3/pgen2/pgen.py
Normal file
|
|
@ -0,0 +1,411 @@
|
||||||
|
# Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from collections.abc import Iterator, Sequence
|
||||||
|
from typing import IO, Any, NoReturn, Union
|
||||||
|
|
||||||
|
from blib2to3.pgen2 import grammar, token, tokenize
|
||||||
|
from blib2to3.pgen2.tokenize import TokenInfo
|
||||||
|
|
||||||
|
Path = Union[str, "os.PathLike[str]"]
|
||||||
|
|
||||||
|
|
||||||
|
class PgenGrammar(grammar.Grammar):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ParserGenerator:
|
||||||
|
filename: Path
|
||||||
|
stream: IO[str]
|
||||||
|
generator: Iterator[TokenInfo]
|
||||||
|
first: dict[str, dict[str, int] | None]
|
||||||
|
|
||||||
|
def __init__(self, filename: Path, stream: IO[str] | None = None) -> None:
|
||||||
|
close_stream = None
|
||||||
|
if stream is None:
|
||||||
|
stream = open(filename, encoding="utf-8")
|
||||||
|
close_stream = stream.close
|
||||||
|
self.filename = filename
|
||||||
|
self.generator = tokenize.tokenize(stream.read())
|
||||||
|
self.gettoken() # Initialize lookahead
|
||||||
|
self.dfas, self.startsymbol = self.parse()
|
||||||
|
if close_stream is not None:
|
||||||
|
close_stream()
|
||||||
|
self.first = {} # map from symbol name to set of tokens
|
||||||
|
self.addfirstsets()
|
||||||
|
|
||||||
|
def make_grammar(self) -> PgenGrammar:
|
||||||
|
c = PgenGrammar()
|
||||||
|
names = list(self.dfas.keys())
|
||||||
|
names.sort()
|
||||||
|
names.remove(self.startsymbol)
|
||||||
|
names.insert(0, self.startsymbol)
|
||||||
|
for name in names:
|
||||||
|
i = 256 + len(c.symbol2number)
|
||||||
|
c.symbol2number[name] = i
|
||||||
|
c.number2symbol[i] = name
|
||||||
|
for name in names:
|
||||||
|
dfa = self.dfas[name]
|
||||||
|
states = []
|
||||||
|
for state in dfa:
|
||||||
|
arcs = []
|
||||||
|
for label, next in sorted(state.arcs.items()):
|
||||||
|
arcs.append((self.make_label(c, label), dfa.index(next)))
|
||||||
|
if state.isfinal:
|
||||||
|
arcs.append((0, dfa.index(state)))
|
||||||
|
states.append(arcs)
|
||||||
|
c.states.append(states)
|
||||||
|
c.dfas[c.symbol2number[name]] = (states, self.make_first(c, name))
|
||||||
|
c.start = c.symbol2number[self.startsymbol]
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make_first(self, c: PgenGrammar, name: str) -> dict[int, int]:
|
||||||
|
rawfirst = self.first[name]
|
||||||
|
assert rawfirst is not None
|
||||||
|
first = {}
|
||||||
|
for label in sorted(rawfirst):
|
||||||
|
ilabel = self.make_label(c, label)
|
||||||
|
##assert ilabel not in first # XXX failed on <> ... !=
|
||||||
|
first[ilabel] = 1
|
||||||
|
return first
|
||||||
|
|
||||||
|
def make_label(self, c: PgenGrammar, label: str) -> int:
|
||||||
|
# XXX Maybe this should be a method on a subclass of converter?
|
||||||
|
ilabel = len(c.labels)
|
||||||
|
if label[0].isalpha():
|
||||||
|
# Either a symbol name or a named token
|
||||||
|
if label in c.symbol2number:
|
||||||
|
# A symbol name (a non-terminal)
|
||||||
|
if label in c.symbol2label:
|
||||||
|
return c.symbol2label[label]
|
||||||
|
else:
|
||||||
|
c.labels.append((c.symbol2number[label], None))
|
||||||
|
c.symbol2label[label] = ilabel
|
||||||
|
return ilabel
|
||||||
|
else:
|
||||||
|
# A named token (NAME, NUMBER, STRING)
|
||||||
|
itoken = getattr(token, label, None)
|
||||||
|
assert isinstance(itoken, int), label
|
||||||
|
assert itoken in token.tok_name, label
|
||||||
|
if itoken in c.tokens:
|
||||||
|
return c.tokens[itoken]
|
||||||
|
else:
|
||||||
|
c.labels.append((itoken, None))
|
||||||
|
c.tokens[itoken] = ilabel
|
||||||
|
return ilabel
|
||||||
|
else:
|
||||||
|
# Either a keyword or an operator
|
||||||
|
assert label[0] in ('"', "'"), label
|
||||||
|
value = eval(label)
|
||||||
|
if value[0].isalpha():
|
||||||
|
if label[0] == '"':
|
||||||
|
keywords = c.soft_keywords
|
||||||
|
else:
|
||||||
|
keywords = c.keywords
|
||||||
|
|
||||||
|
# A keyword
|
||||||
|
if value in keywords:
|
||||||
|
return keywords[value]
|
||||||
|
else:
|
||||||
|
c.labels.append((token.NAME, value))
|
||||||
|
keywords[value] = ilabel
|
||||||
|
return ilabel
|
||||||
|
else:
|
||||||
|
# An operator (any non-numeric token)
|
||||||
|
itoken = grammar.opmap[value] # Fails if unknown token
|
||||||
|
if itoken in c.tokens:
|
||||||
|
return c.tokens[itoken]
|
||||||
|
else:
|
||||||
|
c.labels.append((itoken, None))
|
||||||
|
c.tokens[itoken] = ilabel
|
||||||
|
return ilabel
|
||||||
|
|
||||||
|
def addfirstsets(self) -> None:
|
||||||
|
names = list(self.dfas.keys())
|
||||||
|
names.sort()
|
||||||
|
for name in names:
|
||||||
|
if name not in self.first:
|
||||||
|
self.calcfirst(name)
|
||||||
|
# print name, self.first[name].keys()
|
||||||
|
|
||||||
|
def calcfirst(self, name: str) -> None:
|
||||||
|
dfa = self.dfas[name]
|
||||||
|
self.first[name] = None # dummy to detect left recursion
|
||||||
|
state = dfa[0]
|
||||||
|
totalset: dict[str, int] = {}
|
||||||
|
overlapcheck = {}
|
||||||
|
for label in state.arcs:
|
||||||
|
if label in self.dfas:
|
||||||
|
if label in self.first:
|
||||||
|
fset = self.first[label]
|
||||||
|
if fset is None:
|
||||||
|
raise ValueError(f"recursion for rule {name!r}")
|
||||||
|
else:
|
||||||
|
self.calcfirst(label)
|
||||||
|
fset = self.first[label]
|
||||||
|
assert fset is not None
|
||||||
|
totalset.update(fset)
|
||||||
|
overlapcheck[label] = fset
|
||||||
|
else:
|
||||||
|
totalset[label] = 1
|
||||||
|
overlapcheck[label] = {label: 1}
|
||||||
|
inverse: dict[str, str] = {}
|
||||||
|
for label, itsfirst in overlapcheck.items():
|
||||||
|
for symbol in itsfirst:
|
||||||
|
if symbol in inverse:
|
||||||
|
raise ValueError(
|
||||||
|
f"rule {name} is ambiguous; {symbol} is in the first sets of"
|
||||||
|
f" {label} as well as {inverse[symbol]}"
|
||||||
|
)
|
||||||
|
inverse[symbol] = label
|
||||||
|
self.first[name] = totalset
|
||||||
|
|
||||||
|
def parse(self) -> tuple[dict[str, list["DFAState"]], str]:
|
||||||
|
dfas = {}
|
||||||
|
startsymbol: str | None = None
|
||||||
|
# MSTART: (NEWLINE | RULE)* ENDMARKER
|
||||||
|
while self.type != token.ENDMARKER:
|
||||||
|
while self.type == token.NEWLINE:
|
||||||
|
self.gettoken()
|
||||||
|
# RULE: NAME ':' RHS NEWLINE
|
||||||
|
name = self.expect(token.NAME)
|
||||||
|
self.expect(token.OP, ":")
|
||||||
|
a, z = self.parse_rhs()
|
||||||
|
self.expect(token.NEWLINE)
|
||||||
|
# self.dump_nfa(name, a, z)
|
||||||
|
dfa = self.make_dfa(a, z)
|
||||||
|
# self.dump_dfa(name, dfa)
|
||||||
|
# oldlen = len(dfa)
|
||||||
|
self.simplify_dfa(dfa)
|
||||||
|
# newlen = len(dfa)
|
||||||
|
dfas[name] = dfa
|
||||||
|
# print name, oldlen, newlen
|
||||||
|
if startsymbol is None:
|
||||||
|
startsymbol = name
|
||||||
|
assert startsymbol is not None
|
||||||
|
return dfas, startsymbol
|
||||||
|
|
||||||
|
def make_dfa(self, start: "NFAState", finish: "NFAState") -> list["DFAState"]:
|
||||||
|
# To turn an NFA into a DFA, we define the states of the DFA
|
||||||
|
# to correspond to *sets* of states of the NFA. Then do some
|
||||||
|
# state reduction. Let's represent sets as dicts with 1 for
|
||||||
|
# values.
|
||||||
|
assert isinstance(start, NFAState)
|
||||||
|
assert isinstance(finish, NFAState)
|
||||||
|
|
||||||
|
def closure(state: NFAState) -> dict[NFAState, int]:
|
||||||
|
base: dict[NFAState, int] = {}
|
||||||
|
addclosure(state, base)
|
||||||
|
return base
|
||||||
|
|
||||||
|
def addclosure(state: NFAState, base: dict[NFAState, int]) -> None:
|
||||||
|
assert isinstance(state, NFAState)
|
||||||
|
if state in base:
|
||||||
|
return
|
||||||
|
base[state] = 1
|
||||||
|
for label, next in state.arcs:
|
||||||
|
if label is None:
|
||||||
|
addclosure(next, base)
|
||||||
|
|
||||||
|
states = [DFAState(closure(start), finish)]
|
||||||
|
for state in states: # NB states grows while we're iterating
|
||||||
|
arcs: dict[str, dict[NFAState, int]] = {}
|
||||||
|
for nfastate in state.nfaset:
|
||||||
|
for label, next in nfastate.arcs:
|
||||||
|
if label is not None:
|
||||||
|
addclosure(next, arcs.setdefault(label, {}))
|
||||||
|
for label, nfaset in sorted(arcs.items()):
|
||||||
|
for st in states:
|
||||||
|
if st.nfaset == nfaset:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
st = DFAState(nfaset, finish)
|
||||||
|
states.append(st)
|
||||||
|
state.addarc(st, label)
|
||||||
|
return states # List of DFAState instances; first one is start
|
||||||
|
|
||||||
|
def dump_nfa(self, name: str, start: "NFAState", finish: "NFAState") -> None:
|
||||||
|
print("Dump of NFA for", name)
|
||||||
|
todo = [start]
|
||||||
|
for i, state in enumerate(todo):
|
||||||
|
print(" State", i, state is finish and "(final)" or "")
|
||||||
|
for label, next in state.arcs:
|
||||||
|
if next in todo:
|
||||||
|
j = todo.index(next)
|
||||||
|
else:
|
||||||
|
j = len(todo)
|
||||||
|
todo.append(next)
|
||||||
|
if label is None:
|
||||||
|
print(f" -> {j}")
|
||||||
|
else:
|
||||||
|
print(f" {label} -> {j}")
|
||||||
|
|
||||||
|
def dump_dfa(self, name: str, dfa: Sequence["DFAState"]) -> None:
|
||||||
|
print("Dump of DFA for", name)
|
||||||
|
for i, state in enumerate(dfa):
|
||||||
|
print(" State", i, state.isfinal and "(final)" or "")
|
||||||
|
for label, next in sorted(state.arcs.items()):
|
||||||
|
print(f" {label} -> {dfa.index(next)}")
|
||||||
|
|
||||||
|
def simplify_dfa(self, dfa: list["DFAState"]) -> None:
|
||||||
|
# This is not theoretically optimal, but works well enough.
|
||||||
|
# Algorithm: repeatedly look for two states that have the same
|
||||||
|
# set of arcs (same labels pointing to the same nodes) and
|
||||||
|
# unify them, until things stop changing.
|
||||||
|
|
||||||
|
# dfa is a list of DFAState instances
|
||||||
|
changes = True
|
||||||
|
while changes:
|
||||||
|
changes = False
|
||||||
|
for i, state_i in enumerate(dfa):
|
||||||
|
for j in range(i + 1, len(dfa)):
|
||||||
|
state_j = dfa[j]
|
||||||
|
if state_i == state_j:
|
||||||
|
# print " unify", i, j
|
||||||
|
del dfa[j]
|
||||||
|
for state in dfa:
|
||||||
|
state.unifystate(state_j, state_i)
|
||||||
|
changes = True
|
||||||
|
break
|
||||||
|
|
||||||
|
def parse_rhs(self) -> tuple["NFAState", "NFAState"]:
|
||||||
|
# RHS: ALT ('|' ALT)*
|
||||||
|
a, z = self.parse_alt()
|
||||||
|
if self.value != "|":
|
||||||
|
return a, z
|
||||||
|
else:
|
||||||
|
aa = NFAState()
|
||||||
|
zz = NFAState()
|
||||||
|
aa.addarc(a)
|
||||||
|
z.addarc(zz)
|
||||||
|
while self.value == "|":
|
||||||
|
self.gettoken()
|
||||||
|
a, z = self.parse_alt()
|
||||||
|
aa.addarc(a)
|
||||||
|
z.addarc(zz)
|
||||||
|
return aa, zz
|
||||||
|
|
||||||
|
def parse_alt(self) -> tuple["NFAState", "NFAState"]:
|
||||||
|
# ALT: ITEM+
|
||||||
|
a, b = self.parse_item()
|
||||||
|
while self.value in ("(", "[") or self.type in (token.NAME, token.STRING):
|
||||||
|
c, d = self.parse_item()
|
||||||
|
b.addarc(c)
|
||||||
|
b = d
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
def parse_item(self) -> tuple["NFAState", "NFAState"]:
|
||||||
|
# ITEM: '[' RHS ']' | ATOM ['+' | '*']
|
||||||
|
if self.value == "[":
|
||||||
|
self.gettoken()
|
||||||
|
a, z = self.parse_rhs()
|
||||||
|
self.expect(token.OP, "]")
|
||||||
|
a.addarc(z)
|
||||||
|
return a, z
|
||||||
|
else:
|
||||||
|
a, z = self.parse_atom()
|
||||||
|
value = self.value
|
||||||
|
if value not in ("+", "*"):
|
||||||
|
return a, z
|
||||||
|
self.gettoken()
|
||||||
|
z.addarc(a)
|
||||||
|
if value == "+":
|
||||||
|
return a, z
|
||||||
|
else:
|
||||||
|
return a, a
|
||||||
|
|
||||||
|
def parse_atom(self) -> tuple["NFAState", "NFAState"]:
|
||||||
|
# ATOM: '(' RHS ')' | NAME | STRING
|
||||||
|
if self.value == "(":
|
||||||
|
self.gettoken()
|
||||||
|
a, z = self.parse_rhs()
|
||||||
|
self.expect(token.OP, ")")
|
||||||
|
return a, z
|
||||||
|
elif self.type in (token.NAME, token.STRING):
|
||||||
|
a = NFAState()
|
||||||
|
z = NFAState()
|
||||||
|
a.addarc(z, self.value)
|
||||||
|
self.gettoken()
|
||||||
|
return a, z
|
||||||
|
else:
|
||||||
|
self.raise_error(
|
||||||
|
f"expected (...) or NAME or STRING, got {self.type}/{self.value}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def expect(self, type: int, value: Any | None = None) -> str:
|
||||||
|
if self.type != type or (value is not None and self.value != value):
|
||||||
|
self.raise_error(f"expected {type}/{value}, got {self.type}/{self.value}")
|
||||||
|
value = self.value
|
||||||
|
self.gettoken()
|
||||||
|
return value
|
||||||
|
|
||||||
|
def gettoken(self) -> None:
|
||||||
|
tup = next(self.generator)
|
||||||
|
while tup[0] in (tokenize.COMMENT, tokenize.NL):
|
||||||
|
tup = next(self.generator)
|
||||||
|
self.type, self.value, self.begin, self.end, self.line = tup
|
||||||
|
# print token.tok_name[self.type], repr(self.value)
|
||||||
|
|
||||||
|
def raise_error(self, msg: str) -> NoReturn:
|
||||||
|
raise SyntaxError(
|
||||||
|
msg, (str(self.filename), self.end[0], self.end[1], self.line)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NFAState:
|
||||||
|
arcs: list[tuple[str | None, "NFAState"]]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.arcs = [] # list of (label, NFAState) pairs
|
||||||
|
|
||||||
|
def addarc(self, next: "NFAState", label: str | None = None) -> None:
|
||||||
|
assert label is None or isinstance(label, str)
|
||||||
|
assert isinstance(next, NFAState)
|
||||||
|
self.arcs.append((label, next))
|
||||||
|
|
||||||
|
|
||||||
|
class DFAState:
|
||||||
|
nfaset: dict[NFAState, Any]
|
||||||
|
isfinal: bool
|
||||||
|
arcs: dict[str, "DFAState"]
|
||||||
|
|
||||||
|
def __init__(self, nfaset: dict[NFAState, Any], final: NFAState) -> None:
|
||||||
|
assert isinstance(nfaset, dict)
|
||||||
|
assert isinstance(next(iter(nfaset)), NFAState)
|
||||||
|
assert isinstance(final, NFAState)
|
||||||
|
self.nfaset = nfaset
|
||||||
|
self.isfinal = final in nfaset
|
||||||
|
self.arcs = {} # map from label to DFAState
|
||||||
|
|
||||||
|
def addarc(self, next: "DFAState", label: str) -> None:
|
||||||
|
assert isinstance(label, str)
|
||||||
|
assert label not in self.arcs
|
||||||
|
assert isinstance(next, DFAState)
|
||||||
|
self.arcs[label] = next
|
||||||
|
|
||||||
|
def unifystate(self, old: "DFAState", new: "DFAState") -> None:
|
||||||
|
for label, next in self.arcs.items():
|
||||||
|
if next is old:
|
||||||
|
self.arcs[label] = new
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
# Equality test -- ignore the nfaset instance variable
|
||||||
|
assert isinstance(other, DFAState)
|
||||||
|
if self.isfinal != other.isfinal:
|
||||||
|
return False
|
||||||
|
# Can't just return self.arcs == other.arcs, because that
|
||||||
|
# would invoke this method recursively, with cycles...
|
||||||
|
if len(self.arcs) != len(other.arcs):
|
||||||
|
return False
|
||||||
|
for label, next in self.arcs.items():
|
||||||
|
if next is not other.arcs.get(label):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
__hash__: Any = None # For Py3 compatibility.
|
||||||
|
|
||||||
|
|
||||||
|
def generate_grammar(filename: Path = "Grammar.txt") -> PgenGrammar:
|
||||||
|
p = ParserGenerator(filename)
|
||||||
|
return p.make_grammar()
|
||||||
95
.venv_codegen/Lib/site-packages/blib2to3/pgen2/token.py
Normal file
95
.venv_codegen/Lib/site-packages/blib2to3/pgen2/token.py
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
"""Token constants (from "token.h")."""
|
||||||
|
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
# Taken from Python (r53757) and modified to include some tokens
|
||||||
|
# originally monkeypatched in by pgen2.tokenize
|
||||||
|
|
||||||
|
# --start constants--
|
||||||
|
ENDMARKER: Final = 0
|
||||||
|
NAME: Final = 1
|
||||||
|
NUMBER: Final = 2
|
||||||
|
STRING: Final = 3
|
||||||
|
NEWLINE: Final = 4
|
||||||
|
INDENT: Final = 5
|
||||||
|
DEDENT: Final = 6
|
||||||
|
LPAR: Final = 7
|
||||||
|
RPAR: Final = 8
|
||||||
|
LSQB: Final = 9
|
||||||
|
RSQB: Final = 10
|
||||||
|
COLON: Final = 11
|
||||||
|
COMMA: Final = 12
|
||||||
|
SEMI: Final = 13
|
||||||
|
PLUS: Final = 14
|
||||||
|
MINUS: Final = 15
|
||||||
|
STAR: Final = 16
|
||||||
|
SLASH: Final = 17
|
||||||
|
VBAR: Final = 18
|
||||||
|
AMPER: Final = 19
|
||||||
|
LESS: Final = 20
|
||||||
|
GREATER: Final = 21
|
||||||
|
EQUAL: Final = 22
|
||||||
|
DOT: Final = 23
|
||||||
|
PERCENT: Final = 24
|
||||||
|
BACKQUOTE: Final = 25
|
||||||
|
LBRACE: Final = 26
|
||||||
|
RBRACE: Final = 27
|
||||||
|
EQEQUAL: Final = 28
|
||||||
|
NOTEQUAL: Final = 29
|
||||||
|
LESSEQUAL: Final = 30
|
||||||
|
GREATEREQUAL: Final = 31
|
||||||
|
TILDE: Final = 32
|
||||||
|
CIRCUMFLEX: Final = 33
|
||||||
|
LEFTSHIFT: Final = 34
|
||||||
|
RIGHTSHIFT: Final = 35
|
||||||
|
DOUBLESTAR: Final = 36
|
||||||
|
PLUSEQUAL: Final = 37
|
||||||
|
MINEQUAL: Final = 38
|
||||||
|
STAREQUAL: Final = 39
|
||||||
|
SLASHEQUAL: Final = 40
|
||||||
|
PERCENTEQUAL: Final = 41
|
||||||
|
AMPEREQUAL: Final = 42
|
||||||
|
VBAREQUAL: Final = 43
|
||||||
|
CIRCUMFLEXEQUAL: Final = 44
|
||||||
|
LEFTSHIFTEQUAL: Final = 45
|
||||||
|
RIGHTSHIFTEQUAL: Final = 46
|
||||||
|
DOUBLESTAREQUAL: Final = 47
|
||||||
|
DOUBLESLASH: Final = 48
|
||||||
|
DOUBLESLASHEQUAL: Final = 49
|
||||||
|
AT: Final = 50
|
||||||
|
ATEQUAL: Final = 51
|
||||||
|
OP: Final = 52
|
||||||
|
COMMENT: Final = 53
|
||||||
|
NL: Final = 54
|
||||||
|
RARROW: Final = 55
|
||||||
|
AWAIT: Final = 56
|
||||||
|
ASYNC: Final = 57
|
||||||
|
ERRORTOKEN: Final = 58
|
||||||
|
COLONEQUAL: Final = 59
|
||||||
|
FSTRING_START: Final = 60
|
||||||
|
FSTRING_MIDDLE: Final = 61
|
||||||
|
FSTRING_END: Final = 62
|
||||||
|
BANG: Final = 63
|
||||||
|
TSTRING_START: Final = 64
|
||||||
|
TSTRING_MIDDLE: Final = 65
|
||||||
|
TSTRING_END: Final = 66
|
||||||
|
N_TOKENS: Final = 67
|
||||||
|
NT_OFFSET: Final = 256
|
||||||
|
# --end constants--
|
||||||
|
|
||||||
|
tok_name: Final[dict[int, str]] = {}
|
||||||
|
for _name, _value in list(globals().items()):
|
||||||
|
if type(_value) is int:
|
||||||
|
tok_name[_value] = _name
|
||||||
|
|
||||||
|
|
||||||
|
def ISTERMINAL(x: int) -> bool:
|
||||||
|
return x < NT_OFFSET
|
||||||
|
|
||||||
|
|
||||||
|
def ISNONTERMINAL(x: int) -> bool:
|
||||||
|
return x >= NT_OFFSET
|
||||||
|
|
||||||
|
|
||||||
|
def ISEOF(x: int) -> bool:
|
||||||
|
return x == ENDMARKER
|
||||||
226
.venv_codegen/Lib/site-packages/blib2to3/pgen2/tokenize.py
Normal file
226
.venv_codegen/Lib/site-packages/blib2to3/pgen2/tokenize.py
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation.
|
||||||
|
# All rights reserved.
|
||||||
|
|
||||||
|
# mypy: allow-untyped-defs, allow-untyped-calls
|
||||||
|
|
||||||
|
"""Tokenization help for Python programs.
|
||||||
|
|
||||||
|
generate_tokens(readline) is a generator that breaks a stream of
|
||||||
|
text into Python tokens. It accepts a readline-like method which is called
|
||||||
|
repeatedly to get the next line of input (or "" for EOF). It generates
|
||||||
|
5-tuples with these members:
|
||||||
|
|
||||||
|
the token type (see token.py)
|
||||||
|
the token (a string)
|
||||||
|
the starting (row, column) indices of the token (a 2-tuple of ints)
|
||||||
|
the ending (row, column) indices of the token (a 2-tuple of ints)
|
||||||
|
the original line (string)
|
||||||
|
|
||||||
|
It is designed to match the working of the Python tokenizer exactly, except
|
||||||
|
that it produces COMMENT tokens for comments and gives type OP for all
|
||||||
|
operators
|
||||||
|
|
||||||
|
Older entry points
|
||||||
|
tokenize_loop(readline, tokeneater)
|
||||||
|
tokenize(readline, tokeneater=printtoken)
|
||||||
|
are the same, except instead of generating tokens, tokeneater is a callback
|
||||||
|
function to which the 5 fields described above are passed as 5 arguments,
|
||||||
|
each time a new token is found."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
from blib2to3.pgen2.grammar import Grammar
|
||||||
|
from blib2to3.pgen2.token import (
|
||||||
|
ASYNC,
|
||||||
|
AWAIT,
|
||||||
|
COMMENT,
|
||||||
|
DEDENT,
|
||||||
|
ENDMARKER,
|
||||||
|
FSTRING_END,
|
||||||
|
FSTRING_MIDDLE,
|
||||||
|
FSTRING_START,
|
||||||
|
INDENT,
|
||||||
|
NAME,
|
||||||
|
NEWLINE,
|
||||||
|
NL,
|
||||||
|
NUMBER,
|
||||||
|
OP,
|
||||||
|
STRING,
|
||||||
|
TSTRING_END,
|
||||||
|
TSTRING_MIDDLE,
|
||||||
|
TSTRING_START,
|
||||||
|
tok_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
__author__ = "Ka-Ping Yee <ping@lfw.org>"
|
||||||
|
__credits__ = "GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, Skip Montanaro"
|
||||||
|
|
||||||
|
import pytokens
|
||||||
|
from pytokens import TokenType
|
||||||
|
|
||||||
|
from . import token as _token
|
||||||
|
|
||||||
|
__all__ = [x for x in dir(_token) if x[0] != "_"] + [
|
||||||
|
"tokenize",
|
||||||
|
"generate_tokens",
|
||||||
|
"untokenize",
|
||||||
|
]
|
||||||
|
del _token
|
||||||
|
|
||||||
|
Coord = tuple[int, int]
|
||||||
|
TokenInfo = tuple[int, str, Coord, Coord, str]
|
||||||
|
|
||||||
|
TOKEN_TYPE_MAP = {
|
||||||
|
TokenType.indent: INDENT,
|
||||||
|
TokenType.dedent: DEDENT,
|
||||||
|
TokenType.newline: NEWLINE,
|
||||||
|
TokenType.nl: NL,
|
||||||
|
TokenType.comment: COMMENT,
|
||||||
|
TokenType.semicolon: OP,
|
||||||
|
TokenType.lparen: OP,
|
||||||
|
TokenType.rparen: OP,
|
||||||
|
TokenType.lbracket: OP,
|
||||||
|
TokenType.rbracket: OP,
|
||||||
|
TokenType.lbrace: OP,
|
||||||
|
TokenType.rbrace: OP,
|
||||||
|
TokenType.colon: OP,
|
||||||
|
TokenType.op: OP,
|
||||||
|
TokenType.identifier: NAME,
|
||||||
|
TokenType.number: NUMBER,
|
||||||
|
TokenType.string: STRING,
|
||||||
|
TokenType.fstring_start: FSTRING_START,
|
||||||
|
TokenType.fstring_middle: FSTRING_MIDDLE,
|
||||||
|
TokenType.fstring_end: FSTRING_END,
|
||||||
|
TokenType.tstring_start: TSTRING_START,
|
||||||
|
TokenType.tstring_middle: TSTRING_MIDDLE,
|
||||||
|
TokenType.tstring_end: TSTRING_END,
|
||||||
|
TokenType.endmarker: ENDMARKER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TokenError(Exception): ...
|
||||||
|
|
||||||
|
|
||||||
|
def transform_whitespace(
|
||||||
|
token: pytokens.Token, source: str, prev_token: pytokens.Token | None
|
||||||
|
) -> pytokens.Token:
|
||||||
|
r"""
|
||||||
|
Black treats `\\\n` at the end of a line as a 'NL' token, while it
|
||||||
|
is ignored as whitespace in the regular Python parser.
|
||||||
|
But, only the first one. If there's a `\\\n` following it
|
||||||
|
(as in, a \ just by itself on a line), that is not made into NL.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
token.type == TokenType.whitespace
|
||||||
|
and prev_token is not None
|
||||||
|
and prev_token.type not in (TokenType.nl, TokenType.newline)
|
||||||
|
):
|
||||||
|
token_str = source[token.start_index : token.end_index]
|
||||||
|
if token_str.startswith("\\\r\n"):
|
||||||
|
return pytokens.Token(
|
||||||
|
TokenType.nl,
|
||||||
|
token.start_index,
|
||||||
|
token.start_index + 3,
|
||||||
|
token.start_line,
|
||||||
|
token.start_col,
|
||||||
|
token.start_line,
|
||||||
|
token.start_col + 3,
|
||||||
|
)
|
||||||
|
elif token_str.startswith("\\\n") or token_str.startswith("\\\r"):
|
||||||
|
return pytokens.Token(
|
||||||
|
TokenType.nl,
|
||||||
|
token.start_index,
|
||||||
|
token.start_index + 2,
|
||||||
|
token.start_line,
|
||||||
|
token.start_col,
|
||||||
|
token.start_line,
|
||||||
|
token.start_col + 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def tokenize(source: str, grammar: Grammar | None = None) -> Iterator[TokenInfo]:
|
||||||
|
lines = source.split("\n")
|
||||||
|
lines += [""] # For newline tokens in files that don't end in a newline
|
||||||
|
line, column = 1, 0
|
||||||
|
|
||||||
|
prev_token: pytokens.Token | None = None
|
||||||
|
try:
|
||||||
|
for token in pytokens.tokenize(source):
|
||||||
|
token = transform_whitespace(token, source, prev_token)
|
||||||
|
|
||||||
|
line, column = token.start_line, token.start_col
|
||||||
|
if token.type == TokenType.whitespace:
|
||||||
|
continue
|
||||||
|
|
||||||
|
token_str = source[token.start_index : token.end_index]
|
||||||
|
|
||||||
|
if token.type == TokenType.newline and token_str == "":
|
||||||
|
# Black doesn't yield empty newline tokens at the end of a file
|
||||||
|
# if there's no newline at the end of a file.
|
||||||
|
prev_token = token
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_line = lines[token.start_line - 1]
|
||||||
|
|
||||||
|
if token.type == TokenType.identifier and token_str in ("async", "await"):
|
||||||
|
# Black uses `async` and `await` token types just for those two keywords
|
||||||
|
yield (
|
||||||
|
ASYNC if token_str == "async" else AWAIT,
|
||||||
|
token_str,
|
||||||
|
(token.start_line, token.start_col),
|
||||||
|
(token.end_line, token.end_col),
|
||||||
|
source_line,
|
||||||
|
)
|
||||||
|
elif token.type == TokenType.op and token_str == "...":
|
||||||
|
# Black doesn't have an ellipsis token yet, yield 3 DOTs instead
|
||||||
|
assert token.start_line == token.end_line
|
||||||
|
assert token.end_col == token.start_col + 3
|
||||||
|
|
||||||
|
token_str = "."
|
||||||
|
for start_col in range(token.start_col, token.start_col + 3):
|
||||||
|
end_col = start_col + 1
|
||||||
|
yield (
|
||||||
|
TOKEN_TYPE_MAP[token.type],
|
||||||
|
token_str,
|
||||||
|
(token.start_line, start_col),
|
||||||
|
(token.end_line, end_col),
|
||||||
|
source_line,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
token_type = TOKEN_TYPE_MAP.get(token.type)
|
||||||
|
if token_type is None:
|
||||||
|
raise ValueError(f"Unknown token type: {token.type!r}")
|
||||||
|
yield (
|
||||||
|
TOKEN_TYPE_MAP[token.type],
|
||||||
|
token_str,
|
||||||
|
(token.start_line, token.start_col),
|
||||||
|
(token.end_line, token.end_col),
|
||||||
|
source_line,
|
||||||
|
)
|
||||||
|
prev_token = token
|
||||||
|
|
||||||
|
except pytokens.UnexpectedEOF:
|
||||||
|
raise TokenError("Unexpected EOF in multi-line statement", (line, column))
|
||||||
|
except pytokens.TokenizeError as exc:
|
||||||
|
raise TokenError(f"Failed to parse: {type(exc).__name__}", (line, column))
|
||||||
|
|
||||||
|
|
||||||
|
def printtoken(
|
||||||
|
type: int, token: str, srow_col: Coord, erow_col: Coord, line: str
|
||||||
|
) -> None: # for testing
|
||||||
|
srow, scol = srow_col
|
||||||
|
erow, ecol = erow_col
|
||||||
|
print(f"{srow},{scol}-{erow},{ecol}:\t{tok_name[type]}\t{token!r}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # testing
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
token_iterator = tokenize(open(sys.argv[1]).read())
|
||||||
|
else:
|
||||||
|
token_iterator = tokenize(sys.stdin.read())
|
||||||
|
|
||||||
|
for tok in token_iterator:
|
||||||
|
printtoken(*tok)
|
||||||
208
.venv_codegen/Lib/site-packages/blib2to3/pygram.py
Normal file
208
.venv_codegen/Lib/site-packages/blib2to3/pygram.py
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
# Copyright 2006 Google, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""Export the Python grammar and symbols."""
|
||||||
|
|
||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
# Local imports
|
||||||
|
from .pgen2 import driver
|
||||||
|
from .pgen2.grammar import Grammar
|
||||||
|
|
||||||
|
# Moved into initialize because mypyc can't handle __file__ (XXX bug)
|
||||||
|
# # The grammar file
|
||||||
|
# _GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "Grammar.txt")
|
||||||
|
# _PATTERN_GRAMMAR_FILE = os.path.join(os.path.dirname(__file__),
|
||||||
|
# "PatternGrammar.txt")
|
||||||
|
|
||||||
|
|
||||||
|
class Symbols:
|
||||||
|
def __init__(self, grammar: Grammar) -> None:
|
||||||
|
"""Initializer.
|
||||||
|
|
||||||
|
Creates an attribute for each grammar symbol (nonterminal),
|
||||||
|
whose value is the symbol's type (an int >= 256).
|
||||||
|
"""
|
||||||
|
for name, symbol in grammar.symbol2number.items():
|
||||||
|
setattr(self, name, symbol)
|
||||||
|
|
||||||
|
|
||||||
|
class _python_symbols(Symbols):
|
||||||
|
and_expr: int
|
||||||
|
and_test: int
|
||||||
|
annassign: int
|
||||||
|
arglist: int
|
||||||
|
argument: int
|
||||||
|
arith_expr: int
|
||||||
|
asexpr_test: int
|
||||||
|
assert_stmt: int
|
||||||
|
async_funcdef: int
|
||||||
|
async_stmt: int
|
||||||
|
atom: int
|
||||||
|
augassign: int
|
||||||
|
break_stmt: int
|
||||||
|
case_block: int
|
||||||
|
classdef: int
|
||||||
|
comp_for: int
|
||||||
|
comp_if: int
|
||||||
|
comp_iter: int
|
||||||
|
comp_op: int
|
||||||
|
comparison: int
|
||||||
|
compound_stmt: int
|
||||||
|
continue_stmt: int
|
||||||
|
decorated: int
|
||||||
|
decorator: int
|
||||||
|
decorators: int
|
||||||
|
del_stmt: int
|
||||||
|
dictsetmaker: int
|
||||||
|
dotted_as_name: int
|
||||||
|
dotted_as_names: int
|
||||||
|
dotted_name: int
|
||||||
|
encoding_decl: int
|
||||||
|
eval_input: int
|
||||||
|
except_clause: int
|
||||||
|
expr: int
|
||||||
|
expr_stmt: int
|
||||||
|
exprlist: int
|
||||||
|
factor: int
|
||||||
|
file_input: int
|
||||||
|
flow_stmt: int
|
||||||
|
for_stmt: int
|
||||||
|
fstring: int
|
||||||
|
fstring_format_spec: int
|
||||||
|
fstring_middle: int
|
||||||
|
fstring_replacement_field: int
|
||||||
|
funcdef: int
|
||||||
|
global_stmt: int
|
||||||
|
guard: int
|
||||||
|
if_stmt: int
|
||||||
|
import_as_name: int
|
||||||
|
import_as_names: int
|
||||||
|
import_from: int
|
||||||
|
import_name: int
|
||||||
|
import_stmt: int
|
||||||
|
lambdef: int
|
||||||
|
listmaker: int
|
||||||
|
match_stmt: int
|
||||||
|
namedexpr_test: int
|
||||||
|
not_test: int
|
||||||
|
old_comp_for: int
|
||||||
|
old_comp_if: int
|
||||||
|
old_comp_iter: int
|
||||||
|
old_lambdef: int
|
||||||
|
old_test: int
|
||||||
|
or_test: int
|
||||||
|
parameters: int
|
||||||
|
paramspec: int
|
||||||
|
pass_stmt: int
|
||||||
|
pattern: int
|
||||||
|
patterns: int
|
||||||
|
power: int
|
||||||
|
raise_stmt: int
|
||||||
|
return_stmt: int
|
||||||
|
shift_expr: int
|
||||||
|
simple_stmt: int
|
||||||
|
single_input: int
|
||||||
|
sliceop: int
|
||||||
|
small_stmt: int
|
||||||
|
subject_expr: int
|
||||||
|
star_expr: int
|
||||||
|
stmt: int
|
||||||
|
subscript: int
|
||||||
|
subscriptlist: int
|
||||||
|
suite: int
|
||||||
|
term: int
|
||||||
|
test: int
|
||||||
|
testlist: int
|
||||||
|
testlist1: int
|
||||||
|
testlist_gexp: int
|
||||||
|
testlist_safe: int
|
||||||
|
testlist_star_expr: int
|
||||||
|
tfpdef: int
|
||||||
|
tfplist: int
|
||||||
|
tname: int
|
||||||
|
tname_star: int
|
||||||
|
trailer: int
|
||||||
|
try_stmt: int
|
||||||
|
tstring: int
|
||||||
|
tstring_format_spec: int
|
||||||
|
tstring_middle: int
|
||||||
|
tstring_replacement_field: int
|
||||||
|
type_stmt: int
|
||||||
|
typedargslist: int
|
||||||
|
typeparam: int
|
||||||
|
typeparams: int
|
||||||
|
typevar: int
|
||||||
|
typevartuple: int
|
||||||
|
varargslist: int
|
||||||
|
vfpdef: int
|
||||||
|
vfplist: int
|
||||||
|
vname: int
|
||||||
|
while_stmt: int
|
||||||
|
with_stmt: int
|
||||||
|
xor_expr: int
|
||||||
|
yield_arg: int
|
||||||
|
yield_expr: int
|
||||||
|
yield_stmt: int
|
||||||
|
|
||||||
|
|
||||||
|
class _pattern_symbols(Symbols):
|
||||||
|
Alternative: int
|
||||||
|
Alternatives: int
|
||||||
|
Details: int
|
||||||
|
Matcher: int
|
||||||
|
NegatedUnit: int
|
||||||
|
Repeater: int
|
||||||
|
Unit: int
|
||||||
|
|
||||||
|
|
||||||
|
python_grammar: Grammar
|
||||||
|
python_grammar_async_keywords: Grammar
|
||||||
|
python_grammar_soft_keywords: Grammar
|
||||||
|
pattern_grammar: Grammar
|
||||||
|
python_symbols: _python_symbols
|
||||||
|
pattern_symbols: _pattern_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(cache_dir: Union[str, "os.PathLike[str]", None] = None) -> None:
|
||||||
|
global python_grammar
|
||||||
|
global python_grammar_async_keywords
|
||||||
|
global python_grammar_soft_keywords
|
||||||
|
global python_symbols
|
||||||
|
global pattern_grammar
|
||||||
|
global pattern_symbols
|
||||||
|
|
||||||
|
# The grammar file
|
||||||
|
_GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "Grammar.txt")
|
||||||
|
_PATTERN_GRAMMAR_FILE = os.path.join(
|
||||||
|
os.path.dirname(__file__), "PatternGrammar.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
python_grammar = driver.load_packaged_grammar("blib2to3", _GRAMMAR_FILE, cache_dir)
|
||||||
|
assert "print" not in python_grammar.keywords
|
||||||
|
assert "exec" not in python_grammar.keywords
|
||||||
|
|
||||||
|
soft_keywords = python_grammar.soft_keywords.copy()
|
||||||
|
python_grammar.soft_keywords.clear()
|
||||||
|
|
||||||
|
python_symbols = _python_symbols(python_grammar)
|
||||||
|
|
||||||
|
# Python 3.0-3.6
|
||||||
|
python_grammar.version = (3, 0)
|
||||||
|
|
||||||
|
# Python 3.7+
|
||||||
|
python_grammar_async_keywords = python_grammar.copy()
|
||||||
|
python_grammar_async_keywords.async_keywords = True
|
||||||
|
python_grammar_async_keywords.version = (3, 7)
|
||||||
|
|
||||||
|
# Python 3.10+
|
||||||
|
python_grammar_soft_keywords = python_grammar_async_keywords.copy()
|
||||||
|
python_grammar_soft_keywords.soft_keywords = soft_keywords
|
||||||
|
python_grammar_soft_keywords.version = (3, 10)
|
||||||
|
|
||||||
|
pattern_grammar = driver.load_packaged_grammar(
|
||||||
|
"blib2to3", _PATTERN_GRAMMAR_FILE, cache_dir
|
||||||
|
)
|
||||||
|
pattern_symbols = _pattern_symbols(pattern_grammar)
|
||||||
971
.venv_codegen/Lib/site-packages/blib2to3/pytree.py
Normal file
971
.venv_codegen/Lib/site-packages/blib2to3/pytree.py
Normal file
|
|
@ -0,0 +1,971 @@
|
||||||
|
# Copyright 2006 Google, Inc. All Rights Reserved.
|
||||||
|
# Licensed to PSF under a Contributor Agreement.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Python parse tree definitions.
|
||||||
|
|
||||||
|
This is a very concrete parse tree; we need to keep every token and
|
||||||
|
even the comments and whitespace between tokens.
|
||||||
|
|
||||||
|
There's also a pattern matching implementation here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# mypy: allow-untyped-defs, allow-incomplete-defs
|
||||||
|
|
||||||
|
from collections.abc import Iterable, Iterator
|
||||||
|
from typing import Any, Optional, TypeVar, Union
|
||||||
|
|
||||||
|
from blib2to3.pgen2.grammar import Grammar
|
||||||
|
|
||||||
|
__author__ = "Guido van Rossum <guido@python.org>"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
HUGE: int = 0x7FFFFFFF # maximum repeat count, default max
|
||||||
|
|
||||||
|
_type_reprs: dict[int, str | int] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def type_repr(type_num: int) -> str | int:
|
||||||
|
global _type_reprs
|
||||||
|
if not _type_reprs:
|
||||||
|
from . import pygram
|
||||||
|
|
||||||
|
if not hasattr(pygram, "python_symbols"):
|
||||||
|
pygram.initialize(cache_dir=None)
|
||||||
|
|
||||||
|
# printing tokens is possible but not as useful
|
||||||
|
# from .pgen2 import token // token.__dict__.items():
|
||||||
|
for name in dir(pygram.python_symbols):
|
||||||
|
val = getattr(pygram.python_symbols, name)
|
||||||
|
if type(val) == int:
|
||||||
|
_type_reprs[val] = name
|
||||||
|
return _type_reprs.setdefault(type_num, type_num)
|
||||||
|
|
||||||
|
|
||||||
|
_P = TypeVar("_P", bound="Base")
|
||||||
|
|
||||||
|
NL = Union["Node", "Leaf"]
|
||||||
|
Context = tuple[str, tuple[int, int]]
|
||||||
|
RawNode = tuple[int, Optional[str], Optional[Context], Optional[list[NL]]]
|
||||||
|
|
||||||
|
|
||||||
|
class Base:
|
||||||
|
"""
|
||||||
|
Abstract base class for Node and Leaf.
|
||||||
|
|
||||||
|
This provides some default functionality and boilerplate using the
|
||||||
|
template pattern.
|
||||||
|
|
||||||
|
A node may be a subnode of at most one parent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Default values for instance variables
|
||||||
|
type: int # int: token number (< 256) or symbol number (>= 256)
|
||||||
|
parent: Optional["Node"] = None # Parent node pointer, or None
|
||||||
|
children: list[NL] # List of subnodes
|
||||||
|
was_changed: bool = False
|
||||||
|
was_checked: bool = False
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwds):
|
||||||
|
"""Constructor that prevents Base from being instantiated."""
|
||||||
|
assert cls is not Base, "Cannot instantiate Base"
|
||||||
|
return object.__new__(cls)
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Compare two nodes for equality.
|
||||||
|
|
||||||
|
This calls the method _eq().
|
||||||
|
"""
|
||||||
|
if self.__class__ is not other.__class__:
|
||||||
|
return NotImplemented
|
||||||
|
return self._eq(other)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prefix(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _eq(self: _P, other: _P) -> bool:
|
||||||
|
"""
|
||||||
|
Compare two nodes for equality.
|
||||||
|
|
||||||
|
This is called by __eq__ and __ne__. It is only called if the two nodes
|
||||||
|
have the same type. This must be implemented by the concrete subclass.
|
||||||
|
Nodes should be considered equal if they have the same structure,
|
||||||
|
ignoring the prefix string and other context information.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __deepcopy__(self: _P, memo: Any) -> _P:
|
||||||
|
return self.clone()
|
||||||
|
|
||||||
|
def clone(self: _P) -> _P:
|
||||||
|
"""
|
||||||
|
Return a cloned (deep) copy of self.
|
||||||
|
|
||||||
|
This must be implemented by the concrete subclass.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def post_order(self) -> Iterator[NL]:
|
||||||
|
"""
|
||||||
|
Return a post-order iterator for the tree.
|
||||||
|
|
||||||
|
This must be implemented by the concrete subclass.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def pre_order(self) -> Iterator[NL]:
|
||||||
|
"""
|
||||||
|
Return a pre-order iterator for the tree.
|
||||||
|
|
||||||
|
This must be implemented by the concrete subclass.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def replace(self, new: NL | list[NL]) -> None:
|
||||||
|
"""Replace this node with a new one in the parent."""
|
||||||
|
assert self.parent is not None, str(self)
|
||||||
|
assert new is not None
|
||||||
|
if not isinstance(new, list):
|
||||||
|
new = [new]
|
||||||
|
l_children = []
|
||||||
|
found = False
|
||||||
|
for ch in self.parent.children:
|
||||||
|
if ch is self:
|
||||||
|
assert not found, (self.parent.children, self, new)
|
||||||
|
if new is not None:
|
||||||
|
l_children.extend(new)
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
l_children.append(ch)
|
||||||
|
assert found, (self.children, self, new)
|
||||||
|
self.parent.children = l_children
|
||||||
|
self.parent.changed()
|
||||||
|
self.parent.invalidate_sibling_maps()
|
||||||
|
for x in new:
|
||||||
|
x.parent = self.parent
|
||||||
|
self.parent = None
|
||||||
|
|
||||||
|
def get_lineno(self) -> int | None:
|
||||||
|
"""Return the line number which generated the invocant node."""
|
||||||
|
node = self
|
||||||
|
while not isinstance(node, Leaf):
|
||||||
|
if not node.children:
|
||||||
|
return None
|
||||||
|
node = node.children[0]
|
||||||
|
return node.lineno
|
||||||
|
|
||||||
|
def changed(self) -> None:
|
||||||
|
if self.was_changed:
|
||||||
|
return
|
||||||
|
if self.parent:
|
||||||
|
self.parent.changed()
|
||||||
|
self.was_changed = True
|
||||||
|
|
||||||
|
def remove(self) -> int | None:
|
||||||
|
"""
|
||||||
|
Remove the node from the tree. Returns the position of the node in its
|
||||||
|
parent's children before it was removed.
|
||||||
|
"""
|
||||||
|
if self.parent:
|
||||||
|
for i, node in enumerate(self.parent.children):
|
||||||
|
if node is self:
|
||||||
|
del self.parent.children[i]
|
||||||
|
self.parent.changed()
|
||||||
|
self.parent.invalidate_sibling_maps()
|
||||||
|
self.parent = None
|
||||||
|
return i
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next_sibling(self) -> NL | None:
|
||||||
|
"""
|
||||||
|
The node immediately following the invocant in their parent's children
|
||||||
|
list. If the invocant does not have a next sibling, it is None
|
||||||
|
"""
|
||||||
|
if self.parent is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.parent.next_sibling_map is None:
|
||||||
|
self.parent.update_sibling_maps()
|
||||||
|
assert self.parent.next_sibling_map is not None
|
||||||
|
return self.parent.next_sibling_map[id(self)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prev_sibling(self) -> NL | None:
|
||||||
|
"""
|
||||||
|
The node immediately preceding the invocant in their parent's children
|
||||||
|
list. If the invocant does not have a previous sibling, it is None.
|
||||||
|
"""
|
||||||
|
if self.parent is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.parent.prev_sibling_map is None:
|
||||||
|
self.parent.update_sibling_maps()
|
||||||
|
assert self.parent.prev_sibling_map is not None
|
||||||
|
return self.parent.prev_sibling_map[id(self)]
|
||||||
|
|
||||||
|
def leaves(self) -> Iterator["Leaf"]:
|
||||||
|
for child in self.children:
|
||||||
|
yield from child.leaves()
|
||||||
|
|
||||||
|
def depth(self) -> int:
|
||||||
|
if self.parent is None:
|
||||||
|
return 0
|
||||||
|
return 1 + self.parent.depth()
|
||||||
|
|
||||||
|
def get_suffix(self) -> str:
|
||||||
|
"""
|
||||||
|
Return the string immediately following the invocant node. This is
|
||||||
|
effectively equivalent to node.next_sibling.prefix
|
||||||
|
"""
|
||||||
|
next_sib = self.next_sibling
|
||||||
|
if next_sib is None:
|
||||||
|
return ""
|
||||||
|
prefix = next_sib.prefix
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
|
||||||
|
class Node(Base):
|
||||||
|
"""Concrete implementation for interior nodes."""
|
||||||
|
|
||||||
|
fixers_applied: list[Any] | None
|
||||||
|
used_names: set[str] | None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: int,
|
||||||
|
children: list[NL],
|
||||||
|
context: Any | None = None,
|
||||||
|
prefix: str | None = None,
|
||||||
|
fixers_applied: list[Any] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
Takes a type constant (a symbol number >= 256), a sequence of
|
||||||
|
child nodes, and an optional context keyword argument.
|
||||||
|
|
||||||
|
As a side effect, the parent pointers of the children are updated.
|
||||||
|
"""
|
||||||
|
assert type >= 256, type
|
||||||
|
self.type = type
|
||||||
|
self.children = list(children)
|
||||||
|
for ch in self.children:
|
||||||
|
assert ch.parent is None, repr(ch)
|
||||||
|
ch.parent = self
|
||||||
|
self.invalidate_sibling_maps()
|
||||||
|
if prefix is not None:
|
||||||
|
self.prefix = prefix
|
||||||
|
if fixers_applied:
|
||||||
|
self.fixers_applied = fixers_applied[:]
|
||||||
|
else:
|
||||||
|
self.fixers_applied = None
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Return a canonical string representation."""
|
||||||
|
assert self.type is not None
|
||||||
|
return f"{self.__class__.__name__}({type_repr(self.type)}, {self.children!r})"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""
|
||||||
|
Return a pretty string representation.
|
||||||
|
|
||||||
|
This reproduces the input source exactly.
|
||||||
|
"""
|
||||||
|
return "".join(map(str, self.children))
|
||||||
|
|
||||||
|
def _eq(self, other: Base) -> bool:
|
||||||
|
"""Compare two nodes for equality."""
|
||||||
|
return (self.type, self.children) == (other.type, other.children)
|
||||||
|
|
||||||
|
def clone(self) -> "Node":
|
||||||
|
assert self.type is not None
|
||||||
|
"""Return a cloned (deep) copy of self."""
|
||||||
|
return Node(
|
||||||
|
self.type,
|
||||||
|
[ch.clone() for ch in self.children],
|
||||||
|
fixers_applied=self.fixers_applied,
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_order(self) -> Iterator[NL]:
|
||||||
|
"""Return a post-order iterator for the tree."""
|
||||||
|
for child in self.children:
|
||||||
|
yield from child.post_order()
|
||||||
|
yield self
|
||||||
|
|
||||||
|
def pre_order(self) -> Iterator[NL]:
|
||||||
|
"""Return a pre-order iterator for the tree."""
|
||||||
|
yield self
|
||||||
|
for child in self.children:
|
||||||
|
yield from child.pre_order()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prefix(self) -> str:
|
||||||
|
"""
|
||||||
|
The whitespace and comments preceding this node in the input.
|
||||||
|
"""
|
||||||
|
if not self.children:
|
||||||
|
return ""
|
||||||
|
return self.children[0].prefix
|
||||||
|
|
||||||
|
@prefix.setter
|
||||||
|
def prefix(self, prefix: str) -> None:
|
||||||
|
if self.children:
|
||||||
|
self.children[0].prefix = prefix
|
||||||
|
|
||||||
|
def set_child(self, i: int, child: NL) -> None:
|
||||||
|
"""
|
||||||
|
Equivalent to 'node.children[i] = child'. This method also sets the
|
||||||
|
child's parent attribute appropriately.
|
||||||
|
"""
|
||||||
|
child.parent = self
|
||||||
|
self.children[i].parent = None
|
||||||
|
self.children[i] = child
|
||||||
|
self.changed()
|
||||||
|
self.invalidate_sibling_maps()
|
||||||
|
|
||||||
|
def insert_child(self, i: int, child: NL) -> None:
|
||||||
|
"""
|
||||||
|
Equivalent to 'node.children.insert(i, child)'. This method also sets
|
||||||
|
the child's parent attribute appropriately.
|
||||||
|
"""
|
||||||
|
child.parent = self
|
||||||
|
self.children.insert(i, child)
|
||||||
|
self.changed()
|
||||||
|
self.invalidate_sibling_maps()
|
||||||
|
|
||||||
|
def append_child(self, child: NL) -> None:
|
||||||
|
"""
|
||||||
|
Equivalent to 'node.children.append(child)'. This method also sets the
|
||||||
|
child's parent attribute appropriately.
|
||||||
|
"""
|
||||||
|
child.parent = self
|
||||||
|
self.children.append(child)
|
||||||
|
self.changed()
|
||||||
|
self.invalidate_sibling_maps()
|
||||||
|
|
||||||
|
def invalidate_sibling_maps(self) -> None:
|
||||||
|
self.prev_sibling_map: dict[int, NL | None] | None = None
|
||||||
|
self.next_sibling_map: dict[int, NL | None] | None = None
|
||||||
|
|
||||||
|
def update_sibling_maps(self) -> None:
|
||||||
|
_prev: dict[int, NL | None] = {}
|
||||||
|
_next: dict[int, NL | None] = {}
|
||||||
|
self.prev_sibling_map = _prev
|
||||||
|
self.next_sibling_map = _next
|
||||||
|
previous: NL | None = None
|
||||||
|
for current in self.children:
|
||||||
|
_prev[id(current)] = previous
|
||||||
|
_next[id(previous)] = current
|
||||||
|
previous = current
|
||||||
|
_next[id(current)] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Leaf(Base):
|
||||||
|
"""Concrete implementation for leaf nodes."""
|
||||||
|
|
||||||
|
# Default values for instance variables
|
||||||
|
value: str
|
||||||
|
fixers_applied: list[Any]
|
||||||
|
bracket_depth: int
|
||||||
|
# Changed later in brackets.py
|
||||||
|
opening_bracket: Optional["Leaf"] = None
|
||||||
|
used_names: set[str] | None
|
||||||
|
_prefix = "" # Whitespace and comments preceding this token in the input
|
||||||
|
lineno: int = 0 # Line where this token starts in the input
|
||||||
|
column: int = 0 # Column where this token starts in the input
|
||||||
|
# If not None, this Leaf is created by converting a block of fmt off/skip
|
||||||
|
# code, and `fmt_pass_converted_first_leaf` points to the first Leaf in the
|
||||||
|
# converted code.
|
||||||
|
fmt_pass_converted_first_leaf: Optional["Leaf"] = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: int,
|
||||||
|
value: str,
|
||||||
|
context: Context | None = None,
|
||||||
|
prefix: str | None = None,
|
||||||
|
fixers_applied: list[Any] = [],
|
||||||
|
opening_bracket: Optional["Leaf"] = None,
|
||||||
|
fmt_pass_converted_first_leaf: Optional["Leaf"] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
Takes a type constant (a token number < 256), a string value, and an
|
||||||
|
optional context keyword argument.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert 0 <= type < 256, type
|
||||||
|
if context is not None:
|
||||||
|
self._prefix, (self.lineno, self.column) = context
|
||||||
|
self.type = type
|
||||||
|
self.value = value
|
||||||
|
if prefix is not None:
|
||||||
|
self._prefix = prefix
|
||||||
|
self.fixers_applied: list[Any] | None = fixers_applied[:]
|
||||||
|
self.children = []
|
||||||
|
self.opening_bracket = opening_bracket
|
||||||
|
self.fmt_pass_converted_first_leaf = fmt_pass_converted_first_leaf
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Return a canonical string representation."""
|
||||||
|
from .pgen2.token import tok_name
|
||||||
|
|
||||||
|
assert self.type is not None
|
||||||
|
return (
|
||||||
|
f"{self.__class__.__name__}({tok_name.get(self.type, self.type)},"
|
||||||
|
f" {self.value!r})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""
|
||||||
|
Return a pretty string representation.
|
||||||
|
|
||||||
|
This reproduces the input source exactly.
|
||||||
|
"""
|
||||||
|
return self._prefix + str(self.value)
|
||||||
|
|
||||||
|
def _eq(self, other: "Leaf") -> bool:
|
||||||
|
"""Compare two nodes for equality."""
|
||||||
|
return (self.type, self.value) == (other.type, other.value)
|
||||||
|
|
||||||
|
def clone(self) -> "Leaf":
|
||||||
|
assert self.type is not None
|
||||||
|
"""Return a cloned (deep) copy of self."""
|
||||||
|
return Leaf(
|
||||||
|
self.type,
|
||||||
|
self.value,
|
||||||
|
(self.prefix, (self.lineno, self.column)),
|
||||||
|
fixers_applied=self.fixers_applied,
|
||||||
|
)
|
||||||
|
|
||||||
|
def leaves(self) -> Iterator["Leaf"]:
|
||||||
|
yield self
|
||||||
|
|
||||||
|
def post_order(self) -> Iterator["Leaf"]:
|
||||||
|
"""Return a post-order iterator for the tree."""
|
||||||
|
yield self
|
||||||
|
|
||||||
|
def pre_order(self) -> Iterator["Leaf"]:
|
||||||
|
"""Return a pre-order iterator for the tree."""
|
||||||
|
yield self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prefix(self) -> str:
|
||||||
|
"""
|
||||||
|
The whitespace and comments preceding this token in the input.
|
||||||
|
"""
|
||||||
|
return self._prefix
|
||||||
|
|
||||||
|
@prefix.setter
|
||||||
|
def prefix(self, prefix: str) -> None:
|
||||||
|
self.changed()
|
||||||
|
self._prefix = prefix
|
||||||
|
|
||||||
|
|
||||||
|
def convert(gr: Grammar, raw_node: RawNode) -> NL:
|
||||||
|
"""
|
||||||
|
Convert raw node information to a Node or Leaf instance.
|
||||||
|
|
||||||
|
This is passed to the parser driver which calls it whenever a reduction of a
|
||||||
|
grammar rule produces a new complete node, so that the tree is build
|
||||||
|
strictly bottom-up.
|
||||||
|
"""
|
||||||
|
type, value, context, children = raw_node
|
||||||
|
if children or type in gr.number2symbol:
|
||||||
|
# If there's exactly one child, return that child instead of
|
||||||
|
# creating a new node.
|
||||||
|
assert children is not None
|
||||||
|
if len(children) == 1:
|
||||||
|
return children[0]
|
||||||
|
return Node(type, children, context=context)
|
||||||
|
else:
|
||||||
|
return Leaf(type, value or "", context=context)
|
||||||
|
|
||||||
|
|
||||||
|
_Results = dict[str, NL]
|
||||||
|
|
||||||
|
|
||||||
|
class BasePattern:
|
||||||
|
"""
|
||||||
|
A pattern is a tree matching pattern.
|
||||||
|
|
||||||
|
It looks for a specific node type (token or symbol), and
|
||||||
|
optionally for a specific content.
|
||||||
|
|
||||||
|
This is an abstract base class. There are three concrete
|
||||||
|
subclasses:
|
||||||
|
|
||||||
|
- LeafPattern matches a single leaf node;
|
||||||
|
- NodePattern matches a single node (usually non-leaf);
|
||||||
|
- WildcardPattern matches a sequence of nodes of variable length.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Defaults for instance variables
|
||||||
|
type: int | None
|
||||||
|
type = None # Node type (token if < 256, symbol if >= 256)
|
||||||
|
content: Any = None # Optional content matching pattern
|
||||||
|
name: str | None = None # Optional name used to store match in results dict
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwds):
|
||||||
|
"""Constructor that prevents BasePattern from being instantiated."""
|
||||||
|
assert cls is not BasePattern, "Cannot instantiate BasePattern"
|
||||||
|
return object.__new__(cls)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
assert self.type is not None
|
||||||
|
args = [type_repr(self.type), self.content, self.name]
|
||||||
|
while args and args[-1] is None:
|
||||||
|
del args[-1]
|
||||||
|
return f"{self.__class__.__name__}({', '.join(map(repr, args))})"
|
||||||
|
|
||||||
|
def _submatch(self, node, results=None) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def optimize(self) -> "BasePattern":
|
||||||
|
"""
|
||||||
|
A subclass can define this as a hook for optimizations.
|
||||||
|
|
||||||
|
Returns either self or another node with the same effect.
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def match(self, node: NL, results: _Results | None = None) -> bool:
|
||||||
|
"""
|
||||||
|
Does this pattern exactly match a node?
|
||||||
|
|
||||||
|
Returns True if it matches, False if not.
|
||||||
|
|
||||||
|
If results is not None, it must be a dict which will be
|
||||||
|
updated with the nodes matching named subpatterns.
|
||||||
|
|
||||||
|
Default implementation for non-wildcard patterns.
|
||||||
|
"""
|
||||||
|
if self.type is not None and node.type != self.type:
|
||||||
|
return False
|
||||||
|
if self.content is not None:
|
||||||
|
r: _Results | None = None
|
||||||
|
if results is not None:
|
||||||
|
r = {}
|
||||||
|
if not self._submatch(node, r):
|
||||||
|
return False
|
||||||
|
if r:
|
||||||
|
assert results is not None
|
||||||
|
results.update(r)
|
||||||
|
if results is not None and self.name:
|
||||||
|
results[self.name] = node
|
||||||
|
return True
|
||||||
|
|
||||||
|
def match_seq(self, nodes: list[NL], results: _Results | None = None) -> bool:
|
||||||
|
"""
|
||||||
|
Does this pattern exactly match a sequence of nodes?
|
||||||
|
|
||||||
|
Default implementation for non-wildcard patterns.
|
||||||
|
"""
|
||||||
|
if len(nodes) != 1:
|
||||||
|
return False
|
||||||
|
return self.match(nodes[0], results)
|
||||||
|
|
||||||
|
def generate_matches(self, nodes: list[NL]) -> Iterator[tuple[int, _Results]]:
|
||||||
|
"""
|
||||||
|
Generator yielding all matches for this pattern.
|
||||||
|
|
||||||
|
Default implementation for non-wildcard patterns.
|
||||||
|
"""
|
||||||
|
r: _Results = {}
|
||||||
|
if nodes and self.match(nodes[0], r):
|
||||||
|
yield 1, r
|
||||||
|
|
||||||
|
|
||||||
|
class LeafPattern(BasePattern):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: int | None = None,
|
||||||
|
content: str | None = None,
|
||||||
|
name: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initializer. Takes optional type, content, and name.
|
||||||
|
|
||||||
|
The type, if given must be a token type (< 256). If not given,
|
||||||
|
this matches any *leaf* node; the content may still be required.
|
||||||
|
|
||||||
|
The content, if given, must be a string.
|
||||||
|
|
||||||
|
If a name is given, the matching node is stored in the results
|
||||||
|
dict under that key.
|
||||||
|
"""
|
||||||
|
if type is not None:
|
||||||
|
assert 0 <= type < 256, type
|
||||||
|
if content is not None:
|
||||||
|
assert isinstance(content, str), repr(content)
|
||||||
|
self.type = type
|
||||||
|
self.content = content
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def match(self, node: NL, results=None) -> bool:
|
||||||
|
"""Override match() to insist on a leaf node."""
|
||||||
|
if not isinstance(node, Leaf):
|
||||||
|
return False
|
||||||
|
return BasePattern.match(self, node, results)
|
||||||
|
|
||||||
|
def _submatch(self, node, results=None):
|
||||||
|
"""
|
||||||
|
Match the pattern's content to the node's children.
|
||||||
|
|
||||||
|
This assumes the node type matches and self.content is not None.
|
||||||
|
|
||||||
|
Returns True if it matches, False if not.
|
||||||
|
|
||||||
|
If results is not None, it must be a dict which will be
|
||||||
|
updated with the nodes matching named subpatterns.
|
||||||
|
|
||||||
|
When returning False, the results dict may still be updated.
|
||||||
|
"""
|
||||||
|
return self.content == node.value
|
||||||
|
|
||||||
|
|
||||||
|
class NodePattern(BasePattern):
|
||||||
|
wildcards: bool = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type: int | None = None,
|
||||||
|
content: Iterable[str] | None = None,
|
||||||
|
name: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initializer. Takes optional type, content, and name.
|
||||||
|
|
||||||
|
The type, if given, must be a symbol type (>= 256). If the
|
||||||
|
type is None this matches *any* single node (leaf or not),
|
||||||
|
except if content is not None, in which it only matches
|
||||||
|
non-leaf nodes that also match the content pattern.
|
||||||
|
|
||||||
|
The content, if not None, must be a sequence of Patterns that
|
||||||
|
must match the node's children exactly. If the content is
|
||||||
|
given, the type must not be None.
|
||||||
|
|
||||||
|
If a name is given, the matching node is stored in the results
|
||||||
|
dict under that key.
|
||||||
|
"""
|
||||||
|
if type is not None:
|
||||||
|
assert type >= 256, type
|
||||||
|
if content is not None:
|
||||||
|
assert not isinstance(content, str), repr(content)
|
||||||
|
newcontent = list(content)
|
||||||
|
for i, item in enumerate(newcontent):
|
||||||
|
assert isinstance(item, BasePattern), (i, item)
|
||||||
|
# I don't even think this code is used anywhere, but it does cause
|
||||||
|
# unreachable errors from mypy. This function's signature does look
|
||||||
|
# odd though *shrug*.
|
||||||
|
if isinstance(item, WildcardPattern): # type: ignore[unreachable]
|
||||||
|
self.wildcards = True # type: ignore[unreachable]
|
||||||
|
self.type = type
|
||||||
|
self.content = newcontent # TODO: this is unbound when content is None
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def _submatch(self, node, results=None) -> bool:
|
||||||
|
"""
|
||||||
|
Match the pattern's content to the node's children.
|
||||||
|
|
||||||
|
This assumes the node type matches and self.content is not None.
|
||||||
|
|
||||||
|
Returns True if it matches, False if not.
|
||||||
|
|
||||||
|
If results is not None, it must be a dict which will be
|
||||||
|
updated with the nodes matching named subpatterns.
|
||||||
|
|
||||||
|
When returning False, the results dict may still be updated.
|
||||||
|
"""
|
||||||
|
if self.wildcards:
|
||||||
|
for c, r in generate_matches(self.content, node.children):
|
||||||
|
if c == len(node.children):
|
||||||
|
if results is not None:
|
||||||
|
results.update(r)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if len(self.content) != len(node.children):
|
||||||
|
return False
|
||||||
|
for subpattern, child in zip(self.content, node.children):
|
||||||
|
if not subpattern.match(child, results):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class WildcardPattern(BasePattern):
|
||||||
|
"""
|
||||||
|
A wildcard pattern can match zero or more nodes.
|
||||||
|
|
||||||
|
This has all the flexibility needed to implement patterns like:
|
||||||
|
|
||||||
|
.* .+ .? .{m,n}
|
||||||
|
(a b c | d e | f)
|
||||||
|
(...)* (...)+ (...)? (...){m,n}
|
||||||
|
|
||||||
|
except it always uses non-greedy matching.
|
||||||
|
"""
|
||||||
|
|
||||||
|
min: int
|
||||||
|
max: int
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
content: str | None = None,
|
||||||
|
min: int = 0,
|
||||||
|
max: int = HUGE,
|
||||||
|
name: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content: optional sequence of subsequences of patterns;
|
||||||
|
if absent, matches one node;
|
||||||
|
if present, each subsequence is an alternative [*]
|
||||||
|
min: optional minimum number of times to match, default 0
|
||||||
|
max: optional maximum number of times to match, default HUGE
|
||||||
|
name: optional name assigned to this match
|
||||||
|
|
||||||
|
[*] Thus, if content is [[a, b, c], [d, e], [f, g, h]] this is
|
||||||
|
equivalent to (a b c | d e | f g h); if content is None,
|
||||||
|
this is equivalent to '.' in regular expression terms.
|
||||||
|
The min and max parameters work as follows:
|
||||||
|
min=0, max=maxint: .*
|
||||||
|
min=1, max=maxint: .+
|
||||||
|
min=0, max=1: .?
|
||||||
|
min=1, max=1: .
|
||||||
|
If content is not None, replace the dot with the parenthesized
|
||||||
|
list of alternatives, e.g. (a b c | d e | f g h)*
|
||||||
|
"""
|
||||||
|
assert 0 <= min <= max <= HUGE, (min, max)
|
||||||
|
if content is not None:
|
||||||
|
f = lambda s: tuple(s)
|
||||||
|
wrapped_content = tuple(map(f, content)) # Protect against alterations
|
||||||
|
# Check sanity of alternatives
|
||||||
|
assert len(wrapped_content), repr(
|
||||||
|
wrapped_content
|
||||||
|
) # Can't have zero alternatives
|
||||||
|
for alt in wrapped_content:
|
||||||
|
assert len(alt), repr(alt) # Can have empty alternatives
|
||||||
|
self.content = wrapped_content
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def optimize(self) -> Any:
|
||||||
|
"""Optimize certain stacked wildcard patterns."""
|
||||||
|
subpattern = None
|
||||||
|
if (
|
||||||
|
self.content is not None
|
||||||
|
and len(self.content) == 1
|
||||||
|
and len(self.content[0]) == 1
|
||||||
|
):
|
||||||
|
subpattern = self.content[0][0]
|
||||||
|
if self.min == 1 and self.max == 1:
|
||||||
|
if self.content is None:
|
||||||
|
return NodePattern(name=self.name)
|
||||||
|
if subpattern is not None and self.name == subpattern.name:
|
||||||
|
return subpattern.optimize()
|
||||||
|
if (
|
||||||
|
self.min <= 1
|
||||||
|
and isinstance(subpattern, WildcardPattern)
|
||||||
|
and subpattern.min <= 1
|
||||||
|
and self.name == subpattern.name
|
||||||
|
):
|
||||||
|
return WildcardPattern(
|
||||||
|
subpattern.content,
|
||||||
|
self.min * subpattern.min,
|
||||||
|
self.max * subpattern.max,
|
||||||
|
subpattern.name,
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def match(self, node, results=None) -> bool:
|
||||||
|
"""Does this pattern exactly match a node?"""
|
||||||
|
return self.match_seq([node], results)
|
||||||
|
|
||||||
|
def match_seq(self, nodes, results=None) -> bool:
|
||||||
|
"""Does this pattern exactly match a sequence of nodes?"""
|
||||||
|
for c, r in self.generate_matches(nodes):
|
||||||
|
if c == len(nodes):
|
||||||
|
if results is not None:
|
||||||
|
results.update(r)
|
||||||
|
if self.name:
|
||||||
|
results[self.name] = list(nodes)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def generate_matches(self, nodes) -> Iterator[tuple[int, _Results]]:
|
||||||
|
"""
|
||||||
|
Generator yielding matches for a sequence of nodes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodes: sequence of nodes
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
(count, results) tuples where:
|
||||||
|
count: the match comprises nodes[:count];
|
||||||
|
results: dict containing named submatches.
|
||||||
|
"""
|
||||||
|
if self.content is None:
|
||||||
|
# Shortcut for special case (see __init__.__doc__)
|
||||||
|
for count in range(self.min, 1 + min(len(nodes), self.max)):
|
||||||
|
r = {}
|
||||||
|
if self.name:
|
||||||
|
r[self.name] = nodes[:count]
|
||||||
|
yield count, r
|
||||||
|
elif self.name == "bare_name":
|
||||||
|
yield self._bare_name_matches(nodes)
|
||||||
|
else:
|
||||||
|
# The reason for this is that hitting the recursion limit usually
|
||||||
|
# results in some ugly messages about how RuntimeErrors are being
|
||||||
|
# ignored. We only have to do this on CPython, though, because other
|
||||||
|
# implementations don't have this nasty bug in the first place.
|
||||||
|
if hasattr(sys, "getrefcount"):
|
||||||
|
save_stderr = sys.stderr
|
||||||
|
sys.stderr = StringIO()
|
||||||
|
try:
|
||||||
|
for count, r in self._recursive_matches(nodes, 0):
|
||||||
|
if self.name:
|
||||||
|
r[self.name] = nodes[:count]
|
||||||
|
yield count, r
|
||||||
|
except RuntimeError:
|
||||||
|
# We fall back to the iterative pattern matching scheme if the recursive
|
||||||
|
# scheme hits the recursion limit.
|
||||||
|
for count, r in self._iterative_matches(nodes):
|
||||||
|
if self.name:
|
||||||
|
r[self.name] = nodes[:count]
|
||||||
|
yield count, r
|
||||||
|
finally:
|
||||||
|
if hasattr(sys, "getrefcount"):
|
||||||
|
sys.stderr = save_stderr
|
||||||
|
|
||||||
|
def _iterative_matches(self, nodes) -> Iterator[tuple[int, _Results]]:
|
||||||
|
"""Helper to iteratively yield the matches."""
|
||||||
|
nodelen = len(nodes)
|
||||||
|
if 0 >= self.min:
|
||||||
|
yield 0, {}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
# generate matches that use just one alt from self.content
|
||||||
|
for alt in self.content:
|
||||||
|
for c, r in generate_matches(alt, nodes):
|
||||||
|
yield c, r
|
||||||
|
results.append((c, r))
|
||||||
|
|
||||||
|
# for each match, iterate down the nodes
|
||||||
|
while results:
|
||||||
|
new_results = []
|
||||||
|
for c0, r0 in results:
|
||||||
|
# stop if the entire set of nodes has been matched
|
||||||
|
if c0 < nodelen and c0 <= self.max:
|
||||||
|
for alt in self.content:
|
||||||
|
for c1, r1 in generate_matches(alt, nodes[c0:]):
|
||||||
|
if c1 > 0:
|
||||||
|
r = {}
|
||||||
|
r.update(r0)
|
||||||
|
r.update(r1)
|
||||||
|
yield c0 + c1, r
|
||||||
|
new_results.append((c0 + c1, r))
|
||||||
|
results = new_results
|
||||||
|
|
||||||
|
def _bare_name_matches(self, nodes) -> tuple[int, _Results]:
|
||||||
|
"""Special optimized matcher for bare_name."""
|
||||||
|
count = 0
|
||||||
|
r = {} # type: _Results
|
||||||
|
done = False
|
||||||
|
max = len(nodes)
|
||||||
|
while not done and count < max:
|
||||||
|
done = True
|
||||||
|
for leaf in self.content:
|
||||||
|
if leaf[0].match(nodes[count], r):
|
||||||
|
count += 1
|
||||||
|
done = False
|
||||||
|
break
|
||||||
|
assert self.name is not None
|
||||||
|
r[self.name] = nodes[:count]
|
||||||
|
return count, r
|
||||||
|
|
||||||
|
def _recursive_matches(self, nodes, count) -> Iterator[tuple[int, _Results]]:
|
||||||
|
"""Helper to recursively yield the matches."""
|
||||||
|
assert self.content is not None
|
||||||
|
if count >= self.min:
|
||||||
|
yield 0, {}
|
||||||
|
if count < self.max:
|
||||||
|
for alt in self.content:
|
||||||
|
for c0, r0 in generate_matches(alt, nodes):
|
||||||
|
for c1, r1 in self._recursive_matches(nodes[c0:], count + 1):
|
||||||
|
r = {}
|
||||||
|
r.update(r0)
|
||||||
|
r.update(r1)
|
||||||
|
yield c0 + c1, r
|
||||||
|
|
||||||
|
|
||||||
|
class NegatedPattern(BasePattern):
|
||||||
|
def __init__(self, content: BasePattern | None = None) -> None:
|
||||||
|
"""
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
The argument is either a pattern or None. If it is None, this
|
||||||
|
only matches an empty sequence (effectively '$' in regex
|
||||||
|
lingo). If it is not None, this matches whenever the argument
|
||||||
|
pattern doesn't have any matches.
|
||||||
|
"""
|
||||||
|
if content is not None:
|
||||||
|
assert isinstance(content, BasePattern), repr(content)
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
def match(self, node, results=None) -> bool:
|
||||||
|
# We never match a node in its entirety
|
||||||
|
return False
|
||||||
|
|
||||||
|
def match_seq(self, nodes, results=None) -> bool:
|
||||||
|
# We only match an empty sequence of nodes in its entirety
|
||||||
|
return len(nodes) == 0
|
||||||
|
|
||||||
|
def generate_matches(self, nodes: list[NL]) -> Iterator[tuple[int, _Results]]:
|
||||||
|
if self.content is None:
|
||||||
|
# Return a match if there is an empty sequence
|
||||||
|
if len(nodes) == 0:
|
||||||
|
yield 0, {}
|
||||||
|
else:
|
||||||
|
# Return a match if the argument pattern has no matches
|
||||||
|
for c, r in self.content.generate_matches(nodes):
|
||||||
|
return
|
||||||
|
yield 0, {}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_matches(
|
||||||
|
patterns: list[BasePattern], nodes: list[NL]
|
||||||
|
) -> Iterator[tuple[int, _Results]]:
|
||||||
|
"""
|
||||||
|
Generator yielding matches for a sequence of patterns and nodes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
patterns: a sequence of patterns
|
||||||
|
nodes: a sequence of nodes
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
(count, results) tuples where:
|
||||||
|
count: the entire sequence of patterns matches nodes[:count];
|
||||||
|
results: dict containing named submatches.
|
||||||
|
"""
|
||||||
|
if not patterns:
|
||||||
|
yield 0, {}
|
||||||
|
else:
|
||||||
|
p, rest = patterns[0], patterns[1:]
|
||||||
|
for c0, r0 in p.generate_matches(nodes):
|
||||||
|
if not rest:
|
||||||
|
yield c0, r0
|
||||||
|
else:
|
||||||
|
for c1, r1 in generate_matches(rest, nodes[c0:]):
|
||||||
|
r = {}
|
||||||
|
r.update(r0)
|
||||||
|
r.update(r1)
|
||||||
|
yield c0 + c1, r
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pip
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: click
|
||||||
|
Version: 8.3.2
|
||||||
|
Summary: Composable command line interface toolkit
|
||||||
|
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||||
|
Requires-Python: >=3.10
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
License-Expression: BSD-3-Clause
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
License-File: LICENSE.txt
|
||||||
|
Requires-Dist: colorama; platform_system == 'Windows'
|
||||||
|
Project-URL: Changes, https://click.palletsprojects.com/page/changes/
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Project-URL: Documentation, https://click.palletsprojects.com/
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Source, https://github.com/pallets/click/
|
||||||
|
|
||||||
|
<div align="center"><img src="https://raw.githubusercontent.com/pallets/click/refs/heads/stable/docs/_static/click-name.svg" alt="" height="150"></div>
|
||||||
|
|
||||||
|
# Click
|
||||||
|
|
||||||
|
Click is a Python package for creating beautiful command line interfaces
|
||||||
|
in a composable way with as little code as necessary. It's the "Command
|
||||||
|
Line Interface Creation Kit". It's highly configurable but comes with
|
||||||
|
sensible defaults out of the box.
|
||||||
|
|
||||||
|
It aims to make the process of writing command line tools quick and fun
|
||||||
|
while also preventing any frustration caused by the inability to
|
||||||
|
implement an intended CLI API.
|
||||||
|
|
||||||
|
Click in three points:
|
||||||
|
|
||||||
|
- Arbitrary nesting of commands
|
||||||
|
- Automatic help page generation
|
||||||
|
- Supports lazy loading of subcommands at runtime
|
||||||
|
|
||||||
|
|
||||||
|
## A Simple Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import click
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--count", default=1, help="Number of greetings.")
|
||||||
|
@click.option("--name", prompt="Your name", help="The person to greet.")
|
||||||
|
def hello(count, name):
|
||||||
|
"""Simple program that greets NAME for a total of COUNT times."""
|
||||||
|
for _ in range(count):
|
||||||
|
click.echo(f"Hello, {name}!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
hello()
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python hello.py --count=3
|
||||||
|
Your name: Click
|
||||||
|
Hello, Click!
|
||||||
|
Hello, Click!
|
||||||
|
Hello, Click!
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
The Pallets organization develops and supports Click and other popular
|
||||||
|
packages. In order to grow the community of contributors and users, and
|
||||||
|
allow the maintainers to devote more time to the projects, [please
|
||||||
|
donate today][].
|
||||||
|
|
||||||
|
[please donate today]: https://palletsprojects.com/donate
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See our [detailed contributing documentation][contrib] for many ways to
|
||||||
|
contribute, including reporting issues, requesting features, asking or answering
|
||||||
|
questions, and making PRs.
|
||||||
|
|
||||||
|
[contrib]: https://palletsprojects.com/contributing/
|
||||||
|
|
||||||
40
.venv_codegen/Lib/site-packages/click-8.3.2.dist-info/RECORD
Normal file
40
.venv_codegen/Lib/site-packages/click-8.3.2.dist-info/RECORD
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
click-8.3.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
click-8.3.2.dist-info/METADATA,sha256=yA3Hu5bMxtntTd4QrI8hTFAc58rHSPhDbP6bklbUQkA,2621
|
||||||
|
click-8.3.2.dist-info/RECORD,,
|
||||||
|
click-8.3.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||||
|
click-8.3.2.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
||||||
|
click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473
|
||||||
|
click/__pycache__/__init__.cpython-310.pyc,,
|
||||||
|
click/__pycache__/_compat.cpython-310.pyc,,
|
||||||
|
click/__pycache__/_termui_impl.cpython-310.pyc,,
|
||||||
|
click/__pycache__/_textwrap.cpython-310.pyc,,
|
||||||
|
click/__pycache__/_utils.cpython-310.pyc,,
|
||||||
|
click/__pycache__/_winconsole.cpython-310.pyc,,
|
||||||
|
click/__pycache__/core.cpython-310.pyc,,
|
||||||
|
click/__pycache__/decorators.cpython-310.pyc,,
|
||||||
|
click/__pycache__/exceptions.cpython-310.pyc,,
|
||||||
|
click/__pycache__/formatting.cpython-310.pyc,,
|
||||||
|
click/__pycache__/globals.cpython-310.pyc,,
|
||||||
|
click/__pycache__/parser.cpython-310.pyc,,
|
||||||
|
click/__pycache__/shell_completion.cpython-310.pyc,,
|
||||||
|
click/__pycache__/termui.cpython-310.pyc,,
|
||||||
|
click/__pycache__/testing.cpython-310.pyc,,
|
||||||
|
click/__pycache__/types.cpython-310.pyc,,
|
||||||
|
click/__pycache__/utils.cpython-310.pyc,,
|
||||||
|
click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693
|
||||||
|
click/_termui_impl.py,sha256=rgCb3On8X5A4200rA5L6i13u5iapmFer7sru57Jy6zA,27093
|
||||||
|
click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400
|
||||||
|
click/_utils.py,sha256=kZwtTf5gMuCilJJceS2iTCvRvCY-0aN5rJq8gKw7p8g,943
|
||||||
|
click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465
|
||||||
|
click/core.py,sha256=7db9qr_wqXbQriDHCDc26OK0MsaLCSt4yrz14Kn7AEQ,132905
|
||||||
|
click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461
|
||||||
|
click/exceptions.py,sha256=8utf8w6V5hJXMnO_ic1FNrtbwuEn1NUu1aDwV8UqnG4,9954
|
||||||
|
click/formatting.py,sha256=RVfwwr0rwWNpgGr8NaHodPzkIr7_tUyVh_nDdanLMNc,9730
|
||||||
|
click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923
|
||||||
|
click/parser.py,sha256=Q31pH0FlQZEq-UXE_ABRzlygEfvxPTuZbWNh4xfXmzw,19010
|
||||||
|
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
click/shell_completion.py,sha256=Cc4GQUFuWpfQBa9sF5qXeeYI7n3tI_1k6ZdSn4BZbT0,20994
|
||||||
|
click/termui.py,sha256=hqCEjNndU-nzW08nRAkBaVgfZp_FdCA9KxfIWlKYaMc,31037
|
||||||
|
click/testing.py,sha256=LjNfHqNctxc3GfRkLgifO6gnRetblh8yGXzjw4FPFCQ,18978
|
||||||
|
click/types.py,sha256=ek54BNSFwPKsqtfT7jsqcc4WHui8AIFVMKM4oVZIXhc,39927
|
||||||
|
click/utils.py,sha256=gCUoewdAhA-QLBUUHxrLh4uj6m7T1WjZZMNPvR0I7YA,20257
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: flit 3.12.0
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright 2014 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
123
.venv_codegen/Lib/site-packages/click/__init__.py
Normal file
123
.venv_codegen/Lib/site-packages/click/__init__.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
"""
|
||||||
|
Click is a simple Python module inspired by the stdlib optparse to make
|
||||||
|
writing command line scripts fun. Unlike other modules, it's based
|
||||||
|
around a simple API that does not come with too much magic and is
|
||||||
|
composable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .core import Argument as Argument
|
||||||
|
from .core import Command as Command
|
||||||
|
from .core import CommandCollection as CommandCollection
|
||||||
|
from .core import Context as Context
|
||||||
|
from .core import Group as Group
|
||||||
|
from .core import Option as Option
|
||||||
|
from .core import Parameter as Parameter
|
||||||
|
from .decorators import argument as argument
|
||||||
|
from .decorators import command as command
|
||||||
|
from .decorators import confirmation_option as confirmation_option
|
||||||
|
from .decorators import group as group
|
||||||
|
from .decorators import help_option as help_option
|
||||||
|
from .decorators import make_pass_decorator as make_pass_decorator
|
||||||
|
from .decorators import option as option
|
||||||
|
from .decorators import pass_context as pass_context
|
||||||
|
from .decorators import pass_obj as pass_obj
|
||||||
|
from .decorators import password_option as password_option
|
||||||
|
from .decorators import version_option as version_option
|
||||||
|
from .exceptions import Abort as Abort
|
||||||
|
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
||||||
|
from .exceptions import BadOptionUsage as BadOptionUsage
|
||||||
|
from .exceptions import BadParameter as BadParameter
|
||||||
|
from .exceptions import ClickException as ClickException
|
||||||
|
from .exceptions import FileError as FileError
|
||||||
|
from .exceptions import MissingParameter as MissingParameter
|
||||||
|
from .exceptions import NoSuchOption as NoSuchOption
|
||||||
|
from .exceptions import UsageError as UsageError
|
||||||
|
from .formatting import HelpFormatter as HelpFormatter
|
||||||
|
from .formatting import wrap_text as wrap_text
|
||||||
|
from .globals import get_current_context as get_current_context
|
||||||
|
from .termui import clear as clear
|
||||||
|
from .termui import confirm as confirm
|
||||||
|
from .termui import echo_via_pager as echo_via_pager
|
||||||
|
from .termui import edit as edit
|
||||||
|
from .termui import getchar as getchar
|
||||||
|
from .termui import launch as launch
|
||||||
|
from .termui import pause as pause
|
||||||
|
from .termui import progressbar as progressbar
|
||||||
|
from .termui import prompt as prompt
|
||||||
|
from .termui import secho as secho
|
||||||
|
from .termui import style as style
|
||||||
|
from .termui import unstyle as unstyle
|
||||||
|
from .types import BOOL as BOOL
|
||||||
|
from .types import Choice as Choice
|
||||||
|
from .types import DateTime as DateTime
|
||||||
|
from .types import File as File
|
||||||
|
from .types import FLOAT as FLOAT
|
||||||
|
from .types import FloatRange as FloatRange
|
||||||
|
from .types import INT as INT
|
||||||
|
from .types import IntRange as IntRange
|
||||||
|
from .types import ParamType as ParamType
|
||||||
|
from .types import Path as Path
|
||||||
|
from .types import STRING as STRING
|
||||||
|
from .types import Tuple as Tuple
|
||||||
|
from .types import UNPROCESSED as UNPROCESSED
|
||||||
|
from .types import UUID as UUID
|
||||||
|
from .utils import echo as echo
|
||||||
|
from .utils import format_filename as format_filename
|
||||||
|
from .utils import get_app_dir as get_app_dir
|
||||||
|
from .utils import get_binary_stream as get_binary_stream
|
||||||
|
from .utils import get_text_stream as get_text_stream
|
||||||
|
from .utils import open_file as open_file
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(name: str) -> object:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if name == "BaseCommand":
|
||||||
|
from .core import _BaseCommand
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'BaseCommand' is deprecated and will be removed in Click 9.0. Use"
|
||||||
|
" 'Command' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _BaseCommand
|
||||||
|
|
||||||
|
if name == "MultiCommand":
|
||||||
|
from .core import _MultiCommand
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'MultiCommand' is deprecated and will be removed in Click 9.0. Use"
|
||||||
|
" 'Group' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _MultiCommand
|
||||||
|
|
||||||
|
if name == "OptionParser":
|
||||||
|
from .parser import _OptionParser
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'OptionParser' is deprecated and will be removed in Click 9.0. The"
|
||||||
|
" old parser is available in 'optparse'.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _OptionParser
|
||||||
|
|
||||||
|
if name == "__version__":
|
||||||
|
import importlib.metadata
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"The '__version__' attribute is deprecated and will be removed in"
|
||||||
|
" Click 9.1. Use feature detection or"
|
||||||
|
" 'importlib.metadata.version(\"click\")' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return importlib.metadata.version("click")
|
||||||
|
|
||||||
|
raise AttributeError(name)
|
||||||
622
.venv_codegen/Lib/site-packages/click/_compat.py
Normal file
622
.venv_codegen/Lib/site-packages/click/_compat.py
Normal file
|
|
@ -0,0 +1,622 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import codecs
|
||||||
|
import collections.abc as cabc
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
from types import TracebackType
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
CYGWIN = sys.platform.startswith("cygwin")
|
||||||
|
WIN = sys.platform.startswith("win")
|
||||||
|
auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None
|
||||||
|
_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
|
||||||
|
|
||||||
|
|
||||||
|
def _make_text_stream(
|
||||||
|
stream: t.BinaryIO,
|
||||||
|
encoding: str | None,
|
||||||
|
errors: str | None,
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
if encoding is None:
|
||||||
|
encoding = get_best_encoding(stream)
|
||||||
|
if errors is None:
|
||||||
|
errors = "replace"
|
||||||
|
return _NonClosingTextIOWrapper(
|
||||||
|
stream,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
line_buffering=True,
|
||||||
|
force_readable=force_readable,
|
||||||
|
force_writable=force_writable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_ascii_encoding(encoding: str) -> bool:
|
||||||
|
"""Checks if a given encoding is ascii."""
|
||||||
|
try:
|
||||||
|
return codecs.lookup(encoding).name == "ascii"
|
||||||
|
except LookupError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_encoding(stream: t.IO[t.Any]) -> str:
|
||||||
|
"""Returns the default stream encoding if not found."""
|
||||||
|
rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
|
||||||
|
if is_ascii_encoding(rv):
|
||||||
|
return "utf-8"
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
stream: t.BinaryIO,
|
||||||
|
encoding: str | None,
|
||||||
|
errors: str | None,
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
**extra: t.Any,
|
||||||
|
) -> None:
|
||||||
|
self._stream = stream = t.cast(
|
||||||
|
t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
|
||||||
|
)
|
||||||
|
super().__init__(stream, encoding, errors, **extra)
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
try:
|
||||||
|
self.detach()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isatty(self) -> bool:
|
||||||
|
# https://bitbucket.org/pypy/pypy/issue/1803
|
||||||
|
return self._stream.isatty()
|
||||||
|
|
||||||
|
|
||||||
|
class _FixupStream:
|
||||||
|
"""The new io interface needs more from streams than streams
|
||||||
|
traditionally implement. As such, this fix-up code is necessary in
|
||||||
|
some circumstances.
|
||||||
|
|
||||||
|
The forcing of readable and writable flags are there because some tools
|
||||||
|
put badly patched objects on sys (one such offender are certain version
|
||||||
|
of jupyter notebook).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
stream: t.BinaryIO,
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
):
|
||||||
|
self._stream = stream
|
||||||
|
self._force_readable = force_readable
|
||||||
|
self._force_writable = force_writable
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self._stream, name)
|
||||||
|
|
||||||
|
def read1(self, size: int) -> bytes:
|
||||||
|
f = getattr(self._stream, "read1", None)
|
||||||
|
|
||||||
|
if f is not None:
|
||||||
|
return t.cast(bytes, f(size))
|
||||||
|
|
||||||
|
return self._stream.read(size)
|
||||||
|
|
||||||
|
def readable(self) -> bool:
|
||||||
|
if self._force_readable:
|
||||||
|
return True
|
||||||
|
x = getattr(self._stream, "readable", None)
|
||||||
|
if x is not None:
|
||||||
|
return t.cast(bool, x())
|
||||||
|
try:
|
||||||
|
self._stream.read(0)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def writable(self) -> bool:
|
||||||
|
if self._force_writable:
|
||||||
|
return True
|
||||||
|
x = getattr(self._stream, "writable", None)
|
||||||
|
if x is not None:
|
||||||
|
return t.cast(bool, x())
|
||||||
|
try:
|
||||||
|
self._stream.write(b"")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self._stream.write(b"")
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def seekable(self) -> bool:
|
||||||
|
x = getattr(self._stream, "seekable", None)
|
||||||
|
if x is not None:
|
||||||
|
return t.cast(bool, x())
|
||||||
|
try:
|
||||||
|
self._stream.seek(self._stream.tell())
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool:
|
||||||
|
try:
|
||||||
|
return isinstance(stream.read(0), bytes)
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
# This happens in some cases where the stream was already
|
||||||
|
# closed. In this case, we assume the default.
|
||||||
|
|
||||||
|
|
||||||
|
def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool:
|
||||||
|
try:
|
||||||
|
stream.write(b"")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
stream.write("")
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None:
|
||||||
|
# We need to figure out if the given stream is already binary.
|
||||||
|
# This can happen because the official docs recommend detaching
|
||||||
|
# the streams to get binary streams. Some code might do this, so
|
||||||
|
# we need to deal with this case explicitly.
|
||||||
|
if _is_binary_reader(stream, False):
|
||||||
|
return t.cast(t.BinaryIO, stream)
|
||||||
|
|
||||||
|
buf = getattr(stream, "buffer", None)
|
||||||
|
|
||||||
|
# Same situation here; this time we assume that the buffer is
|
||||||
|
# actually binary in case it's closed.
|
||||||
|
if buf is not None and _is_binary_reader(buf, True):
|
||||||
|
return t.cast(t.BinaryIO, buf)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None:
|
||||||
|
# We need to figure out if the given stream is already binary.
|
||||||
|
# This can happen because the official docs recommend detaching
|
||||||
|
# the streams to get binary streams. Some code might do this, so
|
||||||
|
# we need to deal with this case explicitly.
|
||||||
|
if _is_binary_writer(stream, False):
|
||||||
|
return t.cast(t.BinaryIO, stream)
|
||||||
|
|
||||||
|
buf = getattr(stream, "buffer", None)
|
||||||
|
|
||||||
|
# Same situation here; this time we assume that the buffer is
|
||||||
|
# actually binary in case it's closed.
|
||||||
|
if buf is not None and _is_binary_writer(buf, True):
|
||||||
|
return t.cast(t.BinaryIO, buf)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _stream_is_misconfigured(stream: t.TextIO) -> bool:
|
||||||
|
"""A stream is misconfigured if its encoding is ASCII."""
|
||||||
|
# If the stream does not have an encoding set, we assume it's set
|
||||||
|
# to ASCII. This appears to happen in certain unittest
|
||||||
|
# environments. It's not quite clear what the correct behavior is
|
||||||
|
# but this at least will force Click to recover somehow.
|
||||||
|
return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool:
|
||||||
|
"""A stream attribute is compatible if it is equal to the
|
||||||
|
desired value or the desired value is unset and the attribute
|
||||||
|
has a value.
|
||||||
|
"""
|
||||||
|
stream_value = getattr(stream, attr, None)
|
||||||
|
return stream_value == value or (value is None and stream_value is not None)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_compatible_text_stream(
|
||||||
|
stream: t.TextIO, encoding: str | None, errors: str | None
|
||||||
|
) -> bool:
|
||||||
|
"""Check if a stream's encoding and errors attributes are
|
||||||
|
compatible with the desired values.
|
||||||
|
"""
|
||||||
|
return _is_compat_stream_attr(
|
||||||
|
stream, "encoding", encoding
|
||||||
|
) and _is_compat_stream_attr(stream, "errors", errors)
|
||||||
|
|
||||||
|
|
||||||
|
def _force_correct_text_stream(
|
||||||
|
text_stream: t.IO[t.Any],
|
||||||
|
encoding: str | None,
|
||||||
|
errors: str | None,
|
||||||
|
is_binary: t.Callable[[t.IO[t.Any], bool], bool],
|
||||||
|
find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None],
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
if is_binary(text_stream, False):
|
||||||
|
binary_reader = t.cast(t.BinaryIO, text_stream)
|
||||||
|
else:
|
||||||
|
text_stream = t.cast(t.TextIO, text_stream)
|
||||||
|
# If the stream looks compatible, and won't default to a
|
||||||
|
# misconfigured ascii encoding, return it as-is.
|
||||||
|
if _is_compatible_text_stream(text_stream, encoding, errors) and not (
|
||||||
|
encoding is None and _stream_is_misconfigured(text_stream)
|
||||||
|
):
|
||||||
|
return text_stream
|
||||||
|
|
||||||
|
# Otherwise, get the underlying binary reader.
|
||||||
|
possible_binary_reader = find_binary(text_stream)
|
||||||
|
|
||||||
|
# If that's not possible, silently use the original reader
|
||||||
|
# and get mojibake instead of exceptions.
|
||||||
|
if possible_binary_reader is None:
|
||||||
|
return text_stream
|
||||||
|
|
||||||
|
binary_reader = possible_binary_reader
|
||||||
|
|
||||||
|
# Default errors to replace instead of strict in order to get
|
||||||
|
# something that works.
|
||||||
|
if errors is None:
|
||||||
|
errors = "replace"
|
||||||
|
|
||||||
|
# Wrap the binary stream in a text stream with the correct
|
||||||
|
# encoding parameters.
|
||||||
|
return _make_text_stream(
|
||||||
|
binary_reader,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
force_readable=force_readable,
|
||||||
|
force_writable=force_writable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _force_correct_text_reader(
|
||||||
|
text_reader: t.IO[t.Any],
|
||||||
|
encoding: str | None,
|
||||||
|
errors: str | None,
|
||||||
|
force_readable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
return _force_correct_text_stream(
|
||||||
|
text_reader,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
_is_binary_reader,
|
||||||
|
_find_binary_reader,
|
||||||
|
force_readable=force_readable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _force_correct_text_writer(
|
||||||
|
text_writer: t.IO[t.Any],
|
||||||
|
encoding: str | None,
|
||||||
|
errors: str | None,
|
||||||
|
force_writable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
return _force_correct_text_stream(
|
||||||
|
text_writer,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
_is_binary_writer,
|
||||||
|
_find_binary_writer,
|
||||||
|
force_writable=force_writable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stdin() -> t.BinaryIO:
|
||||||
|
reader = _find_binary_reader(sys.stdin)
|
||||||
|
if reader is None:
|
||||||
|
raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
|
||||||
|
return reader
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stdout() -> t.BinaryIO:
|
||||||
|
writer = _find_binary_writer(sys.stdout)
|
||||||
|
if writer is None:
|
||||||
|
raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
|
||||||
|
return writer
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stderr() -> t.BinaryIO:
|
||||||
|
writer = _find_binary_writer(sys.stderr)
|
||||||
|
if writer is None:
|
||||||
|
raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
|
||||||
|
return writer
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
|
||||||
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
|
||||||
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
|
||||||
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_io_open(
|
||||||
|
file: str | os.PathLike[str] | int,
|
||||||
|
mode: str,
|
||||||
|
encoding: str | None,
|
||||||
|
errors: str | None,
|
||||||
|
) -> t.IO[t.Any]:
|
||||||
|
"""Handles not passing ``encoding`` and ``errors`` in binary mode."""
|
||||||
|
if "b" in mode:
|
||||||
|
return open(file, mode)
|
||||||
|
|
||||||
|
return open(file, mode, encoding=encoding, errors=errors)
|
||||||
|
|
||||||
|
|
||||||
|
def open_stream(
|
||||||
|
filename: str | os.PathLike[str],
|
||||||
|
mode: str = "r",
|
||||||
|
encoding: str | None = None,
|
||||||
|
errors: str | None = "strict",
|
||||||
|
atomic: bool = False,
|
||||||
|
) -> tuple[t.IO[t.Any], bool]:
|
||||||
|
binary = "b" in mode
|
||||||
|
filename = os.fspath(filename)
|
||||||
|
|
||||||
|
# Standard streams first. These are simple because they ignore the
|
||||||
|
# atomic flag. Use fsdecode to handle Path("-").
|
||||||
|
if os.fsdecode(filename) == "-":
|
||||||
|
if any(m in mode for m in ["w", "a", "x"]):
|
||||||
|
if binary:
|
||||||
|
return get_binary_stdout(), False
|
||||||
|
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||||
|
if binary:
|
||||||
|
return get_binary_stdin(), False
|
||||||
|
return get_text_stdin(encoding=encoding, errors=errors), False
|
||||||
|
|
||||||
|
# Non-atomic writes directly go out through the regular open functions.
|
||||||
|
if not atomic:
|
||||||
|
return _wrap_io_open(filename, mode, encoding, errors), True
|
||||||
|
|
||||||
|
# Some usability stuff for atomic writes
|
||||||
|
if "a" in mode:
|
||||||
|
raise ValueError(
|
||||||
|
"Appending to an existing file is not supported, because that"
|
||||||
|
" would involve an expensive `copy`-operation to a temporary"
|
||||||
|
" file. Open the file in normal `w`-mode and copy explicitly"
|
||||||
|
" if that's what you're after."
|
||||||
|
)
|
||||||
|
if "x" in mode:
|
||||||
|
raise ValueError("Use the `overwrite`-parameter instead.")
|
||||||
|
if "w" not in mode:
|
||||||
|
raise ValueError("Atomic writes only make sense with `w`-mode.")
|
||||||
|
|
||||||
|
# Atomic writes are more complicated. They work by opening a file
|
||||||
|
# as a proxy in the same folder and then using the fdopen
|
||||||
|
# functionality to wrap it in a Python file. Then we wrap it in an
|
||||||
|
# atomic file that moves the file over on close.
|
||||||
|
import errno
|
||||||
|
import random
|
||||||
|
|
||||||
|
try:
|
||||||
|
perm: int | None = os.stat(filename).st_mode
|
||||||
|
except OSError:
|
||||||
|
perm = None
|
||||||
|
|
||||||
|
flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
||||||
|
|
||||||
|
if binary:
|
||||||
|
flags |= getattr(os, "O_BINARY", 0)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
tmp_filename = os.path.join(
|
||||||
|
os.path.dirname(filename),
|
||||||
|
f".__atomic-write{random.randrange(1 << 32):08x}",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
|
||||||
|
break
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.EEXIST or (
|
||||||
|
os.name == "nt"
|
||||||
|
and e.errno == errno.EACCES
|
||||||
|
and os.path.isdir(e.filename)
|
||||||
|
and os.access(e.filename, os.W_OK)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
|
if perm is not None:
|
||||||
|
os.chmod(tmp_filename, perm) # in case perm includes bits in umask
|
||||||
|
|
||||||
|
f = _wrap_io_open(fd, mode, encoding, errors)
|
||||||
|
af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
|
||||||
|
return t.cast(t.IO[t.Any], af), True
|
||||||
|
|
||||||
|
|
||||||
|
class _AtomicFile:
|
||||||
|
def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None:
|
||||||
|
self._f = f
|
||||||
|
self._tmp_filename = tmp_filename
|
||||||
|
self._real_filename = real_filename
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._real_filename
|
||||||
|
|
||||||
|
def close(self, delete: bool = False) -> None:
|
||||||
|
if self.closed:
|
||||||
|
return
|
||||||
|
self._f.close()
|
||||||
|
os.replace(self._tmp_filename, self._real_filename)
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self._f, name)
|
||||||
|
|
||||||
|
def __enter__(self) -> _AtomicFile:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
exc_type: type[BaseException] | None,
|
||||||
|
exc_value: BaseException | None,
|
||||||
|
tb: TracebackType | None,
|
||||||
|
) -> None:
|
||||||
|
self.close(delete=exc_type is not None)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return repr(self._f)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_ansi(value: str) -> str:
|
||||||
|
return _ansi_re.sub("", value)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool:
|
||||||
|
while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
|
||||||
|
stream = stream._stream
|
||||||
|
|
||||||
|
return stream.__class__.__module__.startswith("ipykernel.")
|
||||||
|
|
||||||
|
|
||||||
|
def should_strip_ansi(
|
||||||
|
stream: t.IO[t.Any] | None = None, color: bool | None = None
|
||||||
|
) -> bool:
|
||||||
|
if color is None:
|
||||||
|
if stream is None:
|
||||||
|
stream = sys.stdin
|
||||||
|
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
|
||||||
|
return not color
|
||||||
|
|
||||||
|
|
||||||
|
# On Windows, wrap the output streams with colorama to support ANSI
|
||||||
|
# color codes.
|
||||||
|
# NOTE: double check is needed so mypy does not analyze this on Linux
|
||||||
|
if sys.platform.startswith("win") and WIN:
|
||||||
|
from ._winconsole import _get_windows_console_stream
|
||||||
|
|
||||||
|
def _get_argv_encoding() -> str:
|
||||||
|
import locale
|
||||||
|
|
||||||
|
return locale.getpreferredencoding()
|
||||||
|
|
||||||
|
_ansi_stream_wrappers: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
||||||
|
|
||||||
|
def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO:
|
||||||
|
"""Support ANSI color and style codes on Windows by wrapping a
|
||||||
|
stream with colorama.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cached = _ansi_stream_wrappers.get(stream)
|
||||||
|
except Exception:
|
||||||
|
cached = None
|
||||||
|
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
|
import colorama
|
||||||
|
|
||||||
|
strip = should_strip_ansi(stream, color)
|
||||||
|
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
||||||
|
rv = t.cast(t.TextIO, ansi_wrapper.stream)
|
||||||
|
_write = rv.write
|
||||||
|
|
||||||
|
def _safe_write(s: str) -> int:
|
||||||
|
try:
|
||||||
|
return _write(s)
|
||||||
|
except BaseException:
|
||||||
|
ansi_wrapper.reset_all()
|
||||||
|
raise
|
||||||
|
|
||||||
|
rv.write = _safe_write # type: ignore[method-assign]
|
||||||
|
|
||||||
|
try:
|
||||||
|
_ansi_stream_wrappers[stream] = rv
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def _get_argv_encoding() -> str:
|
||||||
|
return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding()
|
||||||
|
|
||||||
|
def _get_windows_console_stream(
|
||||||
|
f: t.TextIO, encoding: str | None, errors: str | None
|
||||||
|
) -> t.TextIO | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def term_len(x: str) -> int:
|
||||||
|
return len(strip_ansi(x))
|
||||||
|
|
||||||
|
|
||||||
|
def isatty(stream: t.IO[t.Any]) -> bool:
|
||||||
|
try:
|
||||||
|
return stream.isatty()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _make_cached_stream_func(
|
||||||
|
src_func: t.Callable[[], t.TextIO | None],
|
||||||
|
wrapper_func: t.Callable[[], t.TextIO],
|
||||||
|
) -> t.Callable[[], t.TextIO | None]:
|
||||||
|
cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
||||||
|
|
||||||
|
def func() -> t.TextIO | None:
|
||||||
|
stream = src_func()
|
||||||
|
|
||||||
|
if stream is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
rv = cache.get(stream)
|
||||||
|
except Exception:
|
||||||
|
rv = None
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
rv = wrapper_func()
|
||||||
|
try:
|
||||||
|
cache[stream] = rv
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return rv
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
|
||||||
|
_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
|
||||||
|
_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
|
||||||
|
|
||||||
|
|
||||||
|
binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = {
|
||||||
|
"stdin": get_binary_stdin,
|
||||||
|
"stdout": get_binary_stdout,
|
||||||
|
"stderr": get_binary_stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = {
|
||||||
|
"stdin": get_text_stdin,
|
||||||
|
"stdout": get_text_stdout,
|
||||||
|
"stderr": get_text_stderr,
|
||||||
|
}
|
||||||
852
.venv_codegen/Lib/site-packages/click/_termui_impl.py
Normal file
852
.venv_codegen/Lib/site-packages/click/_termui_impl.py
Normal file
|
|
@ -0,0 +1,852 @@
|
||||||
|
"""
|
||||||
|
This module contains implementations for the termui module. To keep the
|
||||||
|
import time of Click down, some infrequently used functionality is
|
||||||
|
placed in this module and only imported as needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections.abc as cabc
|
||||||
|
import contextlib
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import typing as t
|
||||||
|
from gettext import gettext as _
|
||||||
|
from io import StringIO
|
||||||
|
from pathlib import Path
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
|
from ._compat import _default_text_stdout
|
||||||
|
from ._compat import CYGWIN
|
||||||
|
from ._compat import get_best_encoding
|
||||||
|
from ._compat import isatty
|
||||||
|
from ._compat import open_stream
|
||||||
|
from ._compat import strip_ansi
|
||||||
|
from ._compat import term_len
|
||||||
|
from ._compat import WIN
|
||||||
|
from .exceptions import ClickException
|
||||||
|
from .utils import echo
|
||||||
|
|
||||||
|
V = t.TypeVar("V")
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
BEFORE_BAR = "\r"
|
||||||
|
AFTER_BAR = "\n"
|
||||||
|
else:
|
||||||
|
BEFORE_BAR = "\r\033[?25l"
|
||||||
|
AFTER_BAR = "\033[?25h\n"
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressBar(t.Generic[V]):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
iterable: cabc.Iterable[V] | None,
|
||||||
|
length: int | None = None,
|
||||||
|
fill_char: str = "#",
|
||||||
|
empty_char: str = " ",
|
||||||
|
bar_template: str = "%(bar)s",
|
||||||
|
info_sep: str = " ",
|
||||||
|
hidden: bool = False,
|
||||||
|
show_eta: bool = True,
|
||||||
|
show_percent: bool | None = None,
|
||||||
|
show_pos: bool = False,
|
||||||
|
item_show_func: t.Callable[[V | None], str | None] | None = None,
|
||||||
|
label: str | None = None,
|
||||||
|
file: t.TextIO | None = None,
|
||||||
|
color: bool | None = None,
|
||||||
|
update_min_steps: int = 1,
|
||||||
|
width: int = 30,
|
||||||
|
) -> None:
|
||||||
|
self.fill_char = fill_char
|
||||||
|
self.empty_char = empty_char
|
||||||
|
self.bar_template = bar_template
|
||||||
|
self.info_sep = info_sep
|
||||||
|
self.hidden = hidden
|
||||||
|
self.show_eta = show_eta
|
||||||
|
self.show_percent = show_percent
|
||||||
|
self.show_pos = show_pos
|
||||||
|
self.item_show_func = item_show_func
|
||||||
|
self.label: str = label or ""
|
||||||
|
|
||||||
|
if file is None:
|
||||||
|
file = _default_text_stdout()
|
||||||
|
|
||||||
|
# There are no standard streams attached to write to. For example,
|
||||||
|
# pythonw on Windows.
|
||||||
|
if file is None:
|
||||||
|
file = StringIO()
|
||||||
|
|
||||||
|
self.file = file
|
||||||
|
self.color = color
|
||||||
|
self.update_min_steps = update_min_steps
|
||||||
|
self._completed_intervals = 0
|
||||||
|
self.width: int = width
|
||||||
|
self.autowidth: bool = width == 0
|
||||||
|
|
||||||
|
if length is None:
|
||||||
|
from operator import length_hint
|
||||||
|
|
||||||
|
length = length_hint(iterable, -1)
|
||||||
|
|
||||||
|
if length == -1:
|
||||||
|
length = None
|
||||||
|
if iterable is None:
|
||||||
|
if length is None:
|
||||||
|
raise TypeError("iterable or length is required")
|
||||||
|
iterable = t.cast("cabc.Iterable[V]", range(length))
|
||||||
|
self.iter: cabc.Iterable[V] = iter(iterable)
|
||||||
|
self.length = length
|
||||||
|
self.pos: int = 0
|
||||||
|
self.avg: list[float] = []
|
||||||
|
self.last_eta: float
|
||||||
|
self.start: float
|
||||||
|
self.start = self.last_eta = time.time()
|
||||||
|
self.eta_known: bool = False
|
||||||
|
self.finished: bool = False
|
||||||
|
self.max_width: int | None = None
|
||||||
|
self.entered: bool = False
|
||||||
|
self.current_item: V | None = None
|
||||||
|
self._is_atty = isatty(self.file)
|
||||||
|
self._last_line: str | None = None
|
||||||
|
|
||||||
|
def __enter__(self) -> ProgressBar[V]:
|
||||||
|
self.entered = True
|
||||||
|
self.render_progress()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
exc_type: type[BaseException] | None,
|
||||||
|
exc_value: BaseException | None,
|
||||||
|
tb: TracebackType | None,
|
||||||
|
) -> None:
|
||||||
|
self.render_finish()
|
||||||
|
|
||||||
|
def __iter__(self) -> cabc.Iterator[V]:
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError("You need to use progress bars in a with block.")
|
||||||
|
self.render_progress()
|
||||||
|
return self.generator()
|
||||||
|
|
||||||
|
def __next__(self) -> V:
|
||||||
|
# Iteration is defined in terms of a generator function,
|
||||||
|
# returned by iter(self); use that to define next(). This works
|
||||||
|
# because `self.iter` is an iterable consumed by that generator,
|
||||||
|
# so it is re-entry safe. Calling `next(self.generator())`
|
||||||
|
# twice works and does "what you want".
|
||||||
|
return next(iter(self))
|
||||||
|
|
||||||
|
def render_finish(self) -> None:
|
||||||
|
if self.hidden or not self._is_atty:
|
||||||
|
return
|
||||||
|
self.file.write(AFTER_BAR)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pct(self) -> float:
|
||||||
|
if self.finished:
|
||||||
|
return 1.0
|
||||||
|
return min(self.pos / (float(self.length or 1) or 1), 1.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_per_iteration(self) -> float:
|
||||||
|
if not self.avg:
|
||||||
|
return 0.0
|
||||||
|
return sum(self.avg) / float(len(self.avg))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def eta(self) -> float:
|
||||||
|
if self.length is not None and not self.finished:
|
||||||
|
return self.time_per_iteration * (self.length - self.pos)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def format_eta(self) -> str:
|
||||||
|
if self.eta_known:
|
||||||
|
t = int(self.eta)
|
||||||
|
seconds = t % 60
|
||||||
|
t //= 60
|
||||||
|
minutes = t % 60
|
||||||
|
t //= 60
|
||||||
|
hours = t % 24
|
||||||
|
t //= 24
|
||||||
|
if t > 0:
|
||||||
|
return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
|
||||||
|
else:
|
||||||
|
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def format_pos(self) -> str:
|
||||||
|
pos = str(self.pos)
|
||||||
|
if self.length is not None:
|
||||||
|
pos += f"/{self.length}"
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def format_pct(self) -> str:
|
||||||
|
return f"{int(self.pct * 100): 4}%"[1:]
|
||||||
|
|
||||||
|
def format_bar(self) -> str:
|
||||||
|
if self.length is not None:
|
||||||
|
bar_length = int(self.pct * self.width)
|
||||||
|
bar = self.fill_char * bar_length
|
||||||
|
bar += self.empty_char * (self.width - bar_length)
|
||||||
|
elif self.finished:
|
||||||
|
bar = self.fill_char * self.width
|
||||||
|
else:
|
||||||
|
chars = list(self.empty_char * (self.width or 1))
|
||||||
|
if self.time_per_iteration != 0:
|
||||||
|
chars[
|
||||||
|
int(
|
||||||
|
(math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
|
||||||
|
* self.width
|
||||||
|
)
|
||||||
|
] = self.fill_char
|
||||||
|
bar = "".join(chars)
|
||||||
|
return bar
|
||||||
|
|
||||||
|
def format_progress_line(self) -> str:
|
||||||
|
show_percent = self.show_percent
|
||||||
|
|
||||||
|
info_bits = []
|
||||||
|
if self.length is not None and show_percent is None:
|
||||||
|
show_percent = not self.show_pos
|
||||||
|
|
||||||
|
if self.show_pos:
|
||||||
|
info_bits.append(self.format_pos())
|
||||||
|
if show_percent:
|
||||||
|
info_bits.append(self.format_pct())
|
||||||
|
if self.show_eta and self.eta_known and not self.finished:
|
||||||
|
info_bits.append(self.format_eta())
|
||||||
|
if self.item_show_func is not None:
|
||||||
|
item_info = self.item_show_func(self.current_item)
|
||||||
|
if item_info is not None:
|
||||||
|
info_bits.append(item_info)
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.bar_template
|
||||||
|
% {
|
||||||
|
"label": self.label,
|
||||||
|
"bar": self.format_bar(),
|
||||||
|
"info": self.info_sep.join(info_bits),
|
||||||
|
}
|
||||||
|
).rstrip()
|
||||||
|
|
||||||
|
def render_progress(self) -> None:
|
||||||
|
if self.hidden:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._is_atty:
|
||||||
|
# Only output the label once if the output is not a TTY.
|
||||||
|
if self._last_line != self.label:
|
||||||
|
self._last_line = self.label
|
||||||
|
echo(self.label, file=self.file, color=self.color)
|
||||||
|
return
|
||||||
|
|
||||||
|
buf = []
|
||||||
|
# Update width in case the terminal has been resized
|
||||||
|
if self.autowidth:
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
old_width = self.width
|
||||||
|
self.width = 0
|
||||||
|
clutter_length = term_len(self.format_progress_line())
|
||||||
|
new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
|
||||||
|
if new_width < old_width and self.max_width is not None:
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
buf.append(" " * self.max_width)
|
||||||
|
self.max_width = new_width
|
||||||
|
self.width = new_width
|
||||||
|
|
||||||
|
clear_width = self.width
|
||||||
|
if self.max_width is not None:
|
||||||
|
clear_width = self.max_width
|
||||||
|
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
line = self.format_progress_line()
|
||||||
|
line_len = term_len(line)
|
||||||
|
if self.max_width is None or self.max_width < line_len:
|
||||||
|
self.max_width = line_len
|
||||||
|
|
||||||
|
buf.append(line)
|
||||||
|
buf.append(" " * (clear_width - line_len))
|
||||||
|
line = "".join(buf)
|
||||||
|
# Render the line only if it changed.
|
||||||
|
|
||||||
|
if line != self._last_line:
|
||||||
|
self._last_line = line
|
||||||
|
echo(line, file=self.file, color=self.color, nl=False)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
def make_step(self, n_steps: int) -> None:
|
||||||
|
self.pos += n_steps
|
||||||
|
if self.length is not None and self.pos >= self.length:
|
||||||
|
self.finished = True
|
||||||
|
|
||||||
|
if (time.time() - self.last_eta) < 1.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.last_eta = time.time()
|
||||||
|
|
||||||
|
# self.avg is a rolling list of length <= 7 of steps where steps are
|
||||||
|
# defined as time elapsed divided by the total progress through
|
||||||
|
# self.length.
|
||||||
|
if self.pos:
|
||||||
|
step = (time.time() - self.start) / self.pos
|
||||||
|
else:
|
||||||
|
step = time.time() - self.start
|
||||||
|
|
||||||
|
self.avg = self.avg[-6:] + [step]
|
||||||
|
|
||||||
|
self.eta_known = self.length is not None
|
||||||
|
|
||||||
|
def update(self, n_steps: int, current_item: V | None = None) -> None:
|
||||||
|
"""Update the progress bar by advancing a specified number of
|
||||||
|
steps, and optionally set the ``current_item`` for this new
|
||||||
|
position.
|
||||||
|
|
||||||
|
:param n_steps: Number of steps to advance.
|
||||||
|
:param current_item: Optional item to set as ``current_item``
|
||||||
|
for the updated position.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Added the ``current_item`` optional parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Only render when the number of steps meets the
|
||||||
|
``update_min_steps`` threshold.
|
||||||
|
"""
|
||||||
|
if current_item is not None:
|
||||||
|
self.current_item = current_item
|
||||||
|
|
||||||
|
self._completed_intervals += n_steps
|
||||||
|
|
||||||
|
if self._completed_intervals >= self.update_min_steps:
|
||||||
|
self.make_step(self._completed_intervals)
|
||||||
|
self.render_progress()
|
||||||
|
self._completed_intervals = 0
|
||||||
|
|
||||||
|
def finish(self) -> None:
|
||||||
|
self.eta_known = False
|
||||||
|
self.current_item = None
|
||||||
|
self.finished = True
|
||||||
|
|
||||||
|
def generator(self) -> cabc.Iterator[V]:
|
||||||
|
"""Return a generator which yields the items added to the bar
|
||||||
|
during construction, and updates the progress bar *after* the
|
||||||
|
yielded block returns.
|
||||||
|
"""
|
||||||
|
# WARNING: the iterator interface for `ProgressBar` relies on
|
||||||
|
# this and only works because this is a simple generator which
|
||||||
|
# doesn't create or manage additional state. If this function
|
||||||
|
# changes, the impact should be evaluated both against
|
||||||
|
# `iter(bar)` and `next(bar)`. `next()` in particular may call
|
||||||
|
# `self.generator()` repeatedly, and this must remain safe in
|
||||||
|
# order for that interface to work.
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError("You need to use progress bars in a with block.")
|
||||||
|
|
||||||
|
if not self._is_atty:
|
||||||
|
yield from self.iter
|
||||||
|
else:
|
||||||
|
for rv in self.iter:
|
||||||
|
self.current_item = rv
|
||||||
|
|
||||||
|
# This allows show_item_func to be updated before the
|
||||||
|
# item is processed. Only trigger at the beginning of
|
||||||
|
# the update interval.
|
||||||
|
if self._completed_intervals == 0:
|
||||||
|
self.render_progress()
|
||||||
|
|
||||||
|
yield rv
|
||||||
|
self.update(1)
|
||||||
|
|
||||||
|
self.finish()
|
||||||
|
self.render_progress()
|
||||||
|
|
||||||
|
|
||||||
|
def pager(generator: cabc.Iterable[str], color: bool | None = None) -> None:
|
||||||
|
"""Decide what method to use for paging through text."""
|
||||||
|
stdout = _default_text_stdout()
|
||||||
|
|
||||||
|
# There are no standard streams attached to write to. For example,
|
||||||
|
# pythonw on Windows.
|
||||||
|
if stdout is None:
|
||||||
|
stdout = StringIO()
|
||||||
|
|
||||||
|
if not isatty(sys.stdin) or not isatty(stdout):
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
|
||||||
|
# Split and normalize the pager command into parts.
|
||||||
|
pager_cmd_parts = shlex.split(os.environ.get("PAGER", ""), posix=False)
|
||||||
|
if pager_cmd_parts:
|
||||||
|
if WIN:
|
||||||
|
if _tempfilepager(generator, pager_cmd_parts, color):
|
||||||
|
return
|
||||||
|
elif _pipepager(generator, pager_cmd_parts, color):
|
||||||
|
return
|
||||||
|
|
||||||
|
if os.environ.get("TERM") in ("dumb", "emacs"):
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
if (WIN or sys.platform.startswith("os2")) and _tempfilepager(
|
||||||
|
generator, ["more"], color
|
||||||
|
):
|
||||||
|
return
|
||||||
|
if _pipepager(generator, ["less"], color):
|
||||||
|
return
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
os.close(fd)
|
||||||
|
try:
|
||||||
|
if _pipepager(generator, ["more"], color):
|
||||||
|
return
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
finally:
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _pipepager(
|
||||||
|
generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
|
||||||
|
) -> bool:
|
||||||
|
"""Page through text by feeding it to another program. Invoking a
|
||||||
|
pager through this might support colors.
|
||||||
|
|
||||||
|
Returns `True` if the command was found, `False` otherwise and thus another
|
||||||
|
pager should be attempted.
|
||||||
|
"""
|
||||||
|
# Split the command into the invoked CLI and its parameters.
|
||||||
|
if not cmd_parts:
|
||||||
|
return False
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
cmd = cmd_parts[0]
|
||||||
|
cmd_params = cmd_parts[1:]
|
||||||
|
|
||||||
|
cmd_filepath = shutil.which(cmd)
|
||||||
|
if not cmd_filepath:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Produces a normalized absolute path string.
|
||||||
|
# multi-call binaries such as busybox derive their identity from the symlink
|
||||||
|
# less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox)
|
||||||
|
cmd_path = Path(cmd_filepath).absolute()
|
||||||
|
cmd_name = cmd_path.name
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Make a local copy of the environment to not affect the global one.
|
||||||
|
env = dict(os.environ)
|
||||||
|
|
||||||
|
# If we're piping to less and the user hasn't decided on colors, we enable
|
||||||
|
# them by default we find the -R flag in the command line arguments.
|
||||||
|
if color is None and cmd_name == "less":
|
||||||
|
less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}"
|
||||||
|
if not less_flags:
|
||||||
|
env["LESS"] = "-R"
|
||||||
|
color = True
|
||||||
|
elif "r" in less_flags or "R" in less_flags:
|
||||||
|
color = True
|
||||||
|
|
||||||
|
c = subprocess.Popen(
|
||||||
|
[str(cmd_path)] + cmd_params,
|
||||||
|
shell=False,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
env=env,
|
||||||
|
errors="replace",
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert c.stdin is not None
|
||||||
|
try:
|
||||||
|
for text in generator:
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
|
||||||
|
c.stdin.write(text)
|
||||||
|
except BrokenPipeError:
|
||||||
|
# In case the pager exited unexpectedly, ignore the broken pipe error.
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
# In case there is an exception we want to close the pager immediately
|
||||||
|
# and let the caller handle it.
|
||||||
|
# Otherwise the pager will keep running, and the user may not notice
|
||||||
|
# the error message, or worse yet it may leave the terminal in a broken state.
|
||||||
|
c.terminate()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
# We must close stdin and wait for the pager to exit before we continue
|
||||||
|
try:
|
||||||
|
c.stdin.close()
|
||||||
|
# Close implies flush, so it might throw a BrokenPipeError if the pager
|
||||||
|
# process exited already.
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
||||||
|
# search or other commands inside less).
|
||||||
|
#
|
||||||
|
# That means when the user hits ^C, the parent process (click) terminates,
|
||||||
|
# but less is still alive, paging the output and messing up the terminal.
|
||||||
|
#
|
||||||
|
# If the user wants to make the pager exit on ^C, they should set
|
||||||
|
# `LESS='-K'`. It's not our decision to make.
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
c.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _tempfilepager(
|
||||||
|
generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
|
||||||
|
) -> bool:
|
||||||
|
"""Page through text by invoking a program on a temporary file.
|
||||||
|
|
||||||
|
Returns `True` if the command was found, `False` otherwise and thus another
|
||||||
|
pager should be attempted.
|
||||||
|
"""
|
||||||
|
# Split the command into the invoked CLI and its parameters.
|
||||||
|
if not cmd_parts:
|
||||||
|
return False
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
cmd = cmd_parts[0]
|
||||||
|
|
||||||
|
cmd_filepath = shutil.which(cmd)
|
||||||
|
if not cmd_filepath:
|
||||||
|
return False
|
||||||
|
# Produces a normalized absolute path string.
|
||||||
|
# multi-call binaries such as busybox derive their identity from the symlink
|
||||||
|
# less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox)
|
||||||
|
cmd_path = Path(cmd_filepath).absolute()
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
# TODO: This never terminates if the passed generator never terminates.
|
||||||
|
text = "".join(generator)
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
encoding = get_best_encoding(sys.stdout)
|
||||||
|
with open_stream(filename, "wb")[0] as f:
|
||||||
|
f.write(text.encode(encoding))
|
||||||
|
try:
|
||||||
|
subprocess.call([str(cmd_path), filename])
|
||||||
|
except OSError:
|
||||||
|
# Command not found
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _nullpager(
|
||||||
|
stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None
|
||||||
|
) -> None:
|
||||||
|
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||||
|
for text in generator:
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
stream.write(text)
|
||||||
|
|
||||||
|
|
||||||
|
class Editor:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
editor: str | None = None,
|
||||||
|
env: cabc.Mapping[str, str] | None = None,
|
||||||
|
require_save: bool = True,
|
||||||
|
extension: str = ".txt",
|
||||||
|
) -> None:
|
||||||
|
self.editor = editor
|
||||||
|
self.env = env
|
||||||
|
self.require_save = require_save
|
||||||
|
self.extension = extension
|
||||||
|
|
||||||
|
def get_editor(self) -> str:
|
||||||
|
if self.editor is not None:
|
||||||
|
return self.editor
|
||||||
|
for key in "VISUAL", "EDITOR":
|
||||||
|
rv = os.environ.get(key)
|
||||||
|
if rv:
|
||||||
|
return rv
|
||||||
|
if WIN:
|
||||||
|
return "notepad"
|
||||||
|
|
||||||
|
from shutil import which
|
||||||
|
|
||||||
|
for editor in "sensible-editor", "vim", "nano":
|
||||||
|
if which(editor) is not None:
|
||||||
|
return editor
|
||||||
|
return "vi"
|
||||||
|
|
||||||
|
def edit_files(self, filenames: cabc.Iterable[str]) -> None:
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
editor = self.get_editor()
|
||||||
|
environ: dict[str, str] | None = None
|
||||||
|
|
||||||
|
if self.env:
|
||||||
|
environ = os.environ.copy()
|
||||||
|
environ.update(self.env)
|
||||||
|
|
||||||
|
exc_filename = " ".join(f'"{filename}"' for filename in filenames)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c = subprocess.Popen(
|
||||||
|
args=f"{editor} {exc_filename}", env=environ, shell=True
|
||||||
|
)
|
||||||
|
exit_code = c.wait()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise ClickException(
|
||||||
|
_("{editor}: Editing failed").format(editor=editor)
|
||||||
|
)
|
||||||
|
except OSError as e:
|
||||||
|
raise ClickException(
|
||||||
|
_("{editor}: Editing failed: {e}").format(editor=editor, e=e)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
@t.overload
|
||||||
|
def edit(self, text: bytes | bytearray) -> bytes | None: ...
|
||||||
|
|
||||||
|
# We cannot know whether or not the type expected is str or bytes when None
|
||||||
|
# is passed, so str is returned as that was what was done before.
|
||||||
|
@t.overload
|
||||||
|
def edit(self, text: str | None) -> str | None: ...
|
||||||
|
|
||||||
|
def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None:
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
if text is None:
|
||||||
|
data: bytes | bytearray = b""
|
||||||
|
elif isinstance(text, (bytes, bytearray)):
|
||||||
|
data = text
|
||||||
|
else:
|
||||||
|
if text and not text.endswith("\n"):
|
||||||
|
text += "\n"
|
||||||
|
|
||||||
|
if WIN:
|
||||||
|
data = text.replace("\n", "\r\n").encode("utf-8-sig")
|
||||||
|
else:
|
||||||
|
data = text.encode("utf-8")
|
||||||
|
|
||||||
|
fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
|
||||||
|
f: t.BinaryIO
|
||||||
|
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
# If the filesystem resolution is 1 second, like Mac OS
|
||||||
|
# 10.12 Extended, or 2 seconds, like FAT32, and the editor
|
||||||
|
# closes very fast, require_save can fail. Set the modified
|
||||||
|
# time to be 2 seconds in the past to work around this.
|
||||||
|
os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
|
||||||
|
# Depending on the resolution, the exact value might not be
|
||||||
|
# recorded, so get the new recorded value.
|
||||||
|
timestamp = os.path.getmtime(name)
|
||||||
|
|
||||||
|
self.edit_files((name,))
|
||||||
|
|
||||||
|
if self.require_save and os.path.getmtime(name) == timestamp:
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(name, "rb") as f:
|
||||||
|
rv = f.read()
|
||||||
|
|
||||||
|
if isinstance(text, (bytes, bytearray)):
|
||||||
|
return rv
|
||||||
|
|
||||||
|
return rv.decode("utf-8-sig").replace("\r\n", "\n")
|
||||||
|
finally:
|
||||||
|
os.unlink(name)
|
||||||
|
|
||||||
|
|
||||||
|
def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def _unquote_file(url: str) -> str:
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
if url.startswith("file://"):
|
||||||
|
url = unquote(url[7:])
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
args = ["open"]
|
||||||
|
if wait:
|
||||||
|
args.append("-W")
|
||||||
|
if locate:
|
||||||
|
args.append("-R")
|
||||||
|
args.append(_unquote_file(url))
|
||||||
|
null = open("/dev/null", "w")
|
||||||
|
try:
|
||||||
|
return subprocess.Popen(args, stderr=null).wait()
|
||||||
|
finally:
|
||||||
|
null.close()
|
||||||
|
elif WIN:
|
||||||
|
if locate:
|
||||||
|
url = _unquote_file(url)
|
||||||
|
args = ["explorer", f"/select,{url}"]
|
||||||
|
else:
|
||||||
|
args = ["start"]
|
||||||
|
if wait:
|
||||||
|
args.append("/WAIT")
|
||||||
|
args.append("")
|
||||||
|
args.append(url)
|
||||||
|
try:
|
||||||
|
return subprocess.call(args)
|
||||||
|
except OSError:
|
||||||
|
# Command not found
|
||||||
|
return 127
|
||||||
|
elif CYGWIN:
|
||||||
|
if locate:
|
||||||
|
url = _unquote_file(url)
|
||||||
|
args = ["cygstart", os.path.dirname(url)]
|
||||||
|
else:
|
||||||
|
args = ["cygstart"]
|
||||||
|
if wait:
|
||||||
|
args.append("-w")
|
||||||
|
args.append(url)
|
||||||
|
try:
|
||||||
|
return subprocess.call(args)
|
||||||
|
except OSError:
|
||||||
|
# Command not found
|
||||||
|
return 127
|
||||||
|
|
||||||
|
try:
|
||||||
|
if locate:
|
||||||
|
url = os.path.dirname(_unquote_file(url)) or "."
|
||||||
|
else:
|
||||||
|
url = _unquote_file(url)
|
||||||
|
c = subprocess.Popen(["xdg-open", url])
|
||||||
|
if wait:
|
||||||
|
return c.wait()
|
||||||
|
return 0
|
||||||
|
except OSError:
|
||||||
|
if url.startswith(("http://", "https://")) and not locate and not wait:
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
webbrowser.open(url)
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _translate_ch_to_exc(ch: str) -> None:
|
||||||
|
if ch == "\x03":
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
|
||||||
|
raise EOFError()
|
||||||
|
|
||||||
|
if ch == "\x1a" and WIN: # Windows, Ctrl+Z
|
||||||
|
raise EOFError()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal() -> cabc.Iterator[int]:
|
||||||
|
yield -1
|
||||||
|
|
||||||
|
def getchar(echo: bool) -> str:
|
||||||
|
# The function `getch` will return a bytes object corresponding to
|
||||||
|
# the pressed character. Since Windows 10 build 1803, it will also
|
||||||
|
# return \x00 when called a second time after pressing a regular key.
|
||||||
|
#
|
||||||
|
# `getwch` does not share this probably-bugged behavior. Moreover, it
|
||||||
|
# returns a Unicode object by default, which is what we want.
|
||||||
|
#
|
||||||
|
# Either of these functions will return \x00 or \xe0 to indicate
|
||||||
|
# a special key, and you need to call the same function again to get
|
||||||
|
# the "rest" of the code. The fun part is that \u00e0 is
|
||||||
|
# "latin small letter a with grave", so if you type that on a French
|
||||||
|
# keyboard, you _also_ get a \xe0.
|
||||||
|
# E.g., consider the Up arrow. This returns \xe0 and then \x48. The
|
||||||
|
# resulting Unicode string reads as "a with grave" + "capital H".
|
||||||
|
# This is indistinguishable from when the user actually types
|
||||||
|
# "a with grave" and then "capital H".
|
||||||
|
#
|
||||||
|
# When \xe0 is returned, we assume it's part of a special-key sequence
|
||||||
|
# and call `getwch` again, but that means that when the user types
|
||||||
|
# the \u00e0 character, `getchar` doesn't return until a second
|
||||||
|
# character is typed.
|
||||||
|
# The alternative is returning immediately, but that would mess up
|
||||||
|
# cross-platform handling of arrow keys and others that start with
|
||||||
|
# \xe0. Another option is using `getch`, but then we can't reliably
|
||||||
|
# read non-ASCII characters, because return values of `getch` are
|
||||||
|
# limited to the current 8-bit codepage.
|
||||||
|
#
|
||||||
|
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
|
||||||
|
# is doing the right thing in more situations than with `getch`.
|
||||||
|
|
||||||
|
if echo:
|
||||||
|
func = t.cast(t.Callable[[], str], msvcrt.getwche)
|
||||||
|
else:
|
||||||
|
func = t.cast(t.Callable[[], str], msvcrt.getwch)
|
||||||
|
|
||||||
|
rv = func()
|
||||||
|
|
||||||
|
if rv in ("\x00", "\xe0"):
|
||||||
|
# \x00 and \xe0 are control characters that indicate special key,
|
||||||
|
# see above.
|
||||||
|
rv += func()
|
||||||
|
|
||||||
|
_translate_ch_to_exc(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
else:
|
||||||
|
import termios
|
||||||
|
import tty
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal() -> cabc.Iterator[int]:
|
||||||
|
f: t.TextIO | None
|
||||||
|
fd: int
|
||||||
|
|
||||||
|
if not isatty(sys.stdin):
|
||||||
|
f = open("/dev/tty")
|
||||||
|
fd = f.fileno()
|
||||||
|
else:
|
||||||
|
fd = sys.stdin.fileno()
|
||||||
|
f = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_settings = termios.tcgetattr(fd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tty.setraw(fd)
|
||||||
|
yield fd
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
if f is not None:
|
||||||
|
f.close()
|
||||||
|
except termios.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getchar(echo: bool) -> str:
|
||||||
|
with raw_terminal() as fd:
|
||||||
|
ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
|
||||||
|
|
||||||
|
if echo and isatty(sys.stdout):
|
||||||
|
sys.stdout.write(ch)
|
||||||
|
|
||||||
|
_translate_ch_to_exc(ch)
|
||||||
|
return ch
|
||||||
51
.venv_codegen/Lib/site-packages/click/_textwrap.py
Normal file
51
.venv_codegen/Lib/site-packages/click/_textwrap.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections.abc as cabc
|
||||||
|
import textwrap
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
class TextWrapper(textwrap.TextWrapper):
|
||||||
|
def _handle_long_word(
|
||||||
|
self,
|
||||||
|
reversed_chunks: list[str],
|
||||||
|
cur_line: list[str],
|
||||||
|
cur_len: int,
|
||||||
|
width: int,
|
||||||
|
) -> None:
|
||||||
|
space_left = max(width - cur_len, 1)
|
||||||
|
|
||||||
|
if self.break_long_words:
|
||||||
|
last = reversed_chunks[-1]
|
||||||
|
cut = last[:space_left]
|
||||||
|
res = last[space_left:]
|
||||||
|
cur_line.append(cut)
|
||||||
|
reversed_chunks[-1] = res
|
||||||
|
elif not cur_line:
|
||||||
|
cur_line.append(reversed_chunks.pop())
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def extra_indent(self, indent: str) -> cabc.Iterator[None]:
|
||||||
|
old_initial_indent = self.initial_indent
|
||||||
|
old_subsequent_indent = self.subsequent_indent
|
||||||
|
self.initial_indent += indent
|
||||||
|
self.subsequent_indent += indent
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.initial_indent = old_initial_indent
|
||||||
|
self.subsequent_indent = old_subsequent_indent
|
||||||
|
|
||||||
|
def indent_only(self, text: str) -> str:
|
||||||
|
rv = []
|
||||||
|
|
||||||
|
for idx, line in enumerate(text.splitlines()):
|
||||||
|
indent = self.initial_indent
|
||||||
|
|
||||||
|
if idx > 0:
|
||||||
|
indent = self.subsequent_indent
|
||||||
|
|
||||||
|
rv.append(f"{indent}{line}")
|
||||||
|
|
||||||
|
return "\n".join(rv)
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue