diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 01a5639af0..7eca7607da 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -55,4 +55,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: snapshot-report-textual - path: tests/snapshot_tests/output/snapshot_report.html + path: snapshot_report.html diff --git a/docs/blog/images/text-area-learnings/cursor_position_updating_via_api.png b/docs/blog/images/text-area-learnings/cursor_position_updating_via_api.png new file mode 100644 index 0000000000..c10f78dc84 Binary files /dev/null and b/docs/blog/images/text-area-learnings/cursor_position_updating_via_api.png differ diff --git a/docs/blog/images/text-area-learnings/maintain_offset.gif b/docs/blog/images/text-area-learnings/maintain_offset.gif new file mode 100644 index 0000000000..d39bca5e0d Binary files /dev/null and b/docs/blog/images/text-area-learnings/maintain_offset.gif differ diff --git a/docs/blog/images/text-area-learnings/text-area-api-insert.gif b/docs/blog/images/text-area-learnings/text-area-api-insert.gif new file mode 100644 index 0000000000..529eb01e3d Binary files /dev/null and b/docs/blog/images/text-area-learnings/text-area-api-insert.gif differ diff --git a/docs/blog/images/text-area-learnings/text-area-pyinstrument.png b/docs/blog/images/text-area-learnings/text-area-pyinstrument.png new file mode 100644 index 0000000000..2a8cc3609c Binary files /dev/null and b/docs/blog/images/text-area-learnings/text-area-pyinstrument.png differ diff --git a/docs/blog/images/text-area-learnings/text-area-syntax-error.gif b/docs/blog/images/text-area-learnings/text-area-syntax-error.gif new file mode 100644 index 0000000000..0a74cb649e Binary files /dev/null and b/docs/blog/images/text-area-learnings/text-area-syntax-error.gif differ diff --git a/docs/blog/images/text-area-learnings/text-area-theme-cycle.gif b/docs/blog/images/text-area-learnings/text-area-theme-cycle.gif new file mode 100644 index 0000000000..c73e9dd9eb Binary files /dev/null and b/docs/blog/images/text-area-learnings/text-area-theme-cycle.gif differ diff --git a/docs/blog/images/text-area-learnings/text-area-welcome.gif b/docs/blog/images/text-area-learnings/text-area-welcome.gif new file mode 100644 index 0000000000..baaf821edc Binary files /dev/null and b/docs/blog/images/text-area-learnings/text-area-welcome.gif differ diff --git a/docs/blog/posts/text-area-learnings.md b/docs/blog/posts/text-area-learnings.md new file mode 100644 index 0000000000..d55a6b96e9 --- /dev/null +++ b/docs/blog/posts/text-area-learnings.md @@ -0,0 +1,210 @@ +--- +draft: false +date: 2023-09-18 +categories: + - DevLog +authors: + - darrenburns +--- + +# Things I learned while building Textual's TextArea + +`TextArea` is the latest widget to be added to Textual's [growing collection](https://textual.textualize.io/widget_gallery/). +It provides a multi-line space to edit text, and features optional syntax highlighting for a selection of languages. + +![text-area-welcome.gif](../images/text-area-learnings/text-area-welcome.gif) + +Adding a `TextArea` to your Textual app is as simple as adding this to your `compose` method: + +```python +yield TextArea() +``` + +Enabling syntax highlighting for a language is as simple as: + +```python +yield TextArea(language="python") +``` + +Working on the `TextArea` widget for Textual taught me a lot about Python and my general +approach to software engineering. It gave me an appreciation for the subtle functionality behind +the editors we use on a daily basis — features we may not even notice, despite +some engineer spending hours perfecting it to provide a small boost to our development experience. + +This post is a tour of some of these learnings. + + + +## Vertical cursor movement is more than just `cursor_row++` + +When you move the cursor vertically, you can't simply keep the same column index and clamp it within the line. +Editors should maintain the visual column offset where possible, +meaning they must account for double-width emoji (sigh 😔) and East-Asian characters. + +![maintain_offset.gif](../images/text-area-learnings/maintain_offset.gif){ loading=lazy } + +Notice that although the cursor is on column 11 while on line 1, it lands on column 6 when it +arrives at line 3. +This is because the 6th character of line 3 _visually_ aligns with the 11th character of line 1. + + +## Edits from other sources may move my cursor + +There are two ways to interact with the `TextArea`: + +1. You can type into it. +2. You can make API calls to edit the content in it. + +In the example below, `Hello, world!\n` is repeatedly inserted at the start of the document via the +API. +Notice that this updates the location of my cursor, ensuring that I don't lose my place. + +![text-area-api-insert.gif](../images/text-area-learnings/text-area-api-insert.gif){ loading=lazy } + +This subtle feature should aid those implementing collaborative and multi-cursor editing. + +This turned out to be one of the more complex features of the whole project, and went through several iterations before I was happy with the result. + +Thankfully it resulted in some wonderful Tetris-esque whiteboards along the way! + + + ![cursor_position_updating_via_api.png](../images/text-area-learnings/cursor_position_updating_via_api.png){ loading=lazy } + A TetrisArea white-boarding session. + + +Sometimes stepping away from the screen and scribbling on a whiteboard with your colleagues (thanks [Dave](https://fosstodon.org/@davep)!) is what's needed to finally crack a tough problem. + +Many thanks to [David Brochart](https://mastodon.top/@davidbrochart) for sending me down this rabbit hole! + +## Spending a few minutes running a profiler can be really beneficial + +While building the `TextArea` widget I avoided heavy optimisation work that may have affected +readability or maintainability. + +However, I did run a profiler in an attempt to detect flawed assumptions or mistakes which were +affecting the performance of my code. + +I spent around 30 minutes profiling `TextArea` +using [pyinstrument](https://pyinstrument.readthedocs.io/en/latest/home.html), and the result was a +**~97%** reduction in the time taken to handle a key press. +What an amazing return on investment for such a minimal time commitment! + + + + ![text-area-pyinstrument.png](../images/text-area-learnings/text-area-pyinstrument.png){ loading=lazy } + "pyinstrument -r html" produces this beautiful output. + + +pyinstrument unveiled two issues that were massively impacting performance. + +### 1. Reparsing highlighting queries on each key press + +I was constructing a tree-sitter `Query` object on each key press, incorrectly assuming it was a +low-overhead call. +This query was completely static, so I moved it into the constructor ensuring the object was created +only once. +This reduced key processing time by around 94% - a substantial and very much noticeable improvement. + +This seems obvious in hindsight, but the code in question was written earlier in the project and had +been relegated in my mind to "code that works correctly and will receive less attention from here on +out". +pyinstrument quickly brought this code back to my attention and highlighted it as a glaring +performance bug. + +### 2. NamedTuples are slower than I expected + +In Python, `NamedTuple`s are slow to create relative to `tuple`s, and this cost was adding up inside +an extremely hot loop which was instantiating a large number of them. +pyinstrument revealed that a large portion of the time during syntax highlighting was spent inside `NamedTuple.__new__`. + +Here's a quick benchmark which constructs 10,000 `NamedTuple`s: + +```toml +❯ hyperfine -w 2 'python sandbox/darren/make_namedtuples.py' +Benchmark 1: python sandbox/darren/make_namedtuples.py + Time (mean ± σ): 15.9 ms ± 0.5 ms [User: 12.8 ms, System: 2.5 ms] + Range (min … max): 15.2 ms … 18.4 ms 165 runs +``` + +Here's the same benchmark using `tuple` instead: + +```toml +❯ hyperfine -w 2 'python sandbox/darren/make_tuples.py' +Benchmark 1: python sandbox/darren/make_tuples.py + Time (mean ± σ): 9.3 ms ± 0.5 ms [User: 6.8 ms, System: 2.0 ms] + Range (min … max): 8.7 ms … 12.3 ms 256 runs +``` + +Switching to `tuple` resulted in another noticeable increase in responsiveness. +Key-press handling time dropped by almost 50%! +Unfortunately, this change _does_ impact readability. +However, the scope in which these tuples were used was very small, and so I felt it was a worthy trade-off. + + +## Syntax highlighting is very different from what I expected + +In order to support syntax highlighting, we make use of +the [tree-sitter](https://tree-sitter.github.io/tree-sitter/) library, which maintains a syntax tree +representing the structure of our document. + +To perform highlighting, we follow these steps: + +1. The user edits the document. +2. We inform tree-sitter of the location of this edit. +3. tree-sitter intelligently parses only the subset of the document impacted by the change, updating the tree. +4. We run a query against the tree to retrieve ranges of text we wish to highlight. +5. These ranges are mapped to styles (defined by the chosen "theme"). +6. These styles to the appropriate text ranges when rendering the widget. + + + ![text-area-theme-cycle.gif](../images/text-area-learnings/text-area-theme-cycle.gif){ loading=lazy } + Cycling through a few of the builtin themes. + + +Another benefit that I didn't consider before working on this project is that tree-sitter +parsers can also be used to highlight syntax errors in a document. +This can be useful in some situations - for example, highlighting mismatched HTML closing tags: + + + ![text-area-syntax-error.gif](../images/text-area-learnings/text-area-syntax-error.gif){ loading=lazy } + Highlighting mismatched closing HTML tags in red. + + +Before building this widget, I was oblivious as to how we might approach syntax highlighting. +Without tree-sitter's incremental parsing approach, I'm not sure reasonable performance would have +been feasible. + +## Edits are replacements + +All single-cursor edits can be distilled into a single behaviour: `replace_range`. +This replaces a range of characters with some text. +We can use this one method to easily implement deletion, insertion, and replacement of text. + +- Inserting text is replacing a zero-width range with the text to insert. +- Pressing backspace (delete left) is just replacing the character behind the cursor with an empty + string. +- Selecting text and pressing delete is just replacing the selected text with an empty string. +- Selecting text and pasting is replacing the selected text with some other text. + +This greatly simplified my initial approach, which involved unique implementations for inserting and +deleting. + + +## The line between "text area" and "VSCode in the terminal" + +A project like this has no clear finish line. +There are always new features, optimisations, and refactors waiting to be made. + +So where do we draw the line? + +We want to provide a widget which can act as both a basic multiline text area that +anyone can drop into their app, yet powerful and extensible enough to act as the foundation +for a Textual-powered text editor. + +Yet, the more features we add, the more opinionated the widget becomes, and the less that users +will feel like they can build it into their _own_ thing. +Finding the sweet spot between feature-rich and flexible is no easy task. + +I don't think the answer is clear, and I don't believe it's possible to please everyone. + +Regardless, I'm happy with where we've landed, and I'm really excited to see what people build using `TextArea` in the future! diff --git a/docs/examples/widgets/horizontal_rules.py b/docs/examples/widgets/horizontal_rules.py index 2327e474ec..643f129bbe 100644 --- a/docs/examples/widgets/horizontal_rules.py +++ b/docs/examples/widgets/horizontal_rules.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult -from textual.widgets import Rule, Label from textual.containers import Vertical +from textual.widgets import Label, Rule class HorizontalRulesApp(App): diff --git a/docs/examples/widgets/java_highlights.scm b/docs/examples/widgets/java_highlights.scm new file mode 100644 index 0000000000..b6259be125 --- /dev/null +++ b/docs/examples/widgets/java_highlights.scm @@ -0,0 +1,140 @@ +; Methods + +(method_declaration + name: (identifier) @function.method) +(method_invocation + name: (identifier) @function.method) +(super) @function.builtin + +; Annotations + +(annotation + name: (identifier) @attribute) +(marker_annotation + name: (identifier) @attribute) + +"@" @operator + +; Types + +(type_identifier) @type + +(interface_declaration + name: (identifier) @type) +(class_declaration + name: (identifier) @type) +(enum_declaration + name: (identifier) @type) + +((field_access + object: (identifier) @type) + (#match? @type "^[A-Z]")) +((scoped_identifier + scope: (identifier) @type) + (#match? @type "^[A-Z]")) +((method_invocation + object: (identifier) @type) + (#match? @type "^[A-Z]")) +((method_reference + . (identifier) @type) + (#match? @type "^[A-Z]")) + +(constructor_declaration + name: (identifier) @type) + +[ + (boolean_type) + (integral_type) + (floating_point_type) + (floating_point_type) + (void_type) +] @type.builtin + +; Variables + +((identifier) @constant + (#match? @constant "^_*[A-Z][A-Z\\d_]+$")) + +(identifier) @variable + +(this) @variable.builtin + +; Literals + +[ + (hex_integer_literal) + (decimal_integer_literal) + (octal_integer_literal) + (decimal_floating_point_literal) + (hex_floating_point_literal) +] @number + +[ + (character_literal) + (string_literal) +] @string + +[ + (true) + (false) + (null_literal) +] @constant.builtin + +[ + (line_comment) + (block_comment) +] @comment + +; Keywords + +[ + "abstract" + "assert" + "break" + "case" + "catch" + "class" + "continue" + "default" + "do" + "else" + "enum" + "exports" + "extends" + "final" + "finally" + "for" + "if" + "implements" + "import" + "instanceof" + "interface" + "module" + "native" + "new" + "non-sealed" + "open" + "opens" + "package" + "private" + "protected" + "provides" + "public" + "requires" + "return" + "sealed" + "static" + "strictfp" + "switch" + "synchronized" + "throw" + "throws" + "to" + "transient" + "transitive" + "try" + "uses" + "volatile" + "while" + "with" +] @keyword diff --git a/docs/examples/widgets/text_area_custom_language.py b/docs/examples/widgets/text_area_custom_language.py new file mode 100644 index 0000000000..70ee7e16b9 --- /dev/null +++ b/docs/examples/widgets/text_area_custom_language.py @@ -0,0 +1,34 @@ +from pathlib import Path + +from tree_sitter_languages import get_language + +from textual.app import App, ComposeResult +from textual.widgets import TextArea + +java_language = get_language("java") +java_highlight_query = (Path(__file__).parent / "java_highlights.scm").read_text() +java_code = """\ +class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +""" + + +class TextAreaCustomLanguage(App): + def compose(self) -> ComposeResult: + text_area = TextArea(text=java_code) + text_area.cursor_blink = False + + # Register the Java language and highlight query + text_area.register_language(java_language, java_highlight_query) + + # Switch to Java + text_area.language = "java" + yield text_area + + +app = TextAreaCustomLanguage() +if __name__ == "__main__": + app.run() diff --git a/docs/examples/widgets/text_area_custom_theme.py b/docs/examples/widgets/text_area_custom_theme.py new file mode 100644 index 0000000000..c2c81a115f --- /dev/null +++ b/docs/examples/widgets/text_area_custom_theme.py @@ -0,0 +1,42 @@ +from rich.style import Style + +from textual._text_area_theme import TextAreaTheme +from textual.app import App, ComposeResult +from textual.widgets import TextArea + +TEXT = """\ +# says hello +def hello(name): + print("hello" + name) + +# says goodbye +def goodbye(name): + print("goodbye" + name) +""" + +MY_THEME = TextAreaTheme( + # This name will be used to refer to the theme... + name="my_cool_theme", + # Basic styles such as background, cursor, selection, gutter, etc... + cursor_style=Style(color="white", bgcolor="blue"), + cursor_line_style=Style(bgcolor="yellow"), + # `syntax_styles` maps tokens parsed from the document to Rich styles. + syntax_styles={ + "string": Style(color="red"), + "comment": Style(color="magenta"), + }, +) + + +class TextAreaCustomThemes(App): + def compose(self) -> ComposeResult: + text_area = TextArea(TEXT, language="python") + text_area.cursor_blink = False + text_area.register_theme(MY_THEME) + text_area.theme = "my_cool_theme" + yield text_area + + +app = TextAreaCustomThemes() +if __name__ == "__main__": + app.run() diff --git a/docs/examples/widgets/text_area_example.py b/docs/examples/widgets/text_area_example.py new file mode 100644 index 0000000000..2e0e31c060 --- /dev/null +++ b/docs/examples/widgets/text_area_example.py @@ -0,0 +1,20 @@ +from textual.app import App, ComposeResult +from textual.widgets import TextArea + +TEXT = """\ +def hello(name): + print("hello" + name) + +def goodbye(name): + print("goodbye" + name) +""" + + +class TextAreaExample(App): + def compose(self) -> ComposeResult: + yield TextArea(TEXT, language="python") + + +app = TextAreaExample() +if __name__ == "__main__": + app.run() diff --git a/docs/examples/widgets/text_area_extended.py b/docs/examples/widgets/text_area_extended.py new file mode 100644 index 0000000000..8ac237db88 --- /dev/null +++ b/docs/examples/widgets/text_area_extended.py @@ -0,0 +1,23 @@ +from textual import events +from textual.app import App, ComposeResult +from textual.widgets import TextArea + + +class ExtendedTextArea(TextArea): + """A subclass of TextArea with parenthesis-closing functionality.""" + + def _on_key(self, event: events.Key) -> None: + if event.character == "(": + self.insert("()") + self.move_cursor_relative(columns=-1) + event.prevent_default() + + +class TextAreaKeyPressHook(App): + def compose(self) -> ComposeResult: + yield ExtendedTextArea(language="python") + + +app = TextAreaKeyPressHook() +if __name__ == "__main__": + app.run() diff --git a/docs/examples/widgets/text_area_selection.py b/docs/examples/widgets/text_area_selection.py new file mode 100644 index 0000000000..4165eb2d2d --- /dev/null +++ b/docs/examples/widgets/text_area_selection.py @@ -0,0 +1,23 @@ +from textual.app import App, ComposeResult +from textual.widgets import TextArea +from textual.widgets.text_area import Selection + +TEXT = """\ +def hello(name): + print("hello" + name) + +def goodbye(name): + print("goodbye" + name) +""" + + +class TextAreaSelection(App): + def compose(self) -> ComposeResult: + text_area = TextArea(TEXT, language="python") + text_area.selection = Selection(start=(0, 0), end=(2, 0)) # (1)! + yield text_area + + +app = TextAreaSelection() +if __name__ == "__main__": + app.run() diff --git a/docs/examples/widgets/vertical_rules.py b/docs/examples/widgets/vertical_rules.py index 27592bef8f..5001045305 100644 --- a/docs/examples/widgets/vertical_rules.py +++ b/docs/examples/widgets/vertical_rules.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult -from textual.widgets import Rule, Label from textual.containers import Horizontal +from textual.widgets import Label, Rule class VerticalRulesApp(App): diff --git a/docs/widget_gallery.md b/docs/widget_gallery.md index 559fc62929..f0384f5a72 100644 --- a/docs/widget_gallery.md +++ b/docs/widget_gallery.md @@ -281,7 +281,7 @@ Displays simple static content. Typically used as a base class. ## Switch -A on / off control, inspired by toggle buttons. +An on / off control, inspired by toggle buttons. [Switch reference](./widgets/switch.md){ .md-button .md-button--primary } @@ -307,6 +307,14 @@ A Combination of Tabs and ContentSwitcher to navigate static content. ```{.textual path="docs/examples/widgets/tabbed_content.py" press="j"} ``` +## TextArea + +A multi-line text area which supports syntax highlighting various languages. + +[TextArea reference](./widgets/text_area.md){ .md-button .md-button--primary } + +```{.textual path="docs/examples/widgets/text_area.py" columns="42" lines="8"} +``` ## Tree diff --git a/docs/widgets/_template.md b/docs/widgets/_template.md index ecedff151c..70c6a43385 100644 --- a/docs/widgets/_template.md +++ b/docs/widgets/_template.md @@ -32,6 +32,7 @@ Example app showing the widget: ## Reactive Attributes +## Messages ## Bindings diff --git a/docs/widgets/text_area.md b/docs/widgets/text_area.md new file mode 100644 index 0000000000..2fddae64eb --- /dev/null +++ b/docs/widgets/text_area.md @@ -0,0 +1,467 @@ + +# TextArea + +!!! tip "Added in version 0.38.0" + +A widget for editing text which may span multiple lines. +Supports syntax highlighting for a selection of languages. + +- [x] Focusable +- [ ] Container + + +## Guide + +### Loading text + +In this example we load some initial text into the `TextArea`, and set the language to `"python"` to enable syntax highlighting. + +=== "Output" + + ```{.textual path="docs/examples/widgets/text_area_example.py" columns="42" lines="8"} + ``` + +=== "text_area_example.py" + + ```python + --8<-- "docs/examples/widgets/text_area_example.py" + ``` + +To load content into the `TextArea` after it has already been created, +use the [`load_text`][textual.widgets._text_area.TextArea.load_text] method. + +To update the parser used for syntax highlighting, set the [`language`][textual.widgets._text_area.TextArea.language] reactive attribute: + +```python +# Set the language to Markdown +text_area.language = "markdown" +``` + +!!! note + Syntax highlighting is unavailable on Python 3.7. + +!!! note + More built-in languages will be added in the future. For now, you can [add your own](#adding-support-for-custom-languages). + + +### Reading content from `TextArea` + +There are a number of ways to retrieve content from the `TextArea`: + +- The [`TextArea.text`][textual.widgets._text_area.TextArea.text] property returns all content in the text area as a string. +- The [`TextArea.selected_text`][textual.widgets._text_area.TextArea.selected_text] property returns the text corresponding to the current selection. +- The [`TextArea.get_text_range`][textual.widgets._text_area.TextArea.get_text_range] method returns the text between two locations. + +In all cases, when multiple lines of text are retrieved, the [document line separator](#line-separators) will be used. + +### Editing content inside `TextArea` + +The content of the `TextArea` can be updated using the [`replace`][textual.widgets._text_area.TextArea.replace] method. +This method is the programmatic equivalent of selecting some text and then pasting. + +Some other convenient methods are available, such as [`insert`][textual.widgets._text_area.TextArea.insert], [`delete`][textual.widgets._text_area.TextArea.delete], and [`clear`][textual.widgets._text_area.TextArea.clear]. + +### Working with the cursor + +#### Moving the cursor + +The cursor location is available via the [`cursor_location`][textual.widgets._text_area.TextArea.cursor_location] property, which represents +the location of the cursor as a tuple `(row_index, column_index)`. These indices are zero-based. +Writing a new value to `cursor_location` will immediately update the location of the cursor. + +```python +>>> text_area = TextArea() +>>> text_area.cursor_location +(0, 0) +>>> text_area.cursor_location = (0, 4) +>>> text_area.cursor_location +(0, 4) +``` + +`cursor_location` is a simple way to move the cursor programmatically, but it doesn't let us select text. + +#### Selecting text + +To select text, we can use the `selection` reactive attribute. +Let's select the first two lines of text in a document by adding `text_area.selection = Selection(start=(0, 0), end=(2, 0))` to our code: + +=== "Output" + + ```{.textual path="docs/examples/widgets/text_area_selection.py" columns="42" lines="8"} + ``` + +=== "text_area_selection.py" + + ```python hl_lines="17" + --8<-- "docs/examples/widgets/text_area_selection.py" + ``` + + 1. Selects the first two lines of text. + +Note that selections can happen in both directions, so `Selection((2, 0), (0, 0))` is also valid. + +!!! tip + + The `end` attribute of the `selection` is always equal to `TextArea.cursor_location`. In other words, + the `cursor_location` attribute is simply a convenience for accessing `text_area.selection.end`. + +#### More cursor utilities + +There are a number of additional utility methods available for interacting with the cursor. + +##### Location information + +A number of properties exist on `TextArea` which give information about the current cursor location. +These properties begin with `cursor_at_`, and return booleans. +For example, [`cursor_at_start_of_line`][textual.widgets._text_area.TextArea.cursor_at_start_of_line] tells us if the cursor is at a start of line. + +We can also check the location the cursor _would_ arrive at if we were to move it. +For example, [`get_cursor_right_location`][textual.widgets._text_area.TextArea.get_cursor_right_location] returns the location +the cursor would move to if it were to move right. +A number of similar methods exist, with names like `get_cursor_*_location`. + +##### Cursor movement methods + +The [`move_cursor`][textual.widgets._text_area.TextArea.move_cursor] method allows you to move the cursor to a new location while selecting +text, or move the cursor and scroll to keep it centered. + +```python +# Move the cursor from its current location to row index 4, +# column index 8, while selecting all the text between. +text_area.move_cursor((4, 8), select=True) +``` + +The [`move_cursor_relative`][textual.widgets._text_area.TextArea.move_cursor_relative] method offers a very similar interface, but moves the cursor relative +to its current location. + +##### Common selections + +There are some methods available which make common selections easier: + +- [`select_line`][textual.widgets._text_area.TextArea.select_line] selects a line by index. Bound to ++f6++ by default. +- [`select_all`][textual.widgets._text_area.TextArea.select_all] selects all text. Bound to ++f7++ by default. + +### Themes + +`TextArea` ships with some builtin themes, and you can easily add your own. + +Themes give you control over the look and feel, including syntax highlighting, +the cursor, selection, gutter, and more. + +#### Using builtin themes + +The initial theme of the `TextArea` is determined by the `theme` parameter. + +```python +# Create a TextArea with the 'dracula' theme. +yield TextArea("print(123)", language="python", theme="dracula") +``` + +You can check which themes are available using the [`available_themes`][textual.widgets._text_area.TextArea.available_themes] property. + +```python +>>> text_area = TextArea() +>>> print(text_area.available_themes) +{'dracula', 'github_light', 'monokai', 'vscode_dark'} +``` + +After creating a `TextArea`, you can change the theme by setting the [`theme`][textual.widgets._text_area.TextArea.theme] +attribute to one of the available themes. + +```python +text_area.theme = "vscode_dark" +``` + +On setting this attribute the `TextArea` will immediately refresh to display the updated theme. + +#### Custom themes + +Using custom (non-builtin) themes is two-step process: + +1. Create an instance of [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme]. +2. Register it using [`TextArea.register_theme`][textual.widgets._text_area.TextArea.register_theme]. + +##### 1. Creating a theme + +Let's create a simple theme, `"my_cool_theme"`, which colors the cursor blue, and the cursor line yellow. +Our theme will also syntax highlight strings as red, and comments as magenta. + +```python +from rich.style import Style +from textual.widgets.text_area import TextAreaTheme +# ... +my_theme = TextAreaTheme( + # This name will be used to refer to the theme... + name="my_cool_theme", + # Basic styles such as background, cursor, selection, gutter, etc... + cursor_style=Style(color="white", bgcolor="blue"), + cursor_line_style=Style(bgcolor="yellow"), + # `syntax_styles` is for syntax highlighting. + # It maps tokens parsed from the document to Rich styles. + syntax_styles={ + "string": Style(color="red"), + "comment": Style(color="magenta"), + } +) +``` + +Attributes like `cursor_style` and `cursor_line_style` apply general language-agnostic +styling to the widget. + +The `syntax_styles` attribute of `TextAreaTheme` is used for syntax highlighting and +depends on the `language` currently in use. +For more details, see [syntax highlighting](#syntax-highlighting). + +If you wish to build on an existing theme, you can obtain a reference to it using the [`TextAreaTheme.get_builtin_theme`][textual.widgets.text_area.TextAreaTheme.get_builtin_theme] classmethod: + +```python +from textual.widgets.text_area import TextAreaTheme + +monokai = TextAreaTheme.get_builtin_theme("monokai") +``` + +##### 2. Registering a theme + +Our theme can now be registered with the `TextArea` instance. + +```python +text_area.register_theme(my_theme) +``` + +After registering a theme, it'll appear in the `available_themes`: + +```python +>>> print(text_area.available_themes) +{'dracula', 'github_light', 'monokai', 'vscode_dark', 'my_cool_theme'} +``` + +We can now switch to it: + +```python +text_area.theme = "my_cool_theme" +``` + +This immediately updates the appearance of the `TextArea`: + +```{.textual path="docs/examples/widgets/text_area_custom_theme.py" columns="42" lines="8"} +``` + +### Indentation + +The character(s) inserted when you press tab is controlled by setting the `indent_type` attribute to either `tabs` or `spaces`. + +If `indent_type == "spaces"`, pressing ++tab++ will insert up to `indent_width` spaces in order to align with the next tab stop. + +### Line separators + +When content is loaded into `TextArea`, the content is scanned from beginning to end +and the first occurrence of a line separator is recorded. + +This separator will then be used when content is later read from the `TextArea` via +the `text` property. The `TextArea` widget does not support exporting text which +contains mixed line endings. + +Similarly, newline characters pasted into the `TextArea` will be converted. + +You can check the line separator of the current document by inspecting `TextArea.document.newline`: + +```python +>>> text_area = TextArea() +>>> text_area.document.newline +'\n' +``` + +### Line numbers + +The gutter (column on the left containing line numbers) can be toggled by setting +the `show_line_numbers` attribute to `True` or `False`. + +Setting this attribute will immediately repaint the `TextArea` to reflect the new value. + +### Extending `TextArea` + +Sometimes, you may wish to subclass `TextArea` to add some extra functionality. +In this section, we'll briefly explore how we can extend the widget to achieve common goals. + +#### Hooking into key presses + +You may wish to hook into certain key presses to inject some functionality. +This can be done by over-riding `_on_key` and adding the required functionality. + +##### Example - closing parentheses automatically + +Let's extend `TextArea` to add a feature which automatically closes parentheses and moves the cursor to a sensible location. + +```python +--8<-- "docs/examples/widgets/text_area_extended.py" +``` + +This intercepts the key handler when `"("` is pressed, and inserts `"()"` instead. +It then moves the cursor so that it lands between the open and closing parentheses. + +Typing `def hello(` into the `TextArea` results in the bracket automatically being closed: + +```{.textual path="docs/examples/widgets/text_area_extended.py" columns="36" lines="4" press="d,e,f,space,h,e,l,l,o,left_parenthesis"} +``` + +### Advanced concepts + +#### Syntax highlighting + +Syntax highlighting inside the `TextArea` is powered by a library called [`tree-sitter`](https://tree-sitter.github.io/tree-sitter/). + +Each time you update the document in a `TextArea`, an internal syntax tree is updated. +This tree is frequently _queried_ to find location ranges relevant to syntax highlighting. +We give these ranges _names_, and ultimately map them to Rich styles inside `TextAreaTheme.syntax_styles`. + +To illustrate how this works, lets look at how the "Monokai" `TextAreaTheme` highlights Markdown files. + +When the `language` attribute is set to `"markdown"`, a highlight query similar to the one below is used (trimmed for brevity). + +```scheme +(heading_content) @heading +(link) @link +``` + +This highlight query maps `heading_content` nodes returned by the Markdown parser to the name `@heading`, +and `link` nodes to the name `@link`. + +Inside our `TextAreaTheme.syntax_styles` dict, we can map the name `@heading` to a Rich style. +Here's a snippet from the "Monokai" theme which does just that: + +```python +TextAreaTheme( + name="monokai", + base_style=Style(color="#f8f8f2", bgcolor="#272822"), + gutter_style=Style(color="#90908a", bgcolor="#272822"), + # ... + syntax_styles={ + # Colorise @heading and make them bold + "heading": Style(color="#F92672", bold=True), + # Colorise and underline @link + "link": Style(color="#66D9EF", underline=True), + # ... + }, +) +``` + +To understand which names can be mapped inside `syntax_styles`, we recommend looking at the existing +themes and highlighting queries (`.scm` files) in the Textual repository. + +!!! tip + + You may also wish to take a look at the contents of `TextArea._highlights` on an + active `TextArea` instance to see which highlights have been generated for the + open document. + +#### Adding support for custom languages + +To add support for a language to a `TextArea`, use the [`register_language`][textual.widgets._text_area.TextArea.register_language] method. + +To register a language, we require two things: + +1. A tree-sitter `Language` object which contains the grammar for the language. +2. A highlight query which is used for [syntax highlighting](#syntax-highlighting). + +##### Example - adding Java support + +The easiest way to obtain a `Language` object is using the [`py-tree-sitter-languages`](https://github.com/grantjenks/py-tree-sitter-languages) package. Here's how we can use this package to obtain a reference to a `Language` object representing Java: + +```python +from tree_sitter_languages import get_language +java_language = get_language("java") +``` + +!!! note + + `py-tree-sitter-languages` may not be available on some architectures (e.g. Macbooks with Apple Silicon running Python 3.7). + +The exact version of the parser used when you call `get_language` can be checked via +the [`repos.txt` file](https://github.com/grantjenks/py-tree-sitter-languages/blob/a6d4f7c903bf647be1bdcfa504df967d13e40427/repos.txt) in +the version of `py-tree-sitter-languages` you're using. This file contains links to the GitHub +repos and commit hashes of the tree-sitter parsers. In these repos you can often find pre-made highlight queries at `queries/highlights.scm`, +and a file showing all the available node types which can be used in highlight queries at `src/node-types.json`. + +Since we're adding support for Java, lets grab the Java highlight query from the repo by following these steps: + +1. Open [`repos.txt` file](https://github.com/grantjenks/py-tree-sitter-languages/blob/a6d4f7c903bf647be1bdcfa504df967d13e40427/repos.txt) from the `py-tree-sitter-languages` repo. +2. Find the link corresponding to `tree-sitter-java` and go to the repo on GitHub (you may also need to go to the specific commit referenced in `repos.txt`). +3. Go to [`queries/highlights.scm`](https://github.com/tree-sitter/tree-sitter-java/blob/ac14b4b1884102839455d32543ab6d53ae089ab7/queries/highlights.scm) to see the example highlight query for Java. + +Be sure to check the license in the repo to ensure it can be freely copied. + +!!! warning + + It's important to use a highlight query which is compatible with the parser in use, so + pay attention to the commit hash when visiting the repo via `repos.txt`. + +We now have our `Language` and our highlight query, so we can register Java as a language. + +```python +--8<-- "docs/examples/widgets/text_area_custom_language.py" +``` + +Running our app, we can see that the Java code is highlighted. +We can freely edit the text, and the syntax highlighting will update immediately. + +```{.textual path="docs/examples/widgets/text_area_custom_language.py" columns="52" lines="8"} +``` + +Recall that we map names (like `@heading`) from the tree-sitter highlight query to Rich style objects inside the `TextAreaTheme.syntax_styles` dictionary. +If you notice some highlights are missing after registering a language, the issue may be: + +1. The current `TextAreaTheme` doesn't contain a mapping for the name in the highlight query. Adding a new to `syntax_styles` should resolve the issue. +2. The highlight query doesn't assign a name to the pattern you expect to be highlighted. In this case you'll need to update the highlight query to assign to the name. + +!!! tip + + The names assigned in tree-sitter highlight queries are often reused across multiple languages. + For example, `@string` is used in many languages to highlight strings. + +## Reactive attributes + +| Name | Type | Default | Description | +|------------------------|--------------------------|--------------------|--------------------------------------------------| +| `language` | `str | None` | `None` | The language to use for syntax highlighting. | +| `theme` | `str | None` | `TextAreaTheme.default()` | The theme to use for syntax highlighting. | +| `selection` | `Selection` | `Selection()` | The current selection. | +| `show_line_numbers` | `bool` | `True` | Show or hide line numbers. | +| `indent_width` | `int` | `4` | The number of spaces to indent and width of tabs. | +| `match_cursor_bracket` | `bool` | `True` | Enable/disable highlighting matching brackets under cursor. | +| `cursor_blink` | `bool` | `True` | Enable/disable blinking of the cursor when the widget has focus. | + +## Bindings + +The `TextArea` widget defines the following bindings: + +::: textual.widgets._text_area.TextArea.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + + +## Component classes + +The `TextArea` widget defines no component classes. + +Styling should be done exclusively via [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme]. + +## See also + +- [`Input`][textual.widgets.Input] - for single-line text input. +- [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme] - for theming the `TextArea`. +- The tree-sitter documentation [website](https://tree-sitter.github.io/tree-sitter/). +- The tree-sitter Python bindings [repository](https://github.com/tree-sitter/py-tree-sitter). +- `py-tree-sitter-languages` [repository](https://github.com/grantjenks/py-tree-sitter-languages) (provides binary wheels for a large variety of tree-sitter languages). + +--- + +::: textual.widgets._text_area.TextArea + options: + heading_level: 2 + +--- + +::: textual.widgets.text_area + options: + heading_level: 2 diff --git a/mkdocs-nav.yml b/mkdocs-nav.yml index 66e3f2480b..2d61063111 100644 --- a/mkdocs-nav.yml +++ b/mkdocs-nav.yml @@ -164,6 +164,7 @@ nav: - "widgets/switch.md" - "widgets/tabbed_content.md" - "widgets/tabs.md" + - "widgets/text_area.md" - "widgets/tree.md" - API: - "api/index.md" diff --git a/poetry.lock b/poetry.lock index 1d1ce00cba..fcd778a16c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,3 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. - [[package]] name = "aiohttp" version = "3.8.5" @@ -7,95 +5,6 @@ description = "Async http client/server framework (asyncio)" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, - {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, - {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, - {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, - {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, - {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, - {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, - {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, - {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, - {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, - {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, - {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, - {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, - {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, - {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, - {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, - {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, - {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, - {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, - {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, - {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, - {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, - {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, - {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, - {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, -] [package.dependencies] aiosignal = ">=1.1.2" @@ -118,10 +27,6 @@ description = "aiosignal: a list of registered asynchronous callbacks" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] [package.dependencies] frozenlist = ">=1.1.0" @@ -133,10 +38,6 @@ description = "High level compatibility layer for multiple asynchronous event lo category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, -] [package.dependencies] exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} @@ -156,10 +57,6 @@ description = "Timeout context manager for asyncio programs" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] [package.dependencies] typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} @@ -171,10 +68,6 @@ description = "Enhance the standard unittest package with features for testing a category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, - {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, -] [[package]] name = "attrs" @@ -183,10 +76,6 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} @@ -205,10 +94,6 @@ description = "Internationalization utilities" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, - {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, -] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} @@ -220,33 +105,6 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, -] [package.dependencies] click = ">=8.0.0" @@ -271,10 +129,6 @@ description = "A decorator for caching properties in classes." category = "dev" optional = false python-versions = "*" -files = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, -] [[package]] name = "certifi" @@ -283,10 +137,6 @@ description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, -] [[package]] name = "cfgv" @@ -295,10 +145,6 @@ description = "Validate configuration and produce human readable error messages. category = "dev" optional = false python-versions = ">=3.6.1" -files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] [[package]] name = "charset-normalizer" @@ -307,83 +153,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ category = "dev" optional = false python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, -] [[package]] name = "click" @@ -392,10 +161,6 @@ description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -408,10 +173,6 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] [[package]] name = "colored" @@ -420,9 +181,6 @@ description = "Simple library for color and formatting to terminal" category = "dev" optional = false python-versions = "*" -files = [ - {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, -] [[package]] name = "coverage" @@ -431,68 +189,6 @@ description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, -] [package.extras] toml = ["tomli"] @@ -504,10 +200,6 @@ description = "Distribution utilities" category = "dev" optional = false python-versions = "*" -files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, -] [[package]] name = "exceptiongroup" @@ -516,10 +208,6 @@ description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, -] [package.extras] test = ["pytest (>=6)"] @@ -531,10 +219,6 @@ description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, -] [package.extras] docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] @@ -547,82 +231,6 @@ description = "A list-like structure which implements collections.abc.MutableSeq category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, - {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, - {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, - {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, - {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, - {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, - {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, - {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, - {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, - {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, - {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, -] [[package]] name = "ghp-import" @@ -631,10 +239,6 @@ description = "Copy your docs directly to the gh-pages branch." category = "dev" optional = false python-versions = "*" -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] [package.dependencies] python-dateutil = ">=2.8.1" @@ -649,10 +253,6 @@ description = "Git Object Database" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, - {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, -] [package.dependencies] smmap = ">=3.0.1,<6" @@ -664,10 +264,6 @@ description = "GitPython is a Python library used to interact with Git repositor category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.36-py3-none-any.whl", hash = "sha256:8d22b5cfefd17c79914226982bb7851d6ade47545b1735a9d010a2a4c26d8388"}, - {file = "GitPython-3.1.36.tar.gz", hash = "sha256:4bb0c2a6995e85064140d31a33289aa5dce80133a23d36fcd372d716c54d3ebf"}, -] [package.dependencies] gitdb = ">=4.0.1,<5" @@ -683,10 +279,6 @@ description = "Signatures for entire Python programs. Extract the structure, the category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "griffe-0.30.1-py3-none-any.whl", hash = "sha256:b2f3df6952995a6bebe19f797189d67aba7c860755d3d21cc80f64d076d0154c"}, - {file = "griffe-0.30.1.tar.gz", hash = "sha256:007cc11acd20becf1bb8f826419a52b9d403bbad9d8c8535699f5440ddc0a109"}, -] [package.dependencies] cached-property = {version = "*", markers = "python_version < \"3.8\""} @@ -699,10 +291,6 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -714,10 +302,6 @@ description = "A minimal low-level HTTP client." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, - {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, -] [package.dependencies] anyio = ">=3.0,<5.0" @@ -736,10 +320,6 @@ description = "The next generation HTTP client." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, -] [package.dependencies] certifi = "*" @@ -760,10 +340,6 @@ description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, -] [package.extras] license = ["ukkonen"] @@ -775,10 +351,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] [[package]] name = "importlib-metadata" @@ -787,10 +359,6 @@ description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -808,10 +376,6 @@ description = "brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] [[package]] name = "jinja2" @@ -820,10 +384,6 @@ description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] [package.dependencies] MarkupSafe = ">=2.0" @@ -838,10 +398,6 @@ description = "Links recognition library with FULL unicode support." category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"}, - {file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"}, -] [package.dependencies] uc-micro-py = "*" @@ -859,10 +415,6 @@ description = "Python implementation of John Gruber's Markdown." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, - {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, -] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} @@ -878,10 +430,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, - {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, -] [package.dependencies] linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""} @@ -906,58 +454,6 @@ description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] [[package]] name = "mdit-py-plugins" @@ -966,10 +462,6 @@ description = "Collection of plugins for markdown-it-py" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, - {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, -] [package.dependencies] markdown-it-py = ">=1.0.0,<3.0.0" @@ -986,10 +478,6 @@ description = "Markdown URL utilities" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] [[package]] name = "mergedeep" @@ -998,22 +486,14 @@ description = "A deep merge function for 🐍." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] [[package]] name = "mkdocs" -version = "1.5.2" +version = "1.5.3" description = "Project documentation with Markdown." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs-1.5.2-py3-none-any.whl", hash = "sha256:60a62538519c2e96fe8426654a67ee177350451616118a41596ae7c876bb7eac"}, - {file = "mkdocs-1.5.2.tar.gz", hash = "sha256:70d0da09c26cff288852471be03c23f0f521fc15cf16ac89c7a3bfb9ae8d24f9"}, -] [package.dependencies] click = ">=7.0" @@ -1043,10 +523,6 @@ description = "Automatically link across pages in MkDocs." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, -] [package.dependencies] Markdown = ">=3.3" @@ -1059,9 +535,6 @@ description = "A mkdocs plugin that lets you exclude files or trees." category = "dev" optional = false python-versions = "*" -files = [ - {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, -] [package.dependencies] mkdocs = "*" @@ -1073,10 +546,6 @@ description = "Documentation that simply works" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs_material-9.2.7-py3-none-any.whl", hash = "sha256:92e4160d191cc76121fed14ab9f14638e43a6da0f2e9d7a9194d377f0a4e7f18"}, - {file = "mkdocs_material-9.2.7.tar.gz", hash = "sha256:b44da35b0d98cd762d09ef74f1ddce5b6d6e35c13f13beb0c9d82a629e5f229e"}, -] [package.dependencies] babel = ">=2.10,<3.0" @@ -1098,10 +567,6 @@ description = "Extension pack for Python Markdown and MkDocs Material." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, - {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, -] [[package]] name = "mkdocs-rss-plugin" @@ -1110,10 +575,6 @@ description = "MkDocs plugin which generates a static RSS feed using git log and category = "dev" optional = false python-versions = ">=3.7, <4" -files = [ - {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, - {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, -] [package.dependencies] GitPython = ">=3.1,<3.2" @@ -1132,10 +593,6 @@ description = "Automatic documentation from sources, for MkDocs." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, - {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, -] [package.dependencies] Jinja2 = ">=2.11.1" @@ -1158,10 +615,6 @@ description = "A Python handler for mkdocstrings." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mkdocstrings_python-0.10.1-py3-none-any.whl", hash = "sha256:ef239cee2c688e2b949a0a47e42a141d744dd12b7007311b3309dc70e3bafc5c"}, - {file = "mkdocstrings_python-0.10.1.tar.gz", hash = "sha256:b72301fff739070ec517b5b36bf2f7c49d1360a275896a64efb97fc17d3f3968"}, -] [package.dependencies] griffe = ">=0.24" @@ -1174,71 +627,6 @@ description = "MessagePack serializer" category = "dev" optional = false python-versions = "*" -files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, -] [[package]] name = "multidict" @@ -1247,82 +635,6 @@ description = "multidict implementation" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] [[package]] name = "mypy" @@ -1331,34 +643,6 @@ description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, -] [package.dependencies] mypy-extensions = ">=1.0.0" @@ -1379,10 +663,6 @@ description = "Type system extensions for programs checked with the mypy type ch category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] [[package]] name = "nodeenv" @@ -1391,10 +671,6 @@ description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] [package.dependencies] setuptools = "*" @@ -1406,10 +682,6 @@ description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, -] [[package]] name = "paginate" @@ -1418,9 +690,6 @@ description = "Divides large result sets into pages for easier browsing" category = "dev" optional = false python-versions = "*" -files = [ - {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, -] [[package]] name = "pathspec" @@ -1429,10 +698,6 @@ description = "Utility library for gitignore style pattern matching of file path category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] [[package]] name = "platformdirs" @@ -1441,10 +706,6 @@ description = "A small Python package for determining appropriate platform-speci category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, -] [package.dependencies] typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""} @@ -1460,10 +721,6 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, -] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -1479,10 +736,6 @@ description = "A framework for managing and maintaining multi-language pre-commi category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, -] [package.dependencies] cfgv = ">=2.0.0" @@ -1499,10 +752,6 @@ description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, -] [package.extras] plugins = ["importlib-metadata"] @@ -1514,10 +763,6 @@ description = "Extension pack for Python Markdown." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pymdown_extensions-10.2.1-py3-none-any.whl", hash = "sha256:bded105eb8d93f88f2f821f00108cb70cef1269db6a40128c09c5f48bfc60ea4"}, - {file = "pymdown_extensions-10.2.1.tar.gz", hash = "sha256:d0c534b4a5725a4be7ccef25d65a4c97dba58b54ad7c813babf0eb5ba9c81591"}, -] [package.dependencies] markdown = ">=3.2" @@ -1533,10 +778,6 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, -] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} @@ -1557,10 +798,6 @@ description = "Pytest plugin for aiohttp support" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, - {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, -] [package.dependencies] aiohttp = ">=3.8.1" @@ -1577,10 +814,6 @@ description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, - {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, -] [package.dependencies] pytest = ">=7.0.0" @@ -1597,10 +830,6 @@ description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, -] [package.dependencies] coverage = ">=5.2.1" @@ -1617,10 +846,6 @@ description = "Snapshot testing for Textual apps" category = "dev" optional = false python-versions = ">=3.6,<4.0" -files = [ - {file = "pytest_textual_snapshot-0.4.0-py3-none-any.whl", hash = "sha256:879cc5de29cdd31cfe1b6daeb1dc5e42682abebcf4f88e7e3375bd5200683fc0"}, - {file = "pytest_textual_snapshot-0.4.0.tar.gz", hash = "sha256:63782e053928a925d88ff7359dd640f2900e23bc708b3007f8b388e65f2527cb"}, -] [package.dependencies] jinja2 = ">=3.0.0" @@ -1636,10 +861,6 @@ description = "Extensions to the standard Python datetime module" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] [package.dependencies] six = ">=1.5" @@ -1651,10 +872,6 @@ description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" -files = [ - {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, - {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, -] [[package]] name = "pyyaml" @@ -1663,48 +880,6 @@ description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] [[package]] name = "pyyaml-env-tag" @@ -1713,10 +888,6 @@ description = "A custom YAML tag for referencing environment variables in YAML f category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] [package.dependencies] pyyaml = "*" @@ -1728,7 +899,1129 @@ description = "Alternative regular expression module, to replace re." category = "dev" optional = false python-versions = ">=3.6" -files = [ + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "13.5.3" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "syrupy" +version = "3.0.6" +description = "Pytest Snapshot Test Utility" +category = "dev" +optional = false +python-versions = ">=3.7,<4" + +[package.dependencies] +colored = ">=1.3.92,<2.0.0" +pytest = ">=5.1.0,<8.0.0" + +[[package]] +name = "textual-dev" +version = "1.1.0" +description = "Development tools for working with Textual" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +aiohttp = ">=3.8.1" +click = ">=8.1.2" +msgpack = ">=1.0.3" +textual = ">=0.32.0" +typing-extensions = ">=4.4.0,<5.0.0" + +[[package]] +name = "time-machine" +version = "2.10.0" +description = "Travel through time in your tests." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +python-dateutil = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tree-sitter" +version = "0.20.2" +description = "Python bindings for the Tree-Sitter parsing library" +category = "main" +optional = false +python-versions = ">=3.3" + +[[package]] +name = "tree-sitter-languages" +version = "1.7.0" +description = "Binary Python wheels for all tree sitter languages." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +tree-sitter = "*" + +[[package]] +name = "typed-ast" +version = "1.5.5" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "types-setuptools" +version = "67.8.0.0" +description = "Typing stubs for setuptools" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-tree-sitter" +version = "0.20.1.5" +description = "Typing stubs for tree-sitter" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-tree-sitter-languages" +version = "1.7.0.1" +description = "Typing stubs for tree-sitter-languages" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +types-tree-sitter = "*" + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2022.7" +description = "Provider of IANA time zone data" +category = "dev" +optional = false +python-versions = ">=2" + +[[package]] +name = "uc-micro-py" +version = "1.0.2" +description = "Micro subset of unicode data files for linkify-it-py projects." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["coverage", "pytest", "pytest-cov"] + +[[package]] +name = "urllib3" +version = "2.0.5" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.24.5" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "c53cf8b109a11121625f7fb1037b22ff677dea740b70a4318edbd2829ea6080b" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] +aiosignal = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] +anyio = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] +async-timeout = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] +asynctest = [ + {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, + {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, +] +attrs = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] +babel = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] +black = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] +cached-property = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] +certifi = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] +click = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +colored = [ + {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, +] +coverage = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] +distlib = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] +filelock = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] +frozenlist = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, +] +ghp-import = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] +gitdb = [ + {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, + {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, +] +gitpython = [ + {file = "GitPython-3.1.36-py3-none-any.whl", hash = "sha256:8d22b5cfefd17c79914226982bb7851d6ade47545b1735a9d010a2a4c26d8388"}, + {file = "GitPython-3.1.36.tar.gz", hash = "sha256:4bb0c2a6995e85064140d31a33289aa5dce80133a23d36fcd372d716c54d3ebf"}, +] +griffe = [ + {file = "griffe-0.30.1-py3-none-any.whl", hash = "sha256:b2f3df6952995a6bebe19f797189d67aba7c860755d3d21cc80f64d076d0154c"}, + {file = "griffe-0.30.1.tar.gz", hash = "sha256:007cc11acd20becf1bb8f826419a52b9d403bbad9d8c8535699f5440ddc0a109"}, +] +h11 = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +httpcore = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] +httpx = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] +identify = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +importlib-metadata = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] +iniconfig = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] +jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +linkify-it-py = [ + {file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"}, + {file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"}, +] +markdown = [ + {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, +] +markdown-it-py = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] +mdit-py-plugins = [ + {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, + {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, +] +mdurl = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] +mergedeep = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] +mkdocs = [ + {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, + {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, +] +mkdocs-autorefs = [ + {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, + {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, +] +mkdocs-exclude = [ + {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, +] +mkdocs-material = [ + {file = "mkdocs_material-9.2.7-py3-none-any.whl", hash = "sha256:92e4160d191cc76121fed14ab9f14638e43a6da0f2e9d7a9194d377f0a4e7f18"}, + {file = "mkdocs_material-9.2.7.tar.gz", hash = "sha256:b44da35b0d98cd762d09ef74f1ddce5b6d6e35c13f13beb0c9d82a629e5f229e"}, +] +mkdocs-material-extensions = [ + {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, + {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, +] +mkdocs-rss-plugin = [ + {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, + {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, +] +mkdocstrings = [ + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, +] +mkdocstrings-python = [ + {file = "mkdocstrings_python-0.10.1-py3-none-any.whl", hash = "sha256:ef239cee2c688e2b949a0a47e42a141d744dd12b7007311b3309dc70e3bafc5c"}, + {file = "mkdocstrings_python-0.10.1.tar.gz", hash = "sha256:b72301fff739070ec517b5b36bf2f7c49d1360a275896a64efb97fc17d3f3968"}, +] +msgpack = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] +multidict = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] +mypy = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] +mypy-extensions = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] +nodeenv = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] +packaging = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] +paginate = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] +pathspec = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] +platformdirs = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] +pluggy = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] +pre-commit = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] +pygments = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] +pymdown-extensions = [ + {file = "pymdown_extensions-10.2.1-py3-none-any.whl", hash = "sha256:bded105eb8d93f88f2f821f00108cb70cef1269db6a40128c09c5f48bfc60ea4"}, + {file = "pymdown_extensions-10.2.1.tar.gz", hash = "sha256:d0c534b4a5725a4be7ccef25d65a4c97dba58b54ad7c813babf0eb5ba9c81591"}, +] +pytest = [ + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, +] +pytest-aiohttp = [ + {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, + {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, + {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, +] +pytest-cov = [ + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, +] +pytest-textual-snapshot = [ + {file = "pytest_textual_snapshot-0.4.0-py3-none-any.whl", hash = "sha256:879cc5de29cdd31cfe1b6daeb1dc5e42682abebcf4f88e7e3375bd5200683fc0"}, + {file = "pytest_textual_snapshot-0.4.0.tar.gz", hash = "sha256:63782e053928a925d88ff7359dd640f2900e23bc708b3007f8b388e65f2527cb"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [ + {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, + {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, +] +pyyaml = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] +pyyaml-env-tag = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] +regex = [ {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"}, {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"}, {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"}, @@ -1818,163 +2111,43 @@ files = [ {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, ] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +requests = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "dev" -optional = false -python-versions = "*" -files = [ +rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "rich" -version = "13.5.3" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.7.0" -files = [ +rich = [ {file = "rich-13.5.3-py3-none-any.whl", hash = "sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9"}, {file = "rich-13.5.3.tar.gz", hash = "sha256:87b43e0543149efa1253f485cd845bb7ee54df16c9617b8a893650ab84b4acb6"}, ] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "setuptools" -version = "68.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +setuptools = [ {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, ] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ +six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] - -[[package]] -name = "smmap" -version = "5.0.1" -description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +smmap = [ {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, ] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +sniffio = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] - -[[package]] -name = "syrupy" -version = "3.0.6" -description = "Pytest Snapshot Test Utility" -category = "dev" -optional = false -python-versions = ">=3.7,<4" -files = [ +syrupy = [ {file = "syrupy-3.0.6-py3-none-any.whl", hash = "sha256:9c18e22264026b34239bcc87ab7cc8d893eb17236ea7dae634217ea4f22a848d"}, {file = "syrupy-3.0.6.tar.gz", hash = "sha256:583aa5ca691305c27902c3e29a1ce9da50ff9ab5f184c54b1dc124a16e4a6cf4"}, ] - -[package.dependencies] -colored = ">=1.3.92,<2.0.0" -pytest = ">=5.1.0,<8.0.0" - -[[package]] -name = "textual-dev" -version = "1.1.0" -description = "Development tools for working with Textual" -category = "dev" -optional = false -python-versions = ">=3.7,<4.0" -files = [ +textual-dev = [ {file = "textual_dev-1.1.0-py3-none-any.whl", hash = "sha256:c57320636098e31fa5d5c29fc3bc60829bb420da3c76bfed24db6eacf178dbc6"}, {file = "textual_dev-1.1.0.tar.gz", hash = "sha256:e2f8ce4e1c18a16b80282f3257cd2feb49a7ede289a78908c9063ce071bb77ce"}, ] - -[package.dependencies] -aiohttp = ">=3.8.1" -click = ">=8.1.2" -msgpack = ">=1.0.3" -textual = ">=0.32.0" -typing-extensions = ">=4.4.0,<5.0.0" - -[[package]] -name = "time-machine" -version = "2.10.0" -description = "Travel through time in your tests." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +time-machine = [ {file = "time_machine-2.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d5e93c14b935d802a310c1d4694a9fe894b48a733ebd641c9a570d6f9e1f667"}, {file = "time_machine-2.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c0dda6b132c0180941944ede357109016d161d840384c2fb1096a3a2ef619f4"}, {file = "time_machine-2.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:900517e4a4121bf88527343d6aea2b5c99df134815bb8271ef589ec792502a71"}, @@ -2030,42 +2203,124 @@ files = [ {file = "time_machine-2.10.0-cp39-cp39-win_arm64.whl", hash = "sha256:c1775a949dd830579d1af5a271ec53d920dc01657035ad305f55c5a1ac9b9f1e"}, {file = "time_machine-2.10.0.tar.gz", hash = "sha256:64fd89678cf589fc5554c311417128b2782222dd65f703bf248ef41541761da0"}, ] - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ +toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] - -[[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +tree-sitter = [ + {file = "tree_sitter-0.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a151ccf9233b0b84850422654247f68a4d78f548425c76520402ea6fb6cdb24"}, + {file = "tree_sitter-0.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52ca2738c3c4c660c83054ac3e44a49cbecb9f89dc26bb8e154d6ca288aa06b0"}, + {file = "tree_sitter-0.20.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8d51478ea078da7cc6f626e9e36f131bbc5fac036cf38ea4b5b81632cbac37d"}, + {file = "tree_sitter-0.20.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0b2b59e1633efbf19cd2ed1ceb8d51b2c44a278153b1113998c70bc1570b750"}, + {file = "tree_sitter-0.20.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7f691c57d2a65d6e53e2f3574153c9cd0c157ff938b8d6f252edd5e619811403"}, + {file = "tree_sitter-0.20.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba72a363387eebaff9a0b788f864fe47da425136cbd4cac6cd125051f043c296"}, + {file = "tree_sitter-0.20.2-cp310-cp310-win32.whl", hash = "sha256:55e33eb206446d5046d3b5fe36ab300840f5a8a844246adb0ccc68c55c30b722"}, + {file = "tree_sitter-0.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ce9d14daba0a71a778417d9d61dd4038ca96981ddec19e1e8990881469321c"}, + {file = "tree_sitter-0.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:942dbfb8bc380f09b0e323d3884de07d19022930516f33b7503a6eb5f6e18979"}, + {file = "tree_sitter-0.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee5651c11924d426f8d6858a40fd5090ae31574f81ef180bef2055282f43bf62"}, + {file = "tree_sitter-0.20.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fb6982b480031628dad7f229c4c8d90b17d4c281ba97848d3b100666d7fa45f"}, + {file = "tree_sitter-0.20.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:067609c6c7cb6e5a6c4be50076a380fe52b6e8f0641ee9d0da33b24a5b972e82"}, + {file = "tree_sitter-0.20.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:849d7e6b66fe7ded08a633943b30e0ed807eee76104288e6c6841433f4a9651b"}, + {file = "tree_sitter-0.20.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e85689573797e49f86e2d7cf48b9dd23bc044c477df074a78546e666d6990a29"}, + {file = "tree_sitter-0.20.2-cp311-cp311-win32.whl", hash = "sha256:098906148e44ea391a91b019d584dd8d0ea1437af62a9744e280e93163fd35ca"}, + {file = "tree_sitter-0.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:2753a87094b72fe7f02276b3948155618f53aa14e1ca20588f0eeed510f68512"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5de192cb9e7b1c882d45418decb7899f1547f7056df756bcae186bbf4966d96e"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3a77e663293a73a97edbf2a2e05001de08933eb5d311a16bdc25b9b2fac54f3"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:415da4a70c56a003758537517fe9e60b8b0c5f70becde54cc8b8f3ba810adc70"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:707fb4d7a6123b8f9f2b005d61194077c3168c0372556e7418802280eddd4892"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:75fcbfb0a61ad64e7f787eb3f8fbf29b8e2b858dc011897ad039d838a06cee02"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-win32.whl", hash = "sha256:622926530895d939fa6e1e2487e71a311c71d3b09f4c4f19301695ea866304a4"}, + {file = "tree_sitter-0.20.2-cp36-cp36m-win_amd64.whl", hash = "sha256:5c0712f031271d9bc462f1db7623d23703ed9fbcbaa6dc19ba535f58d6110774"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dfdf680ecf5619447243c4c20e4040a7b5e7afca4e1569f03c814e86bfda248"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79650ee23a15559b69542c71ed9eb3297dce21932a7c5c148be384dd0f2cd49d"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63059746b4b2f2f87dd19c208141c69452694aae32459b7a4ebca8539d13bf4"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9398d1e214d4915032cf68a678de7eb803f64d25ef04724d70b88db7bb7746e9"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b506fb2e2bd7a5a1603c644bbb90401fe488f86bbca39706addaa8d2bfc80815"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-win32.whl", hash = "sha256:405e83804ba60ca1c3dbd258adbe0d7b0f1bdce948e5eec5587a2ebedcf930ba"}, + {file = "tree_sitter-0.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a1e66d211c04144484e223922ac094a2367476e6f57000f986c5560dc5a83c6e"}, + {file = "tree_sitter-0.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f8adc325c74c042204ed47d095e0ec86f83de3c7ec4979645f86b58514f60297"}, + {file = "tree_sitter-0.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb49c861e1d111e0df119ecbfaa409e6413b8d91e8f56bcdb15f07fbc35594e"}, + {file = "tree_sitter-0.20.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e17ee83409b01fdd09021997b0c747be2f773bb2bb140ba6fb48b7e12fdd039a"}, + {file = "tree_sitter-0.20.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475ab841647a0d1bc1266c8978279f8e4f7b9520b9a7336d532e5dfc8910214d"}, + {file = "tree_sitter-0.20.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:222350189675d9814966a5c88c6c1378a2ee2f3041c439a6f1d1ff2006f403aa"}, + {file = "tree_sitter-0.20.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:31ea52f0deee70f2cb00aff01e40aae325a34ebe1661de274c9107322fb95f54"}, + {file = "tree_sitter-0.20.2-cp38-cp38-win32.whl", hash = "sha256:cceaf7287137cbca707006624a4a8d4b5ccbfec025793fde84d90524c2bb0946"}, + {file = "tree_sitter-0.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:25b9669911f21ec2b3727bb2f4dfeff6ddb6f81898c3e968d378a660e0d7f90e"}, + {file = "tree_sitter-0.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce30a17f46a6b39a04a599dea88c127a19e3e1f43a2ad0ced71b5c032d585077"}, + {file = "tree_sitter-0.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9576e8b2e663639527e01ab251b87f0bd370bfdd40515588689ebc424aec786"}, + {file = "tree_sitter-0.20.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d03731a498f624ce3536c821ef23b03d1ad569b3845b326a5b7149ef189d732c"}, + {file = "tree_sitter-0.20.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0116ecb163573ebaa0fc04cc99c90bd94c0be5cc4d0a1ebeb102de9cc9a054"}, + {file = "tree_sitter-0.20.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0943b00d3700f253c3ee6a53a71b9a6ca46defd9c0a33edb07a9388e70dc3a9e"}, + {file = "tree_sitter-0.20.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cb566b6f0b5457148cb8310a1ca3d764edf28e47fcccfe0b167861ecaa50c12"}, + {file = "tree_sitter-0.20.2-cp39-cp39-win32.whl", hash = "sha256:4544204a24c2b4d25d1731b0df83f7c819ce87c4f2538a19724b8753815ef388"}, + {file = "tree_sitter-0.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:9517b204e471d6aa59ee2232f6220f315ed5336079034d5c861a24660d6511d6"}, + {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:84343678f58cb354d22ed14b627056ffb33c540cf16c35a83db4eeee8827b935"}, + {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:611a80171d8fa6833dd0c8b022714d2ea789de15a955ec42ec4fd5fcc1032edb"}, + {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bacecfb61694c95ccee462742b3fcea50ba1baf115c42e60adf52b549ef642ce"}, + {file = "tree_sitter-0.20.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f344ae94a268479456f19712736cc7398de5822dc74cca7d39538c28085721d0"}, + {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:221784d7f326fe81ce7174ac5972800f58b9a7c5c48a03719cad9830c22e5a76"}, + {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64210ed8d2a1b7e2951f6576aa0cb7be31ad06d87da26c52961318fc54c7fe77"}, + {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2634ac73b39ceacfa431d6d95692eae7465977fa0b9e9f7ae6cb445991e829a5"}, + {file = "tree_sitter-0.20.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71663a0e8230dae99d9c55e6895bd2c9e42534ec861b255775f704ae2db70c1d"}, + {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32c3e0f30b45a58d36bf6a0ec982ca3eaa23c7f924628da499b7ad22a8abad71"}, + {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b02e4ab2158c25f6f520c93318d562da58fa4ba53e1dbd434be008f48104980"}, + {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10e567eb6961a1e86aebbe26a9ca07d324f8529bca90937a924f8aa0ea4dc127"}, + {file = "tree_sitter-0.20.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:63f8e8e69f5f25c2b565449e1b8a2aa7b6338b4f37c8658c5fbdec04858c30be"}, + {file = "tree_sitter-0.20.2.tar.gz", hash = "sha256:0a6c06abaa55de174241a476b536173bba28241d2ea85d198d33aa8bf009f028"}, +] +tree-sitter-languages = [ + {file = "tree_sitter_languages-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fd8b856c224a74c395ed9495761c3ef8ba86014dbf6037d73634436ae683c808"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:277d1bec6e101a26a4445cd7cb1eb8f8cf5a9bbad1ca80692bfae1af63568272"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0473bd896799ccc87f428766813ddedd3506cad8430dbe863b663c81d7387680"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6799419bc7e3029112f2a3f8b77b6c299f94f03bb70e5c31a437b3180486be"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e5b705c8ce6ef47fc461484878956ecd42a67cbeb0a17e323b86a4439a8fdc3d"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:28a732be6fced2f70184c1b34f64961e3b6259fe6d5f7540c91028c2a43a7109"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-win32.whl", hash = "sha256:f5cdb1ec88f0b8c617330c953555a20cc7e96ca6b1f5c68ab6db347e869cfeeb"}, + {file = "tree_sitter_languages-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:26cb344a75798fce1a73b690504d8e7789f6ba25a178efcd203444d7868caf38"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:433b56cb3dca02b30f21c596f431a2cff90905326be1f8913c3515acb984b21e"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96686390e1a01af44aedef7b33d6be82de3cf674a98a5c7b417e540e6afa62cc"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25a4b6d559fbd76c6ec1b73cf03d09f53aaa5a1b61078a3f518b162866d9d97e"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e504f199c7a4c8b1b1efb05a063450aa23234feea6fa6c06f4077f7248ea9c98"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6b29856e9314b5f68f05dfa45e6674f47535229dda32294ba6d129077a97759c"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:786fdaf3d2120eef9384b0f22d7e2e42a561073ba753c7b438e90a1e7b351650"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-win32.whl", hash = "sha256:a55a7007056d0927b78481b437d79ea0487cc991c7f9c19d67adcceac3d47f53"}, + {file = "tree_sitter_languages-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:4b01d3bdf7ce2aeee4d0df62071a0ca91e618a29845686a5bd714d93c5ef3b36"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b603f1ad01bfb9d178f965125e2528cb7da9666d180f4a9a1acfaedbf5862ea"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70610aa26dd985d2fb9eb07ea8eacc3ceb0cc9c2e91416f51305120cfd919e28"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0444ebc8bdb7dc0d66a816050cfd52376c4e62a94a9c54fde90b29acf3e4bab1"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7eeb5a3307ff1c0994ffff5ea37ec656a716a728b8c9359374104da521a76ded"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6c319cef16f2df667f1c165fe4eee160f2b51a0c4b61db1e70de2ab86420ca9a"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-win32.whl", hash = "sha256:b216650126d95d494f927393903e836a7ef5f0c4db0834f3a0b576f97c13abaf"}, + {file = "tree_sitter_languages-1.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6c96e5785d164a205962a10256808b3d12dccee9827ec88a46899063a2a2d28"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adafeabbd8d47b80122fad18bb61c25ed3da04f5347b7d774b53826accb27b7a"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50e2bc5d2da770ecd5af94f9d716faa4764f890fd61bc0a488e9269653d9fb71"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac773097cff7de6cf265c5be9990b4c6690161452da1d9fc41021d4bf7e8c73a"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b233bfc48cf0f16436200afc7d7643cd87101c321de25b919b61f21f1693aa52"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:eab3caedf50467045ed5cab776a57b494332616376d387c6600fd7ea4f5483cf"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:d533f743a22f5696494d3a5a60adb4cfbef63d58b8b5622993d93d6d0a602444"}, + {file = "tree_sitter_languages-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:aab96f64be30c9f73d6dc958ec22bb1a9fe70e90b2d2a3d233d537b347cea729"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1bf89d771621e28847036b377f865f947e555a6654356d21beab738bb2531a69"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b2f171089ec3c4f1de275edc8f0722e1e3dc7a54e83107098315ea2f0952cfcd"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a091577d3a8454c40f813ee2834314c73cc504522f70f9e33d7c2268d33973f9"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8287efa87d080b340b583a6e81266cc3d8266deb61b8f3312649a9d1562e665a"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5080c06a2df7a59c69d2422a6ae83a5e37e92d57c4bd5e572d0eb5226ab3b0"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ca8f629cfb406a2f9b9f8a3a5c804d4d1ba4cdca41cccba63f51fc1bab13e5de"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-win32.whl", hash = "sha256:fd3561b37a99c9d501719819a8736529ae3a6d597128c15be432d1855f3cb0d9"}, + {file = "tree_sitter_languages-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:377ad60f7a7bf27315676c4fa84cc766aa0019c1e556083763136ed951e934c0"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1dc71b68e48f58cd5b6a9ab7a541714201815629a6554a969cfc579a6ee6e53"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fb1521367b14c275bef70997ea90526e7049f840ba1bbd3ef56c72f5b15596e9"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f73651f7e78371dc3d455e8aba510cc6fb9e1ac1d648c3334157950781eb295"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:049b0dd63be721fe3f9642a2b5a044bea2852de2b35818467996242ae4b7f01f"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c428a8e1f5ecc4eb5c79abff3eb2881123446cde16fd1d8866d527470a6fdd2f"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:40fb3fc11ff90caf65b4713feeb6c4852e5d2a04ef8ae6a2ac734a702a6a6c7e"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-win32.whl", hash = "sha256:f28e9904833b7a909f8227c4560401049bd3310cebe3e0a884d9461f783b9af2"}, + {file = "tree_sitter_languages-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ea47ee390ec2e1c9bf96d7b418775263766021a834910c9f2d578f95a3e27d0f"}, +] +typed-ast = [ {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, @@ -2108,106 +2363,39 @@ files = [ {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, ] - -[[package]] -name = "types-setuptools" -version = "67.8.0.0" -description = "Typing stubs for setuptools" -category = "dev" -optional = false -python-versions = "*" -files = [ +types-setuptools = [ {file = "types-setuptools-67.8.0.0.tar.gz", hash = "sha256:95c9ed61871d6c0e258433373a4e1753c0a7c3627a46f4d4058c7b5a08ab844f"}, {file = "types_setuptools-67.8.0.0-py3-none-any.whl", hash = "sha256:6df73340d96b238a4188b7b7668814b37e8018168aef1eef94a3b1872e3f60ff"}, ] - -[[package]] -name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +types-tree-sitter = [ + {file = "types-tree-sitter-0.20.1.5.tar.gz", hash = "sha256:94f971599548b90b9bbb6af651d235ad795a094a07651bc565a4b8856caebab1"}, + {file = "types_tree_sitter-0.20.1.5-py3-none-any.whl", hash = "sha256:8d7f9961febbad29789ce5c65f79b95b0702f3d34a7c12fabcd69c36c2bbe184"}, +] +types-tree-sitter-languages = [ + {file = "types-tree-sitter-languages-1.7.0.1.tar.gz", hash = "sha256:eadbbfa13f3fcad0711ac8f866cf87692f3c0cfeee72e979a5202b797588d57d"}, + {file = "types_tree_sitter_languages-1.7.0.1-py3-none-any.whl", hash = "sha256:818ec7824ed1bb5bcdbe21022340e0df3930199eb969ea1e08eb03a92440bce2"}, +] +typing-extensions = [ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] - -[[package]] -name = "tzdata" -version = "2022.7" -description = "Provider of IANA time zone data" -category = "dev" -optional = false -python-versions = ">=2" -files = [ +tzdata = [ {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, {file = "tzdata-2022.7.tar.gz", hash = "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"}, ] - -[[package]] -name = "uc-micro-py" -version = "1.0.2" -description = "Micro subset of unicode data files for linkify-it-py projects." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +uc-micro-py = [ {file = "uc-micro-py-1.0.2.tar.gz", hash = "sha256:30ae2ac9c49f39ac6dce743bd187fcd2b574b16ca095fa74cd9396795c954c54"}, {file = "uc_micro_py-1.0.2-py3-none-any.whl", hash = "sha256:8c9110c309db9d9e87302e2f4ad2c3152770930d88ab385cd544e7a7e75f3de0"}, ] - -[package.extras] -test = ["coverage", "pytest", "pytest-cov"] - -[[package]] -name = "urllib3" -version = "2.0.4" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +urllib3 = [ + {file = "urllib3-2.0.5-py3-none-any.whl", hash = "sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e"}, + {file = "urllib3-2.0.5.tar.gz", hash = "sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594"}, ] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "virtualenv" -version = "20.24.5" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +virtualenv = [ {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} -platformdirs = ">=3.9.1,<4" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "watchdog" -version = "3.0.0" -description = "Filesystem events monitoring" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +watchdog = [ {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, @@ -2236,18 +2424,7 @@ files = [ {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, ] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "yarl" -version = "1.9.2" -description = "Yet another URL library" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +yarl = [ {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, @@ -2323,29 +2500,7 @@ files = [ {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, ] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +zipp = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.7" -content-hash = "3817b3d8b678845abb17cddd49d5a6ea5fb9d0083faa356ef232184a94312ba6" diff --git a/pyproject.toml b/pyproject.toml index f343aea290..a3914503ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,8 @@ markdown-it-py = { extras = ["plugins", "linkify"], version = ">=2.1.0" } #rich = {path="../rich", develop=true} importlib-metadata = ">=4.11.3" typing-extensions = "^4.4.0" +tree-sitter = "^0.20.1" +tree_sitter_languages = {version = ">=1.7.0", python = "^3.8"} [tool.poetry.group.dev.dependencies] pytest = "^7.1.3" @@ -65,7 +67,9 @@ httpx = "^0.23.1" types-setuptools = "^67.2.0.1" textual-dev = "^1.1.0" pytest-asyncio = "*" -pytest-textual-snapshot = "*" +pytest-textual-snapshot = ">=0.4.0" +types-tree-sitter = "^0.20.1.4" +types-tree-sitter-languages = "^1.7.0.1" [tool.black] includes = "src" diff --git a/src/textual/_ansi_sequences.py b/src/textual/_ansi_sequences.py index fc3b8de624..9d0c4a601b 100644 --- a/src/textual/_ansi_sequences.py +++ b/src/textual/_ansi_sequences.py @@ -221,6 +221,8 @@ "\x1b[1;5B": (Keys.ControlDown,), # Cursor Mode "\x1b[1;5C": (Keys.ControlRight,), # Cursor Mode "\x1b[1;5D": (Keys.ControlLeft,), # Cursor Mode + "\x1bf": (Keys.ControlRight,), # iTerm natural editing keys + "\x1bb": (Keys.ControlLeft,), # iTerm natural editing keys "\x1b[1;5F": (Keys.ControlEnd,), "\x1b[1;5H": (Keys.ControlHome,), # Tmux sends following keystrokes when control+arrow is pressed, but for diff --git a/src/textual/_text_area_theme.py b/src/textual/_text_area_theme.py new file mode 100644 index 0000000000..93bad81c85 --- /dev/null +++ b/src/textual/_text_area_theme.py @@ -0,0 +1,353 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from rich.style import Style + +from textual.app import DEFAULT_COLORS +from textual.color import Color +from textual.design import DEFAULT_DARK_SURFACE + + +@dataclass +class TextAreaTheme: + """A theme for the `TextArea` widget. + + Allows theming the general widget (gutter, selections, cursor, and so on) and + mapping of tree-sitter tokens to Rich styles. + + For example, consider the following snippet from the `markdown.scm` highlight + query file. We've assigned the `heading_content` token type to the name `heading`. + + ``` + (heading_content) @heading + ``` + + Now, we can map this `heading` name to a Rich style, and it will be styled as + such in the `TextArea`, assuming a parser which returns a `heading_content` + node is used (as will be the case when language="markdown"). + + ``` + TextAreaTheme('my_theme', syntax_styles={'heading': Style(color='cyan', bold=True)}) + ``` + + We can register this theme with our `TextArea` using the [`TextArea.register_theme`][textual.widgets._text_area.TextArea.register_theme] method, + and headings in our markdown files will be styled bold cyan. + """ + + name: str + """The name of the theme.""" + + base_style: Style | None = None + """The background style of the text area. If `None` the parent style will be used.""" + + gutter_style: Style | None = None + """The style of the gutter. If `None`, a legible Style will be generated.""" + + cursor_style: Style | None = None + """The style of the cursor. If `None`, a legible Style will be generated.""" + + cursor_line_style: Style | None = None + """The style to apply to the line the cursor is on.""" + + cursor_line_gutter_style: Style | None = None + """The style to apply to the gutter of the line the cursor is on. If `None`, a legible Style will be + generated.""" + + bracket_matching_style: Style | None = None + """The style to apply to matching brackets. If `None`, a legible Style will be generated.""" + + selection_style: Style | None = None + """The style of the selection. If `None` a default selection Style will be generated.""" + + syntax_styles: dict[str, Style] = field(default_factory=dict) + """The mapping of tree-sitter names from the `highlight_query` to Rich styles.""" + + def __post_init__(self) -> None: + """Generate some styles if they haven't been supplied.""" + if self.base_style is None: + self.base_style = Style() + + if self.base_style.color is None: + self.base_style = Style(color="#f3f3f3", bgcolor=self.base_style.bgcolor) + + if self.base_style.bgcolor is None: + self.base_style = Style( + color=self.base_style.color, bgcolor=DEFAULT_DARK_SURFACE + ) + + assert self.base_style is not None + assert self.base_style.color is not None + assert self.base_style.bgcolor is not None + + if self.gutter_style is None: + self.gutter_style = self.base_style.copy() + + background_color = Color.from_rich_color(self.base_style.bgcolor) + if self.cursor_style is None: + self.cursor_style = Style( + color=background_color.rich_color, + bgcolor=background_color.inverse.rich_color, + ) + + if self.cursor_line_gutter_style is None and self.cursor_line_style is not None: + self.cursor_line_gutter_style = self.cursor_line_style.copy() + + if self.bracket_matching_style is None: + bracket_matching_background = background_color.blend( + background_color.inverse, factor=0.05 + ) + self.bracket_matching_style = Style( + bgcolor=bracket_matching_background.rich_color + ) + + if self.selection_style is None: + selection_background_color = background_color.blend( + DEFAULT_COLORS["dark"].primary, factor=0.75 + ) + self.selection_style = Style.from_color( + bgcolor=selection_background_color.rich_color + ) + + @classmethod + def get_builtin_theme(cls, theme_name: str) -> "TextAreaTheme" | None: + """Get a `TextAreaTheme` by name. + + Given a `theme_name`, return the corresponding `TextAreaTheme` object. + + Args: + theme_name: The name of the theme. + + Returns: + The `TextAreaTheme` corresponding to the name or `None` if the theme isn't + found. + """ + return _BUILTIN_THEMES.get(theme_name) + + def get_highlight(self, name: str) -> Style | None: + """Return the Rich style corresponding to the name defined in the tree-sitter + highlight query for the current theme. + + Args: + name: The name of the highlight. + + Returns: + The `Style` to use for this highlight, or `None` if no style. + """ + return self.syntax_styles.get(name) + + @classmethod + def builtin_themes(cls) -> list[TextAreaTheme]: + """Get a list of all builtin TextAreaThemes. + + Returns: + A list of all builtin TextAreaThemes. + """ + return list(_BUILTIN_THEMES.values()) + + @classmethod + def default(cls) -> TextAreaTheme: + """Get the default syntax theme. + + Returns: + The default TextAreaTheme (probably "monokai"). + """ + return _MONOKAI + + +_MONOKAI = TextAreaTheme( + name="monokai", + base_style=Style(color="#f8f8f2", bgcolor="#272822"), + gutter_style=Style(color="#90908a", bgcolor="#272822"), + cursor_style=Style(color="#272822", bgcolor="#f8f8f0"), + cursor_line_style=Style(bgcolor="#3e3d32"), + cursor_line_gutter_style=Style(color="#c2c2bf", bgcolor="#3e3d32"), + bracket_matching_style=Style(bgcolor="#838889", bold=True), + selection_style=Style(bgcolor="#65686a"), + syntax_styles={ + "string": Style(color="#E6DB74"), + "string.documentation": Style(color="#E6DB74"), + "comment": Style(color="#75715E"), + "keyword": Style(color="#F92672"), + "operator": Style(color="#F92672"), + "repeat": Style(color="#F92672"), + "exception": Style(color="#F92672"), + "include": Style(color="#F92672"), + "keyword.function": Style(color="#F92672"), + "keyword.return": Style(color="#F92672"), + "keyword.operator": Style(color="#F92672"), + "conditional": Style(color="#F92672"), + "number": Style(color="#AE81FF"), + "float": Style(color="#AE81FF"), + "class": Style(color="#A6E22E"), + "type.class": Style(color="#A6E22E"), + "function": Style(color="#A6E22E"), + "function.call": Style(color="#A6E22E"), + "method": Style(color="#A6E22E"), + "method.call": Style(color="#A6E22E"), + "boolean": Style(color="#66D9EF", italic=True), + "json.null": Style(color="#66D9EF", italic=True), + "regex.punctuation.bracket": Style(color="#F92672"), + "regex.operator": Style(color="#F92672"), + "html.end_tag_error": Style(color="red", underline=True), + "tag": Style(color="#F92672"), + "yaml.field": Style(color="#F92672", bold=True), + "json.label": Style(color="#F92672", bold=True), + "toml.type": Style(color="#F92672"), + "toml.datetime": Style(color="#AE81FF"), + "heading": Style(color="#F92672", bold=True), + "bold": Style(bold=True), + "italic": Style(italic=True), + "strikethrough": Style(strike=True), + "link": Style(color="#66D9EF", underline=True), + "inline_code": Style(color="#E6DB74"), + }, +) + +_DRACULA = TextAreaTheme( + name="dracula", + base_style=Style(color="#f8f8f2", bgcolor="#1E1F35"), + gutter_style=Style(color="#6272a4"), + cursor_style=Style(color="#282a36", bgcolor="#f8f8f0"), + cursor_line_style=Style(bgcolor="#282b45"), + cursor_line_gutter_style=Style(color="#c2c2bf", bgcolor="#282b45", bold=True), + bracket_matching_style=Style(bgcolor="#99999d", bold=True, underline=True), + selection_style=Style(bgcolor="#44475A"), + syntax_styles={ + "string": Style(color="#f1fa8c"), + "string.documentation": Style(color="#f1fa8c"), + "comment": Style(color="#6272a4"), + "keyword": Style(color="#ff79c6"), + "operator": Style(color="#ff79c6"), + "repeat": Style(color="#ff79c6"), + "exception": Style(color="#ff79c6"), + "include": Style(color="#ff79c6"), + "keyword.function": Style(color="#ff79c6"), + "keyword.return": Style(color="#ff79c6"), + "keyword.operator": Style(color="#ff79c6"), + "conditional": Style(color="#ff79c6"), + "number": Style(color="#bd93f9"), + "float": Style(color="#bd93f9"), + "class": Style(color="#50fa7b"), + "type.class": Style(color="#50fa7b"), + "function": Style(color="#50fa7b"), + "function.call": Style(color="#50fa7b"), + "method": Style(color="#50fa7b"), + "method.call": Style(color="#50fa7b"), + "boolean": Style(color="#bd93f9"), + "json.null": Style(color="#bd93f9"), + "regex.punctuation.bracket": Style(color="#ff79c6"), + "regex.operator": Style(color="#ff79c6"), + "html.end_tag_error": Style(color="#F83333", underline=True), + "tag": Style(color="#ff79c6"), + "yaml.field": Style(color="#ff79c6", bold=True), + "json.label": Style(color="#ff79c6", bold=True), + "toml.type": Style(color="#ff79c6"), + "toml.datetime": Style(color="#bd93f9"), + "heading": Style(color="#ff79c6", bold=True), + "bold": Style(bold=True), + "italic": Style(italic=True), + "strikethrough": Style(strike=True), + "link": Style(color="#bd93f9", underline=True), + "inline_code": Style(color="#f1fa8c"), + }, +) + +_DARK_VS = TextAreaTheme( + name="vscode_dark", + base_style=Style(color="#CCCCCC", bgcolor="#1F1F1F"), + gutter_style=Style(color="#6E7681", bgcolor="#1F1F1F"), + cursor_style=Style(color="#1e1e1e", bgcolor="#f0f0f0"), + cursor_line_style=Style(bgcolor="#2b2b2b"), + bracket_matching_style=Style(bgcolor="#3a3a3a", bold=True), + cursor_line_gutter_style=Style(color="#CCCCCC", bgcolor="#2b2b2b"), + selection_style=Style(bgcolor="#264F78"), + syntax_styles={ + "string": Style(color="#ce9178"), + "string.documentation": Style(color="#ce9178"), + "comment": Style(color="#6A9955"), + "keyword": Style(color="#569cd6"), + "operator": Style(color="#569cd6"), + "conditional": Style(color="#569cd6"), + "keyword.function": Style(color="#569cd6"), + "keyword.return": Style(color="#569cd6"), + "keyword.operator": Style(color="#569cd6"), + "repeat": Style(color="#569cd6"), + "exception": Style(color="#569cd6"), + "include": Style(color="#569cd6"), + "number": Style(color="#b5cea8"), + "float": Style(color="#b5cea8"), + "class": Style(color="#4EC9B0"), + "type.class": Style(color="#4EC9B0"), + "function": Style(color="#4EC9B0"), + "function.call": Style(color="#4EC9B0"), + "method": Style(color="#4EC9B0"), + "method.call": Style(color="#4EC9B0"), + "boolean": Style(color="#7DAF9C"), + "json.null": Style(color="#7DAF9C"), + "tag": Style(color="#EFCB43"), + "yaml.field": Style(color="#569cd6", bold=True), + "json.label": Style(color="#569cd6", bold=True), + "toml.type": Style(color="#569cd6"), + "heading": Style(color="#569cd6", bold=True), + "bold": Style(bold=True), + "italic": Style(italic=True), + "strikethrough": Style(strike=True), + "link": Style(color="#40A6FF", underline=True), + "inline_code": Style(color="#ce9178"), + "info_string": Style(color="#ce9178", bold=True, italic=True), + }, +) + +_GITHUB_LIGHT = TextAreaTheme( + name="github_light", + base_style=Style(color="#24292e", bgcolor="#f0f0f0"), + gutter_style=Style(color="#BBBBBB", bgcolor="#f0f0f0"), + cursor_style=Style(color="#fafbfc", bgcolor="#24292e"), + cursor_line_style=Style(bgcolor="#ebebeb"), + bracket_matching_style=Style(color="#24292e", underline=True), + cursor_line_gutter_style=Style(color="#A4A4A4", bgcolor="#ebebeb"), + selection_style=Style(bgcolor="#c8c8fa"), + syntax_styles={ + "string": Style(color="#093069"), + "string.documentation": Style(color="#093069"), + "comment": Style(color="#6a737d"), + "keyword": Style(color="#d73a49"), + "operator": Style(color="#0450AE"), + "conditional": Style(color="#CF222E"), + "keyword.function": Style(color="#CF222E"), + "keyword.return": Style(color="#CF222E"), + "keyword.operator": Style(color="#CF222E"), + "repeat": Style(color="#CF222E"), + "exception": Style(color="#CF222E"), + "include": Style(color="#CF222E"), + "number": Style(color="#d73a49"), + "float": Style(color="#d73a49"), + "parameter": Style(color="#24292e"), + "class": Style(color="#963800"), + "variable": Style(color="#e36209"), + "function": Style(color="#6639BB"), + "method": Style(color="#6639BB"), + "boolean": Style(color="#7DAF9C"), + "tag": Style(color="#6639BB"), + "yaml.field": Style(color="#6639BB"), + "json.label": Style(color="#6639BB"), + "toml.type": Style(color="#6639BB"), + "heading": Style(color="#24292e", bold=True), + "bold": Style(bold=True), + "italic": Style(italic=True), + "strikethrough": Style(strike=True), + "link": Style(color="#40A6FF", underline=True), + "inline_code": Style(color="#093069"), + }, +) + +_BUILTIN_THEMES = { + "monokai": _MONOKAI, + "dracula": _DRACULA, + "vscode_dark": _DARK_VS, + "github_light": _GITHUB_LIGHT, +} + +DEFAULT_THEME = TextAreaTheme.get_builtin_theme("monokai") +"""The default TextAreaTheme used by Textual.""" diff --git a/src/textual/_tree_sitter.py b/src/textual/_tree_sitter.py new file mode 100644 index 0000000000..01e300115c --- /dev/null +++ b/src/textual/_tree_sitter.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +try: + from tree_sitter import Language, Parser, Tree + from tree_sitter.binding import Query + from tree_sitter_languages import get_language, get_parser + + TREE_SITTER = True +except ImportError: + TREE_SITTER = False diff --git a/src/textual/_types.py b/src/textual/_types.py index b1ad7972f3..669950c5a2 100644 --- a/src/textual/_types.py +++ b/src/textual/_types.py @@ -1,6 +1,12 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Union -from typing_extensions import Protocol +from typing_extensions import ( + Literal, + Protocol, + SupportsIndex, + get_args, + runtime_checkable, +) if TYPE_CHECKING: from rich.segment import Segment diff --git a/src/textual/document/__init__.py b/src/textual/document/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/textual/document/_document.py b/src/textual/document/_document.py new file mode 100644 index 0000000000..5e8e37d8d0 --- /dev/null +++ b/src/textual/document/_document.py @@ -0,0 +1,389 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from functools import lru_cache +from typing import TYPE_CHECKING, NamedTuple, Tuple, overload + +if TYPE_CHECKING: + from tree_sitter import Node + from tree_sitter.binding import Query + +from textual._cells import cell_len +from textual._types import Literal, get_args +from textual.geometry import Size + +Newline = Literal["\r\n", "\n", "\r"] +"""The type representing valid line separators.""" +VALID_NEWLINES = set(get_args(Newline)) +"""The set of valid line separator strings.""" + + +@dataclass +class EditResult: + """Contains information about an edit that has occurred.""" + + end_location: Location + """The new end Location after the edit is complete.""" + replaced_text: str + """The text that was replaced.""" + + +@lru_cache(maxsize=1024) +def _utf8_encode(text: str) -> bytes: + """Encode the input text as utf-8 bytes. + + The returned encoded bytes may be retrieved from a cache. + + Args: + text: The text to encode. + + Returns: + The utf-8 bytes representing the input string. + """ + return text.encode("utf-8") + + +def _detect_newline_style(text: str) -> Newline: + """Return the newline type used in this document. + + Args: + text: The text to inspect. + + Returns: + The NewlineStyle used in the file. + """ + if "\r\n" in text: # Windows newline + return "\r\n" + elif "\n" in text: # Unix/Linux/MacOS newline + return "\n" + elif "\r" in text: # Old MacOS newline + return "\r" + else: + return "\n" # Default to Unix style newline + + +class DocumentBase(ABC): + """Describes the minimum functionality a Document implementation must + provide in order to be used by the TextArea widget.""" + + @abstractmethod + def replace_range(self, start: Location, end: Location, text: str) -> EditResult: + """Replace the text at the given range. + + Args: + start: A tuple (row, column) where the edit starts. + end: A tuple (row, column) where the edit ends. + text: The text to insert between start and end. + + Returns: + The new end location after the edit is complete. + """ + + @property + @abstractmethod + def text(self) -> str: + """The text from the document as a string.""" + + @property + @abstractmethod + def newline(self) -> Newline: + """Return the line separator used in the document.""" + + @abstractmethod + def get_line(self, index: int) -> str: + """Returns the line with the given index from the document. + + This is used in rendering lines, and will be called by the + TextArea for each line that is rendered. + + Args: + index: The index of the line in the document. + + Returns: + The str instance representing the line. + """ + + @abstractmethod + def get_text_range(self, start: Location, end: Location) -> str: + """Get the text that falls between the start and end locations. + + Args: + start: The start location of the selection. + end: The end location of the selection. + + Returns: + The text between start (inclusive) and end (exclusive). + """ + + @abstractmethod + def get_size(self, indent_width: int) -> Size: + """Get the size of the document. + + The height is generally the number of lines, and the width + is generally the maximum cell length of all the lines. + + Args: + indent_width: The width to use for tab characters. + + Returns: + The Size of the document bounding box. + """ + + def query_syntax_tree( + self, + query: "Query", + start_point: tuple[int, int] | None = None, + end_point: tuple[int, int] | None = None, + ) -> list[tuple["Node", str]]: + """Query the tree-sitter syntax tree. + + The default implementation always returns an empty list. + + To support querying in a subclass, this must be implemented. + + Args: + query: The tree-sitter Query to perform. + start_point: The (row, column byte) to start the query at. + end_point: The (row, column byte) to end the query at. + + Returns: + A tuple containing the nodes and text captured by the query. + """ + return [] + + def prepare_query(self, query: str) -> "Query" | None: + return None + + @property + @abstractmethod + def line_count(self) -> int: + """Returns the number of lines in the document.""" + + @overload + def __getitem__(self, line_index: int) -> str: + ... + + @overload + def __getitem__(self, line_index: slice) -> list[str]: + ... + + @abstractmethod + def __getitem__(self, line_index: int | slice) -> str | list[str]: + """Return the content of a line as a string, excluding newline characters. + + Args: + line_index: The index or slice of the line(s) to retrieve. + + Returns: + The line or list of lines requested. + """ + + +class Document(DocumentBase): + """A document which can be opened in a TextArea.""" + + def __init__(self, text: str) -> None: + self._newline = _detect_newline_style(text) + """The type of newline used in the text.""" + self._lines: list[str] = text.splitlines(keepends=False) + """The lines of the document, excluding newline characters. + + If there's a newline at the end of the file, the final line is an empty string. + """ + if text.endswith(tuple(VALID_NEWLINES)) or not text: + self._lines.append("") + + @property + def lines(self) -> list[str]: + """Get the document as a list of strings, where each string represents a line. + + Newline characters are not included in at the end of the strings. + + The newline character used in this document can be found via the `Document.newline` property. + """ + return self._lines + + @property + def text(self) -> str: + """Get the text from the document.""" + return self._newline.join(self._lines) + + @property + def newline(self) -> Newline: + """Get the Newline used in this document (e.g. '\r\n', '\n'. etc.)""" + return self._newline + + def get_size(self, tab_width: int) -> Size: + """The Size of the document, taking into account the tab rendering width. + + Args: + tab_width: The width to use for tab indents. + + Returns: + The size (width, height) of the document. + """ + lines = self._lines + cell_lengths = [cell_len(line.expandtabs(tab_width)) for line in lines] + max_cell_length = max(cell_lengths, default=0) + height = len(lines) + return Size(max_cell_length, height) + + def replace_range(self, start: Location, end: Location, text: str) -> EditResult: + """Replace text at the given range. + + Args: + start: A tuple (row, column) where the edit starts. + end: A tuple (row, column) where the edit ends. + text: The text to insert between start and end. + + Returns: + The EditResult containing information about the completed + replace operation. + """ + top, bottom = sorted((start, end)) + top_row, top_column = top + bottom_row, bottom_column = bottom + + insert_lines = text.splitlines() + if text.endswith(tuple(VALID_NEWLINES)): + # Special case where a single newline character is inserted. + insert_lines.append("") + + lines = self._lines + + replaced_text = self.get_text_range(top, bottom) + if bottom_row >= len(lines): + after_selection = "" + else: + after_selection = lines[bottom_row][bottom_column:] + + if top_row >= len(lines): + before_selection = "" + else: + before_selection = lines[top_row][:top_column] + + if insert_lines: + insert_lines[0] = before_selection + insert_lines[0] + destination_column = len(insert_lines[-1]) + insert_lines[-1] = insert_lines[-1] + after_selection + else: + destination_column = len(before_selection) + insert_lines = [before_selection + after_selection] + + lines[top_row : bottom_row + 1] = insert_lines + destination_row = top_row + len(insert_lines) - 1 + + end_location = (destination_row, destination_column) + return EditResult(end_location, replaced_text) + + def get_text_range(self, start: Location, end: Location) -> str: + """Get the text that falls between the start and end locations. + + Returns the text between `start` and `end`, including the appropriate + line separator character as specified by `Document._newline`. Note that + `_newline` is set automatically to the first line separator character + found in the document. + + Args: + start: The start location of the selection. + end: The end location of the selection. + + Returns: + The text between start (inclusive) and end (exclusive). + """ + if start == end: + return "" + + top, bottom = sorted((start, end)) + top_row, top_column = top + bottom_row, bottom_column = bottom + lines = self._lines + if top_row == bottom_row: + line = lines[top_row] + selected_text = line[top_column:bottom_column] + else: + start_line = lines[top_row] + end_line = lines[bottom_row] if bottom_row <= self.line_count - 1 else "" + selected_text = start_line[top_column:] + for row in range(top_row + 1, bottom_row): + selected_text += self._newline + lines[row] + + if bottom_row < self.line_count: + selected_text += self._newline + selected_text += end_line[:bottom_column] + + return selected_text + + @property + def line_count(self) -> int: + """Returns the number of lines in the document.""" + return len(self._lines) + + def get_line(self, index: int) -> str: + """Returns the line with the given index from the document. + + Args: + index: The index of the line in the document. + + Returns: + The string representing the line. + """ + line_string = self[index] + return line_string + + @overload + def __getitem__(self, line_index: int) -> str: + ... + + @overload + def __getitem__(self, line_index: slice) -> list[str]: + ... + + def __getitem__(self, line_index: int | slice) -> str | list[str]: + """Return the content of a line as a string, excluding newline characters. + + Args: + line_index: The index or slice of the line(s) to retrieve. + + Returns: + The line or list of lines requested. + """ + return self._lines[line_index] + + +Location = Tuple[int, int] +"""A location (row, column) within the document. Indexing starts at 0.""" + + +class Selection(NamedTuple): + """A range of characters within a document from a start point to the end point. + The location of the cursor is always considered to be the `end` point of the selection. + The selection is inclusive of the minimum point and exclusive of the maximum point. + """ + + start: Location = (0, 0) + """The start location of the selection. + + If you were to click and drag a selection inside a text-editor, this is where you *started* dragging. + """ + end: Location = (0, 0) + """The end location of the selection. + + If you were to click and drag a selection inside a text-editor, this is where you *finished* dragging. + """ + + @classmethod + def cursor(cls, location: Location) -> "Selection": + """Create a Selection with the same start and end point - a "cursor". + + Args: + location: The location to create the zero-width Selection. + """ + return cls(location, location) + + @property + def is_empty(self) -> bool: + """Return True if the selection has 0 width, i.e. it's just a cursor.""" + start, end = self + return start == end diff --git a/src/textual/document/_languages.py b/src/textual/document/_languages.py new file mode 100644 index 0000000000..a33f7544e8 --- /dev/null +++ b/src/textual/document/_languages.py @@ -0,0 +1,13 @@ +BUILTIN_LANGUAGES = sorted( + [ + "markdown", + "yaml", + "sql", + "css", + "html", + "json", + "python", + "regex", + "toml", + ] +) diff --git a/src/textual/document/_syntax_aware_document.py b/src/textual/document/_syntax_aware_document.py new file mode 100644 index 0000000000..3fd828ae48 --- /dev/null +++ b/src/textual/document/_syntax_aware_document.py @@ -0,0 +1,268 @@ +from __future__ import annotations + +try: + from tree_sitter import Language, Node, Parser, Tree + from tree_sitter.binding import Query + from tree_sitter_languages import get_language, get_parser + + TREE_SITTER = True +except ImportError: + TREE_SITTER = False + +from textual.document._document import Document, EditResult, Location, _utf8_encode +from textual.document._languages import BUILTIN_LANGUAGES + + +class SyntaxAwareDocumentError(Exception): + """General error raised when SyntaxAwareDocument is used incorrectly.""" + + +class SyntaxAwareDocument(Document): + """A wrapper around a Document which also maintains a tree-sitter syntax + tree when the document is edited. + + The primary reason for this split is actually to keep tree-sitter stuff separate, + since it isn't supported in Python 3.7. By having the tree-sitter code + isolated in this subclass, it makes it easier to conditionally import. However, + it does come with other design flaws (e.g. Document is required to have methods + which only really make sense on SyntaxAwareDocument). + + If you're reading this and Python 3.7 is no longer supported by Textual, + consider merging this subclass into the `Document` superclass. + """ + + def __init__( + self, + text: str, + language: str | Language, + ): + """Construct a SyntaxAwareDocument. + + Args: + text: The initial text contained in the document. + language: The language to use. You can pass a string to use a supported + language, or pass in your own tree-sitter `Language` object. + """ + + if not TREE_SITTER: + raise RuntimeError("SyntaxAwareDocument unavailable.") + + super().__init__(text) + self.language: Language | None = None + """The tree-sitter Language or None if tree-sitter is unavailable.""" + + self._parser: Parser | None = None + """The tree-sitter Parser or None if tree-sitter is unavailable.""" + + # If the language is `None`, then avoid doing any parsing related stuff. + if isinstance(language, str): + if language not in BUILTIN_LANGUAGES: + raise SyntaxAwareDocumentError(f"Invalid language {language!r}") + self.language = get_language(language) + self._parser = get_parser(language) + else: + self.language = language + self._parser = Parser() + self._parser.set_language(language) + + self._syntax_tree: Tree = self._parser.parse(self._read_callable) # type: ignore + """The tree-sitter Tree (syntax tree) built from the document.""" + + @property + def language_name(self) -> str | None: + return self.language.name if self.language else None + + def prepare_query(self, query: str) -> Query | None: + """Prepare a tree-sitter tree query. + + Queries should be prepared once, then reused. + + To execute a query, call `query_syntax_tree`. + + Args: + The string query to prepare. + + Returns: + The prepared query. + """ + if not TREE_SITTER: + raise SyntaxAwareDocumentError( + "Couldn't prepare query - tree-sitter is not available on this architecture." + ) + + if self.language is None: + raise SyntaxAwareDocumentError( + "Couldn't prepare query - no language assigned." + ) + + return self.language.query(query) + + def query_syntax_tree( + self, + query: Query, + start_point: tuple[int, int] | None = None, + end_point: tuple[int, int] | None = None, + ) -> list[tuple["Node", str]]: + """Query the tree-sitter syntax tree. + + The default implementation always returns an empty list. + + To support querying in a subclass, this must be implemented. + + Args: + query: The tree-sitter Query to perform. + start_point: The (row, column byte) to start the query at. + end_point: The (row, column byte) to end the query at. + + Returns: + A tuple containing the nodes and text captured by the query. + """ + + if not TREE_SITTER: + raise SyntaxAwareDocumentError( + "tree-sitter is not available on this architecture." + ) + + captures_kwargs = {} + if start_point is not None: + captures_kwargs["start_point"] = start_point + if end_point is not None: + captures_kwargs["end_point"] = end_point + + captures = query.captures(self._syntax_tree.root_node, **captures_kwargs) + return captures + + def replace_range(self, start: Location, end: Location, text: str) -> EditResult: + """Replace text at the given range. + + Args: + start: A tuple (row, column) where the edit starts. + end: A tuple (row, column) where the edit ends. + text: The text to insert between start and end. + + Returns: + The new end location after the edit is complete. + """ + top, bottom = sorted((start, end)) + + # An optimisation would be finding the byte offsets as a single operation rather + # than doing two passes over the document content. + start_byte = self._location_to_byte_offset(top) + start_point = self._location_to_point(top) + old_end_byte = self._location_to_byte_offset(bottom) + old_end_point = self._location_to_point(bottom) + + replace_result = super().replace_range(start, end, text) + + text_byte_length = len(_utf8_encode(text)) + end_location = replace_result.end_location + assert self._syntax_tree is not None + assert self._parser is not None + self._syntax_tree.edit( + start_byte=start_byte, + old_end_byte=old_end_byte, + new_end_byte=start_byte + text_byte_length, + start_point=start_point, + old_end_point=old_end_point, + new_end_point=self._location_to_point(end_location), + ) + # Incrementally parse the document. + self._syntax_tree = self._parser.parse( + self._read_callable, self._syntax_tree # type: ignore[arg-type] + ) + + return replace_result + + def get_line(self, line_index: int) -> str: + """Return the string representing the line, not including new line characters. + + Args: + line_index: The index of the line. + + Returns: + The string representing the line. + """ + line_string = self[line_index] + return line_string + + def _location_to_byte_offset(self, location: Location) -> int: + """Given a document coordinate, return the byte offset of that coordinate. + This method only does work if tree-sitter was imported, otherwise it returns 0. + + Args: + location: The location to convert. + + Returns: + An integer byte offset for the given location. + """ + lines = self._lines + row, column = location + lines_above = lines[:row] + end_of_line_width = len(self.newline) + bytes_lines_above = sum( + len(_utf8_encode(line)) + end_of_line_width for line in lines_above + ) + if row < len(lines): + bytes_on_left = len(_utf8_encode(lines[row][:column])) + else: + bytes_on_left = 0 + byte_offset = bytes_lines_above + bytes_on_left + return byte_offset + + def _location_to_point(self, location: Location) -> tuple[int, int]: + """Convert a document location (row_index, column_index) to a tree-sitter + point (row_index, byte_offset_from_start_of_row). If tree-sitter isn't available + returns (0, 0). + + Args: + location: A location (row index, column codepoint offset) + + Returns: + The point corresponding to that location (row index, column byte offset). + """ + lines = self._lines + row, column = location + if row < len(lines): + bytes_on_left = len(_utf8_encode(lines[row][:column])) + else: + bytes_on_left = 0 + return row, bytes_on_left + + def _read_callable(self, byte_offset: int, point: tuple[int, int]) -> bytes: + """A callable which informs tree-sitter about the document content. + + This is passed to tree-sitter which will call it frequently to retrieve + the bytes from the document. + + Args: + byte_offset: The number of (utf-8) bytes from the start of the document. + point: A tuple (row index, column *byte* offset). Note that this differs + from our Location tuple which is (row_index, column codepoint offset). + + Returns: + All the utf-8 bytes between the byte_offset/point and the end of the current + line _including_ the line separator character(s). Returns None if the + offset/point requested by tree-sitter doesn't correspond to a byte. + """ + row, column = point + lines = self._lines + newline = self.newline + + row_out_of_bounds = row >= len(lines) + if row_out_of_bounds: + return b"" + else: + row_text = lines[row] + + encoded_row = _utf8_encode(row_text) + encoded_row_length = len(encoded_row) + + if column < encoded_row_length: + return encoded_row[column:] + _utf8_encode(newline) + elif column == encoded_row_length: + return _utf8_encode(newline[0]) + elif column == encoded_row_length + 1: + if newline == "\r\n": + return b"\n" + + return b"" diff --git a/src/textual/events.py b/src/textual/events.py index af8aaf0533..7cff7d01d0 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -417,6 +417,19 @@ def get_content_offset(self, widget: Widget) -> Offset | None: """ if self.screen_offset not in widget.content_region: return None + return self.get_content_offset_capture(widget) + + def get_content_offset_capture(self, widget: Widget) -> Offset: + """Get offset from a widget's content area. + + This method works even if the offset is outside the widget content region. + + Args: + widget: Widget receiving the event. + + Returns: + An offset where the origin is at the top left of the content area. + """ return self.offset - widget.gutter.top_left def _apply_offset(self, x: int, y: int) -> MouseEvent: diff --git a/src/textual/expand_tabs.py b/src/textual/expand_tabs.py new file mode 100644 index 0000000000..9227f796c2 --- /dev/null +++ b/src/textual/expand_tabs.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import re + +from rich.cells import cell_len + +_TABS_SPLITTER_RE = re.compile(r"(.*?\t|.+?$)") + + +def expand_tabs_inline(line: str, tab_size: int = 4) -> str: + """Expands tabs, taking into account double cell characters. + + Args: + line: The text to expand tabs in. + tab_size: Number of cells in a tab. + Returns: + New string with tabs replaced with spaces. + """ + if "\t" not in line: + return line + new_line_parts: list[str] = [] + add_part = new_line_parts.append + cell_position = 0 + parts = _TABS_SPLITTER_RE.findall(line) + + for part in parts: + if part.endswith("\t"): + part = f"{part[:-1]} " + cell_position += cell_len(part) + tab_remainder = cell_position % tab_size + if tab_remainder: + spaces = tab_size - tab_remainder + part += spaces * " " + add_part(part) + + return "".join(new_line_parts) + + +if __name__ == "__main__": + print(expand_tabs_inline("\tbar")) + print(expand_tabs_inline("1\tbar")) + print(expand_tabs_inline("12\tbar")) + print(expand_tabs_inline("123\tbar")) + print(expand_tabs_inline("1234\tbar")) + print(expand_tabs_inline("💩\tbar")) + print(expand_tabs_inline("💩💩\tbar")) + print(expand_tabs_inline("💩💩💩\tbar")) + print(expand_tabs_inline("F💩\tbar")) + print(expand_tabs_inline("F💩O\tbar")) diff --git a/src/textual/screen.py b/src/textual/screen.py index b93e9dc46e..631dadb4ba 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -953,7 +953,9 @@ def _forward_event(self, event: events.Event) -> None: except errors.NoWidget: self.set_focus(None) else: - if isinstance(event, events.MouseUp) and widget.focusable: + if isinstance(event, events.MouseDown) and widget.focusable: + self.set_focus(widget) + elif isinstance(event, events.MouseUp) and widget.focusable: if self.focused is not widget: self.set_focus(widget) event.stop() diff --git a/src/textual/widget.py b/src/textual/widget.py index b6afa893b1..b229c70735 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -3191,7 +3191,7 @@ def release_mouse(self) -> None: def begin_capture_print(self, stdout: bool = True, stderr: bool = True) -> None: """Capture text from print statements (or writes to stdout / stderr). - If printing is captured, the widget will be send an [events.Print][textual.events.Print] message. + If printing is captured, the widget will be sent an [events.Print][textual.events.Print] message. Call [end_capture_print][textual.widget.Widget.end_capture_print] to disable print capture. diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index af7bd8968d..cd6e21f13b 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -41,6 +41,7 @@ from ._switch import Switch from ._tabbed_content import TabbedContent, TabPane from ._tabs import Tab, Tabs + from ._text_area import TextArea from ._tooltip import Tooltip from ._tree import Tree from ._welcome import Welcome @@ -79,6 +80,7 @@ "TabbedContent", "TabPane", "Tabs", + "TextArea", "RichLog", "Tooltip", "Tree", diff --git a/src/textual/widgets/__init__.pyi b/src/textual/widgets/__init__.pyi index a6f22febc0..d4db2f8f52 100644 --- a/src/textual/widgets/__init__.pyi +++ b/src/textual/widgets/__init__.pyi @@ -33,6 +33,7 @@ from ._tabbed_content import TabbedContent as TabbedContent from ._tabbed_content import TabPane as TabPane from ._tabs import Tab as Tab from ._tabs import Tabs as Tabs +from ._text_area import TextArea as TextArea from ._tooltip import Tooltip as Tooltip from ._tree import Tree as Tree from ._welcome import Welcome as Welcome diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py new file mode 100644 index 0000000000..f40478f088 --- /dev/null +++ b/src/textual/widgets/_text_area.py @@ -0,0 +1,1865 @@ +from __future__ import annotations + +import re +from collections import defaultdict +from dataclasses import dataclass, field +from functools import lru_cache +from pathlib import Path +from typing import TYPE_CHECKING, Any, Iterable, Optional, Tuple + +from rich.style import Style +from rich.text import Text + +from textual._text_area_theme import TextAreaTheme +from textual._tree_sitter import TREE_SITTER +from textual.color import Color +from textual.document._document import ( + Document, + DocumentBase, + EditResult, + Location, + Selection, + _utf8_encode, +) +from textual.document._languages import BUILTIN_LANGUAGES +from textual.document._syntax_aware_document import ( + SyntaxAwareDocument, + SyntaxAwareDocumentError, +) +from textual.expand_tabs import expand_tabs_inline + +if TYPE_CHECKING: + from tree_sitter import Language + from tree_sitter.binding import Query + +from textual import events, log +from textual._cells import cell_len +from textual._types import Literal, Protocol, runtime_checkable +from textual.binding import Binding +from textual.events import MouseEvent +from textual.geometry import Offset, Region, Size, Spacing, clamp +from textual.reactive import Reactive, reactive +from textual.scroll_view import ScrollView +from textual.strip import Strip + +_OPENING_BRACKETS = {"{": "}", "[": "]", "(": ")"} +_CLOSING_BRACKETS = {v: k for k, v in _OPENING_BRACKETS.items()} +_TREE_SITTER_PATH = Path(__file__) / "../../../../tree-sitter/" +_HIGHLIGHTS_PATH = _TREE_SITTER_PATH / "highlights/" + +StartColumn = int +EndColumn = Optional[int] +HighlightName = str +Highlight = Tuple[StartColumn, EndColumn, HighlightName] +"""A tuple representing a syntax highlight within one line.""" + + +class ThemeDoesNotExist(Exception): + """Raised when the user tries to use a theme which does not exist. + This means a theme which is not builtin, or has not been registered. + """ + + pass + + +class LanguageDoesNotExist(Exception): + """Raised when the user tries to use a language which does not exist. + This means a language which is not builtin, or has not been registered. + """ + + pass + + +@dataclass +class TextAreaLanguage: + """A container for a language which has been registered with the TextArea. + + Attributes: + name: The name of the language. + language: The tree-sitter Language. + highlight_query: The tree-sitter highlight query corresponding to the language, as a string. + """ + + name: str + language: "Language" + highlight_query: str + + +class TextArea(ScrollView, can_focus=True): + DEFAULT_CSS = """\ +TextArea { + width: 1fr; + height: 1fr; +} +""" + + BINDINGS = [ + Binding("escape", "screen.focus_next", "Shift Focus", show=False), + # Cursor movement + Binding("up", "cursor_up", "cursor up", show=False), + Binding("down", "cursor_down", "cursor down", show=False), + Binding("left", "cursor_left", "cursor left", show=False), + Binding("right", "cursor_right", "cursor right", show=False), + Binding("ctrl+left", "cursor_word_left", "cursor word left", show=False), + Binding("ctrl+right", "cursor_word_right", "cursor word right", show=False), + Binding("home,ctrl+a", "cursor_line_start", "cursor line start", show=False), + Binding("end,ctrl+e", "cursor_line_end", "cursor line end", show=False), + Binding("pageup", "cursor_page_up", "cursor page up", show=False), + Binding("pagedown", "cursor_page_down", "cursor page down", show=False), + # Making selections (generally holding the shift key and moving cursor) + Binding( + "ctrl+shift+left", + "cursor_word_left(True)", + "cursor left word select", + show=False, + ), + Binding( + "ctrl+shift+right", + "cursor_word_right(True)", + "cursor right word select", + show=False, + ), + Binding( + "shift+home", + "cursor_line_start(True)", + "cursor line start select", + show=False, + ), + Binding( + "shift+end", "cursor_line_end(True)", "cursor line end select", show=False + ), + Binding("shift+up", "cursor_up(True)", "cursor up select", show=False), + Binding("shift+down", "cursor_down(True)", "cursor down select", show=False), + Binding("shift+left", "cursor_left(True)", "cursor left select", show=False), + Binding("shift+right", "cursor_right(True)", "cursor right select", show=False), + # Shortcut ways of making selections + # Binding("f5", "select_word", "select word", show=False), + Binding("f6", "select_line", "select line", show=False), + Binding("f7", "select_all", "select all", show=False), + # Deletion + Binding("backspace", "delete_left", "delete left", show=False), + Binding( + "ctrl+w", "delete_word_left", "delete left to start of word", show=False + ), + Binding("delete,ctrl+d", "delete_right", "delete right", show=False), + Binding( + "ctrl+f", "delete_word_right", "delete right to start of word", show=False + ), + Binding("ctrl+x", "delete_line", "delete line", show=False), + Binding( + "ctrl+u", "delete_to_start_of_line", "delete to line start", show=False + ), + Binding("ctrl+k", "delete_to_end_of_line", "delete to line end", show=False), + ] + """ + | Key(s) | Description | + | :- | :- | + | escape | Focus on the next item. | + | up | Move the cursor up. | + | down | Move the cursor down. | + | left | Move the cursor left. | + | ctrl+left | Move the cursor to the start of the word. | + | ctrl+shift+left | Move the cursor to the start of the word and select. | + | right | Move the cursor right. | + | ctrl+right | Move the cursor to the end of the word. | + | ctrl+shift+right | Move the cursor to the end of the word and select. | + | home,ctrl+a | Move the cursor to the start of the line. | + | end,ctrl+e | Move the cursor to the end of the line. | + | shift+home | Move the cursor to the start of the line and select. | + | shift+end | Move the cursor to the end of the line and select. | + | pageup | Move the cursor one page up. | + | pagedown | Move the cursor one page down. | + | shift+up | Select while moving the cursor up. | + | shift+down | Select while moving the cursor down. | + | shift+left | Select while moving the cursor left. | + | shift+right | Select while moving the cursor right. | + | backspace | Delete character to the left of cursor. | + | ctrl+w | Delete from cursor to start of the word. | + | delete,ctrl+d | Delete character to the right of cursor. | + | ctrl+f | Delete from cursor to end of the word. | + | ctrl+x | Delete the current line. | + | ctrl+u | Delete from cursor to the start of the line. | + | ctrl+k | Delete from cursor to the end of the line. | + | f6 | Select the current line. | + | f7 | Select all text in the document. | + """ + + language: Reactive[str | None] = reactive(None, always_update=True, init=False) + """The language to use. + + This must be set to a valid, non-None value for syntax highlighting to work. + + If the value is a string, a built-in language parser will be used if available. + + If you wish to use an unsupported language, you'll have to register + it first using [`TextArea.register_language`][textual.widgets._text_area.TextArea.register_language]. + """ + + theme: Reactive[str | None] = reactive(None, always_update=True, init=False) + """The name of the theme to use. + + Themes must be registered using [`TextArea.register_theme`][textual.widgets._text_area.TextArea.register_theme] before they can be used. + + Syntax highlighting is only possible when the `language` attribute is set. + """ + + selection: Reactive[Selection] = reactive(Selection(), always_update=True) + """The selection start and end locations (zero-based line_index, offset). + + This represents the cursor location and the current selection. + + The `Selection.end` always refers to the cursor location. + + If no text is selected, then `Selection.end == Selection.start` is True. + + The text selected in the document is available via the `TextArea.selected_text` property. + """ + + show_line_numbers: Reactive[bool] = reactive(True) + """True to show the line number column on the left edge, otherwise False. + + Changing this value will immediately re-render the `TextArea`.""" + + indent_width: Reactive[int] = reactive(4) + """The width of tabs or the multiple of spaces to align to on pressing the `tab` key. + + If the document currently open contains tabs that are currently visible on screen, + altering this value will immediately change the display width of the visible tabs. + """ + + match_cursor_bracket: Reactive[bool] = reactive(True) + """If the cursor is at a bracket, highlight the matching bracket (if found).""" + + cursor_blink: Reactive[bool] = reactive(True) + """True if the cursor should blink.""" + + _cursor_blink_visible: Reactive[bool] = reactive(True, repaint=False) + """Indicates where the cursor is in the blink cycle. If it's currently + not visible due to blinking, this is False.""" + + def __init__( + self, + text: str = "", + *, + language: str | None = None, + theme: str | None = None, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False, + ) -> None: + """Construct a new `TextArea`. + + Args: + text: The initial text to load into the TextArea. + language: The language to use. + theme: The theme to use. + name: The name of the `TextArea` widget. + id: The ID of the widget, used to refer to it from Textual CSS. + classes: One or more Textual CSS compatible class names separated by spaces. + disabled: True if the widget is disabled. + """ + super().__init__(name=name, id=id, classes=classes, disabled=disabled) + self._initial_text = text + + self._languages: dict[str, TextAreaLanguage] = {} + """Maps language names to TextAreaLanguage.""" + + self._themes: dict[str, TextAreaTheme] = {} + """Maps theme names to TextAreaTheme.""" + + self.indent_type: Literal["tabs", "spaces"] = "spaces" + """Whether to indent using tabs or spaces.""" + + self._word_pattern = re.compile(r"(?<=\W)(?=\w)|(?<=\w)(?=\W)") + """Compiled regular expression for what we consider to be a 'word'.""" + + self._last_intentional_cell_width: int = 0 + """Tracks the last column (measured in terms of cell length, since we care here about where the cursor + visually moves rather than logical characters) the user explicitly navigated to so that we can reset to it + whenever possible.""" + + self._undo_stack: list[Undoable] = [] + """A stack (the end of the list is the top of the stack) for tracking edits.""" + + self._selecting = False + """True if we're currently selecting text using the mouse, otherwise False.""" + + self._matching_bracket_location: Location | None = None + """The location (row, column) of the bracket which matches the bracket the + cursor is currently at. If the cursor is at a bracket, or there's no matching + bracket, this will be `None`.""" + + self._highlights: dict[int, list[Highlight]] = defaultdict(list) + """Mapping line numbers to the set of highlights for that line.""" + + self._highlight_query: "Query" | None = None + """The query that's currently being used for highlighting.""" + + self.document: DocumentBase = Document(text) + """The document this widget is currently editing.""" + + self._theme: TextAreaTheme | None = None + """The `TextAreaTheme` corresponding to the set theme name. When the `theme` + reactive is set as a string, the watcher will update this attribute to the + corresponding `TextAreaTheme` object.""" + + self.language = language + """The language of the `TextArea`.""" + + self.theme: str | None = theme + """The name of the theme of the `TextArea` as set by the user.""" + + @staticmethod + def _get_builtin_highlight_query(language_name: str) -> str: + """Get the highlight query for a builtin language. + + Args: + language_name: The name of the builtin language. + + Returns: + The highlight query. + """ + try: + highlight_query_path = ( + Path(_HIGHLIGHTS_PATH.resolve()) / f"{language_name}.scm" + ) + highlight_query = highlight_query_path.read_text() + except OSError: + highlight_query = "" + + return highlight_query + + def _build_highlight_map(self) -> None: + """Query the tree for ranges to highlights, and update the internal highlights mapping.""" + highlights = self._highlights + highlights.clear() + if not self._highlight_query: + return + + captures = self.document.query_syntax_tree(self._highlight_query) + for capture in captures: + node, highlight_name = capture + node_start_row, node_start_column = node.start_point + node_end_row, node_end_column = node.end_point + + if node_start_row == node_end_row: + highlight = (node_start_column, node_end_column, highlight_name) + highlights[node_start_row].append(highlight) + else: + # Add the first line of the node range + highlights[node_start_row].append( + (node_start_column, None, highlight_name) + ) + + # Add the middle lines - entire row of this node is highlighted + for node_row in range(node_start_row + 1, node_end_row): + highlights[node_row].append((0, None, highlight_name)) + + # Add the last line of the node range + highlights[node_end_row].append((0, node_end_column, highlight_name)) + + def _watch_selection(self, selection: Selection) -> None: + """When the cursor moves, scroll it into view.""" + self.scroll_cursor_visible() + cursor_location = selection.end + cursor_row, cursor_column = cursor_location + + try: + character = self.document[cursor_row][cursor_column] + except IndexError: + character = "" + + # Record the location of a matching closing/opening bracket. + match_location = self.find_matching_bracket(character, cursor_location) + self._matching_bracket_location = match_location + if match_location is not None: + match_row, match_column = match_location + if match_row in range(*self._visible_line_indices): + self.refresh_lines(match_row) + + def find_matching_bracket( + self, bracket: str, search_from: Location + ) -> Location | None: + """If the character is a bracket, find the matching bracket. + + Args: + bracket: The character we're searching for the matching bracket of. + search_from: The location to start the search. + + Returns: + The `Location` of the matching bracket, or `None` if it's not found. + If the character is not available for bracket matching, `None` is returned. + """ + match_location = None + bracket_stack = [] + if bracket in _OPENING_BRACKETS: + for candidate, candidate_location in self._yield_character_locations( + search_from + ): + if candidate in _OPENING_BRACKETS: + bracket_stack.append(candidate) + elif candidate in _CLOSING_BRACKETS: + if ( + bracket_stack + and bracket_stack[-1] == _CLOSING_BRACKETS[candidate] + ): + bracket_stack.pop() + if not bracket_stack: + match_location = candidate_location + break + elif bracket in _CLOSING_BRACKETS: + for ( + candidate, + candidate_location, + ) in self._yield_character_locations_reverse(search_from): + if candidate in _CLOSING_BRACKETS: + bracket_stack.append(candidate) + elif candidate in _OPENING_BRACKETS: + if ( + bracket_stack + and bracket_stack[-1] == _OPENING_BRACKETS[candidate] + ): + bracket_stack.pop() + if not bracket_stack: + match_location = candidate_location + break + + return match_location + + def _validate_selection(self, selection: Selection) -> Selection: + """Clamp the selection to valid locations.""" + start, end = selection + clamp_visitable = self.clamp_visitable + return Selection(clamp_visitable(start), clamp_visitable(end)) + + def _watch_language(self, language: str | None) -> None: + """When the language is updated, update the type of document.""" + if language is not None and language not in self.available_languages: + raise LanguageDoesNotExist( + f"{language!r} is not a builtin language, or it has not been registered. " + f"To use a custom language, register it first using `register_language`, " + f"then switch to it by setting the `TextArea.language` attribute." + ) + + self._set_document( + self.document.text if self.document is not None else self._initial_text, + language, + ) + self._initial_text = "" + + def _watch_show_line_numbers(self) -> None: + """The line number gutter contributes to virtual size, so recalculate.""" + self._refresh_size() + + def _watch_indent_width(self) -> None: + """Changing width of tabs will change document display width.""" + self._refresh_size() + + def _watch_theme(self, theme: str | None) -> None: + """We set the styles on this widget when the theme changes, to ensure that + if padding is applied, the colours match.""" + + if theme is None: + # If the theme is None, use the default. + theme_object = TextAreaTheme.default() + else: + # If the user supplied a string theme name, find it and apply it. + try: + theme_object = self._themes[theme] + except KeyError: + theme_object = TextAreaTheme.get_builtin_theme(theme) + + if theme_object is None: + raise ThemeDoesNotExist( + f"{theme!r} is not a builtin theme, or it has not been registered. " + f"To use a custom theme, register it first using `register_theme`, " + f"then switch to that theme by setting the `TextArea.theme` attribute." + ) + + self._theme = theme_object + if theme_object: + base_style = theme_object.base_style + if base_style: + color = base_style.color + background = base_style.bgcolor + if color: + self.styles.color = Color.from_rich_color(color) + if background: + self.styles.background = Color.from_rich_color(background) + + @property + def available_themes(self) -> set[str]: + """A list of the names of the themes available to the `TextArea`. + + The values in this list can be assigned `theme` reactive attribute of + `TextArea`. + + You can retrieve the full specification for a theme by passing one of + the strings from this list into `TextAreaTheme.get_by_name(theme_name: str)`. + + Alternatively, you can directly retrieve a list of `TextAreaTheme` objects + (which contain the full theme specification) by calling + `TextAreaTheme.builtin_themes()`. + """ + return { + theme.name for theme in TextAreaTheme.builtin_themes() + } | self._themes.keys() + + def register_theme(self, theme: TextAreaTheme) -> None: + """Register a theme for use by the `TextArea`. + + After registering a theme, you can set themes by assigning the theme + name to the `TextArea.theme` reactive attribute. For example + `text_area.theme = "my_custom_theme"` where `"my_custom_theme"` is the + name of the theme you registered. + + If you supply a theme with a name that already exists that theme + will be overwritten. + """ + self._themes[theme.name] = theme + + @property + def available_languages(self) -> set[str]: + """A list of the names of languages available to the `TextArea`. + + The values in this list can be assigned to the `language` reactive attribute + of `TextArea`. + + The returned list contains the builtin languages plus those registered via the + `register_language` method. Builtin languages will be listed before + user-registered languages, but there are no other ordering guarantees. + """ + return set(BUILTIN_LANGUAGES) | self._languages.keys() + + def register_language( + self, + language: str | "Language", + highlight_query: str, + ) -> None: + """Register a language and corresponding highlight query. + + Calling this method does not change the language of the `TextArea`. + On switching to this language (via the `language` reactive attribute), + syntax highlighting will be performed using the given highlight query. + + If a string `name` is supplied for a builtin supported language, then + this method will update the default highlight query for that language. + + Registering a language only registers it to this instance of `TextArea`. + + Args: + language: A string referring to a builtin language or a tree-sitter `Language` object. + highlight_query: The highlight query to use for syntax highlighting this language. + """ + + # If tree-sitter is unavailable, do nothing. + if not TREE_SITTER: + return + + from tree_sitter_languages import get_language + + if isinstance(language, str): + language_name = language + language = get_language(language_name) + else: + language_name = language.name + + # Update the custom languages. When changing the document, + # we should first look in here for a language specification. + # If nothing is found, then we can go to the builtin languages. + self._languages[language_name] = TextAreaLanguage( + name=language_name, + language=language, + highlight_query=highlight_query, + ) + # If we updated the currently set language, rebuild the highlights + # using the newly updated highlights query. + if language_name == self.language: + self._set_document(self.text, language_name) + + def _set_document(self, text: str, language: str | None) -> None: + """Construct and return an appropriate document. + + Args: + text: The text of the document. + language: The name of the language to use. This must either be a + built-in supported language, or a language previously registered + via the `register_language` method. + """ + self._highlight_query = None + if TREE_SITTER and language: + # Attempt to get the override language. + text_area_language = self._languages.get(language, None) + document_language: str | "Language" + if text_area_language: + document_language = text_area_language.language + highlight_query = text_area_language.highlight_query + else: + document_language = language + highlight_query = self._get_builtin_highlight_query(language) + document: DocumentBase + try: + document = SyntaxAwareDocument(text, document_language) + except SyntaxAwareDocumentError: + document = Document(text) + log.warning( + f"Parser not found for language {document_language!r}. Parsing disabled." + ) + else: + self._highlight_query = document.prepare_query(highlight_query) + elif language and not TREE_SITTER: + log.warning( + "tree-sitter not available in this environment. Parsing disabled." + ) + document = Document(text) + else: + document = Document(text) + + self.document = document + self._build_highlight_map() + + @property + def _visible_line_indices(self) -> tuple[int, int]: + """Return the visible line indices as a tuple (top, bottom). + + Returns: + A tuple (top, bottom) indicating the top and bottom visible line indices. + """ + return self.scroll_offset.y, self.scroll_offset.y + self.size.height + + def load_text(self, text: str) -> None: + """Load text into the TextArea. + + This will replace the text currently in the TextArea. + + Args: + text: The text to load into the TextArea. + """ + self._set_document(text, self.language) + self.move_cursor((0, 0)) + self._refresh_size() + + def load_document(self, document: DocumentBase) -> None: + """Load a document into the TextArea. + + Args: + document: The document to load into the TextArea. + """ + self.document = document + self.move_cursor((0, 0)) + self._refresh_size() + + @property + def is_syntax_aware(self) -> bool: + """True if the TextArea is currently syntax aware - i.e. it's parsing document content.""" + return isinstance(self.document, SyntaxAwareDocument) + + def _yield_character_locations( + self, start: Location + ) -> Iterable[tuple[str, Location]]: + """Yields character locations starting from the given location. + + Does not yield location of line separator characters like `\\n`. + + Args: + start: The location to start yielding from. + + Returns: + Yields tuples of (character, (row, column)). + """ + row, column = start + document = self.document + line_count = document.line_count + + while 0 <= row < line_count: + line = document[row] + while column < len(line): + yield line[column], (row, column) + column += 1 + column = 0 + row += 1 + + def _yield_character_locations_reverse( + self, start: Location + ) -> Iterable[tuple[str, Location]]: + row, column = start + document = self.document + line_count = document.line_count + + while line_count > row >= 0: + line = document[row] + if column == -1: + column = len(line) - 1 + while column >= 0: + yield line[column], (row, column) + column -= 1 + row -= 1 + + def _refresh_size(self) -> None: + """Update the virtual size of the TextArea.""" + width, height = self.document.get_size(self.indent_width) + # +1 width to make space for the cursor resting at the end of the line + self.virtual_size = Size(width + self.gutter_width + 1, height) + + def render_line(self, widget_y: int) -> Strip: + """Render a single line of the TextArea. Called by Textual. + + Args: + widget_y: Y Coordinate of line relative to the widget region. + + Returns: + A rendered line. + """ + document = self.document + scroll_x, scroll_y = self.scroll_offset + + # Account for how much the TextArea is scrolled. + line_index = widget_y + scroll_y + + # Render the lines beyond the valid line numbers + out_of_bounds = line_index >= document.line_count + if out_of_bounds: + return Strip.blank(self.size.width) + + theme = self._theme + + # Get the line from the Document. + line_string = document.get_line(line_index) + line = Text(line_string, end="") + + line_character_count = len(line) + line.tab_size = self.indent_width + virtual_width, virtual_height = self.virtual_size + expanded_length = max(virtual_width, self.size.width) + line.set_length(expanded_length) + + selection = self.selection + start, end = selection + selection_top, selection_bottom = sorted(selection) + selection_top_row, selection_top_column = selection_top + selection_bottom_row, selection_bottom_column = selection_bottom + + highlights = self._highlights + if highlights and theme: + line_bytes = _utf8_encode(line_string) + byte_to_codepoint = build_byte_to_codepoint_dict(line_bytes) + get_highlight_from_theme = theme.syntax_styles.get + line_highlights = highlights[line_index] + for highlight_start, highlight_end, highlight_name in line_highlights: + node_style = get_highlight_from_theme(highlight_name) + if node_style is not None: + line.stylize( + node_style, + byte_to_codepoint.get(highlight_start, 0), + byte_to_codepoint.get(highlight_end) if highlight_end else None, + ) + + cursor_row, cursor_column = end + cursor_line_style = theme.cursor_line_style if theme else None + if cursor_line_style and cursor_row == line_index: + line.stylize(cursor_line_style) + + # Selection styling + if start != end and selection_top_row <= line_index <= selection_bottom_row: + # If this row intersects with the selection range + selection_style = theme.selection_style if theme else None + cursor_row, _ = end + if selection_style: + if line_character_count == 0 and line_index != cursor_row: + # A simple highlight to show empty lines are included in the selection + line = Text("▌", end="", style=Style(color=selection_style.bgcolor)) + line.set_length(self.virtual_size.width) + else: + if line_index == selection_top_row == selection_bottom_row: + # Selection within a single line + line.stylize( + selection_style, + start=selection_top_column, + end=selection_bottom_column, + ) + else: + # Selection spanning multiple lines + if line_index == selection_top_row: + line.stylize( + selection_style, + start=selection_top_column, + end=line_character_count, + ) + elif line_index == selection_bottom_row: + line.stylize(selection_style, end=selection_bottom_column) + else: + line.stylize(selection_style, end=line_character_count) + + # Highlight the cursor + matching_bracket = self._matching_bracket_location + match_cursor_bracket = self.match_cursor_bracket + draw_matched_brackets = ( + match_cursor_bracket and matching_bracket is not None and start == end + ) + + if cursor_row == line_index: + draw_cursor = not self.cursor_blink or ( + self.cursor_blink and self._cursor_blink_visible + ) + if draw_matched_brackets: + matching_bracket_style = theme.bracket_matching_style if theme else None + if matching_bracket_style: + line.stylize( + matching_bracket_style, + cursor_column, + cursor_column + 1, + ) + + if draw_cursor: + cursor_style = theme.cursor_style if theme else None + if cursor_style: + line.stylize(cursor_style, cursor_column, cursor_column + 1) + + # Highlight the partner opening/closing bracket. + if draw_matched_brackets: + # mypy doesn't know matching bracket is guaranteed to be non-None + assert matching_bracket is not None + bracket_match_row, bracket_match_column = matching_bracket + if theme and bracket_match_row == line_index: + matching_bracket_style = theme.bracket_matching_style + if matching_bracket_style: + line.stylize( + matching_bracket_style, + bracket_match_column, + bracket_match_column + 1, + ) + + # Build the gutter text for this line + gutter_width = self.gutter_width + if self.show_line_numbers: + if cursor_row == line_index: + gutter_style = theme.cursor_line_gutter_style if theme else None + else: + gutter_style = theme.gutter_style if theme else None + + gutter_width_no_margin = gutter_width - 2 + gutter = Text( + f"{line_index + 1:>{gutter_width_no_margin}} ", + style=gutter_style or "", + end="", + ) + else: + gutter = Text("", end="") + + # Render the gutter and the text of this line + console = self.app.console + gutter_segments = console.render(gutter) + text_segments = console.render( + line, + console.options.update_width(expanded_length), + ) + + # Crop the line to show only the visible part (some may be scrolled out of view) + gutter_strip = Strip(gutter_segments, cell_length=gutter_width) + text_strip = Strip(text_segments).crop( + scroll_x, scroll_x + virtual_width - gutter_width + ) + + # Stylize the line the cursor is currently on. + if cursor_row == line_index: + text_strip = text_strip.extend_cell_length( + expanded_length, cursor_line_style + ) + else: + text_strip = text_strip.extend_cell_length( + expanded_length, theme.base_style if theme else None + ) + + # Join and return the gutter and the visible portion of this line + strip = Strip.join([gutter_strip, text_strip]).simplify() + + return strip.apply_style( + theme.base_style + if theme and theme.base_style is not None + else self.rich_style + ) + + @property + def text(self) -> str: + """The entire text content of the document.""" + return self.document.text + + @property + def selected_text(self) -> str: + """The text between the start and end points of the current selection.""" + start, end = self.selection + return self.get_text_range(start, end) + + def get_text_range(self, start: Location, end: Location) -> str: + """Get the text between a start and end location. + + Args: + start: The start location. + end: The end location. + + Returns: + The text between start and end. + """ + start, end = sorted((start, end)) + return self.document.get_text_range(start, end) + + def edit(self, edit: Edit) -> Any: + """Perform an Edit. + + Args: + edit: The Edit to perform. + + Returns: + Data relating to the edit that may be useful. The data returned + may be different depending on the edit performed. + """ + result = edit.do(self) + self._refresh_size() + edit.after(self) + self._build_highlight_map() + return result + + async def _on_key(self, event: events.Key) -> None: + """Handle key presses which correspond to document inserts.""" + key = event.key + insert_values = { + "tab": " " * self._find_columns_to_next_tab_stop(), + "enter": "\n", + } + self._restart_blink() + if event.is_printable or key in insert_values: + event.stop() + event.prevent_default() + insert = insert_values.get(key, event.character) + # `insert` is not None because event.character cannot be + # None because we've checked that it's printable. + assert insert is not None + start, end = self.selection + self.replace(insert, start, end, maintain_selection_offset=False) + + def _find_columns_to_next_tab_stop(self) -> int: + """Get the location of the next tab stop after the cursors position on the current line. + + If the cursor is already at a tab stop, this returns the *next* tab stop location. + + Returns: + The number of cells to the next tab stop from the current cursor column. + """ + cursor_row, cursor_column = self.cursor_location + line_text = self.document[cursor_row] + indent_width = self.indent_width + if not line_text: + return indent_width + + width_before_cursor = self.get_column_width(cursor_row, cursor_column) + spaces_to_insert = indent_width - ( + (indent_width + width_before_cursor) % indent_width + ) + + return spaces_to_insert + + def get_target_document_location(self, event: MouseEvent) -> Location: + """Given a MouseEvent, return the row and column offset of the event in document-space. + + Args: + event: The MouseEvent. + + Returns: + The location of the mouse event within the document. + """ + scroll_x, scroll_y = self.scroll_offset + target_x = event.x - self.gutter_width + scroll_x - self.gutter.left + target_x = max(target_x, 0) + target_row = clamp( + event.y + scroll_y - self.gutter.top, + 0, + self.document.line_count - 1, + ) + target_column = self.cell_width_to_column_index(target_x, target_row) + return target_row, target_column + + # --- Lower level event/key handling + @property + def gutter_width(self) -> int: + """The width of the gutter (the left column containing line numbers). + + Returns: + The cell-width of the line number column. If `show_line_numbers` is `False` returns 0. + """ + # The longest number in the gutter plus two extra characters: `│ `. + gutter_margin = 2 + gutter_width = ( + len(str(self.document.line_count + 1)) + gutter_margin + if self.show_line_numbers + else 0 + ) + return gutter_width + + def _on_mount(self, _: events.Mount) -> None: + self.blink_timer = self.set_interval( + 0.5, + self._toggle_cursor_blink_visible, + pause=not (self.cursor_blink and self.has_focus), + ) + + def _on_blur(self, _: events.Blur) -> None: + self._pause_blink(visible=True) + + def _on_focus(self, _: events.Focus) -> None: + self._restart_blink() + + def _toggle_cursor_blink_visible(self) -> None: + """Toggle visibility of the cursor for the purposes of 'cursor blink'.""" + self._cursor_blink_visible = not self._cursor_blink_visible + cursor_row, _ = self.cursor_location + self.refresh_lines(cursor_row) + + def _restart_blink(self) -> None: + """Reset the cursor blink timer.""" + if self.cursor_blink: + self._cursor_blink_visible = True + self.blink_timer.reset() + + def _pause_blink(self, visible: bool = True) -> None: + """Pause the cursor blinking but ensure it stays visible.""" + self._cursor_blink_visible = visible + self.blink_timer.pause() + + async def _on_mouse_down(self, event: events.MouseDown) -> None: + """Update the cursor position, and begin a selection using the mouse.""" + target = self.get_target_document_location(event) + self.selection = Selection.cursor(target) + self._selecting = True + # Capture the mouse so that if the cursor moves outside the + # TextArea widget while selecting, the widget still scrolls. + self.capture_mouse() + self._pause_blink(visible=True) + + async def _on_mouse_move(self, event: events.MouseMove) -> None: + """Handles click and drag to expand and contract the selection.""" + if self._selecting: + target = self.get_target_document_location(event) + selection_start, _ = self.selection + self.selection = Selection(selection_start, target) + + async def _on_mouse_up(self, event: events.MouseUp) -> None: + """Finalise the selection that has been made using the mouse.""" + self._selecting = False + self.release_mouse() + self.record_cursor_width() + self._restart_blink() + + async def _on_paste(self, event: events.Paste) -> None: + """When a paste occurs, insert the text from the paste event into the document.""" + self.replace(event.text, *self.selection) + + def cell_width_to_column_index(self, cell_width: int, row_index: int) -> int: + """Return the column that the cell width corresponds to on the given row. + + Args: + cell_width: The cell width to convert. + row_index: The index of the row to examine. + + Returns: + The column corresponding to the cell width on that row. + """ + tab_width = self.indent_width + total_cell_offset = 0 + line = self.document[row_index] + for column_index, character in enumerate(line): + total_cell_offset += cell_len(expand_tabs_inline(character, tab_width)) + if total_cell_offset >= cell_width + 1: + return column_index + return len(line) + + def clamp_visitable(self, location: Location) -> Location: + """Clamp the given location to the nearest visitable location. + + Args: + location: The location to clamp. + + Returns: + The nearest location that we could conceivably navigate to using the cursor. + """ + document = self.document + + row, column = location + try: + line_text = document[row] + except IndexError: + line_text = "" + + row = clamp(row, 0, document.line_count - 1) + column = clamp(column, 0, len(line_text)) + + return row, column + + # --- Cursor/selection utilities + def scroll_cursor_visible( + self, center: bool = False, animate: bool = False + ) -> Offset: + """Scroll the `TextArea` such that the cursor is visible on screen. + + Args: + center: True if the cursor should be scrolled to the center. + animate: True if we should animate while scrolling. + + Returns: + The offset that was scrolled to bring the cursor into view. + """ + row, column = self.selection.end + text = self.document[row][:column] + column_offset = cell_len(expand_tabs_inline(text, self.indent_width)) + scroll_offset = self.scroll_to_region( + Region(x=column_offset, y=row, width=3, height=1), + spacing=Spacing(right=self.gutter_width), + animate=animate, + force=True, + center=center, + ) + return scroll_offset + + def move_cursor( + self, + location: Location, + select: bool = False, + center: bool = False, + record_width: bool = True, + ) -> None: + """Move the cursor to a location. + + Args: + location: The location to move the cursor to. + select: If True, select text between the old and new location. + center: If True, scroll such that the cursor is centered. + record_width: If True, record the cursor column cell width after navigating + so that we jump back to the same width the next time we move to a row + that is wide enough. + """ + if select: + start, end = self.selection + self.selection = Selection(start, location) + else: + self.selection = Selection.cursor(location) + + if record_width: + self.record_cursor_width() + + if center: + self.scroll_cursor_visible(center) + + def move_cursor_relative( + self, + rows: int = 0, + columns: int = 0, + select: bool = False, + center: bool = False, + record_width: bool = True, + ) -> None: + """Move the cursor relative to its current location. + + Args: + rows: The number of rows to move down by (negative to move up) + columns: The number of columns to move right by (negative to move left) + select: If True, select text between the old and new location. + center: If True, scroll such that the cursor is centered. + record_width: If True, record the cursor column cell width after navigating + so that we jump back to the same width the next time we move to a row + that is wide enough. + """ + clamp_visitable = self.clamp_visitable + start, end = self.selection + current_row, current_column = end + target = clamp_visitable((current_row + rows, current_column + columns)) + self.move_cursor(target, select, center, record_width) + + def select_line(self, index: int) -> None: + """Select all the text in the specified line. + + Args: + index: The index of the line to select (starting from 0). + """ + try: + line = self.document[index] + except IndexError: + return + else: + self.selection = Selection((index, 0), (index, len(line))) + self.record_cursor_width() + + def action_select_line(self) -> None: + """Select all the text on the current line.""" + cursor_row, _ = self.cursor_location + self.select_line(cursor_row) + + def select_all(self) -> None: + """Select all of the text in the `TextArea`.""" + last_line = self.document.line_count - 1 + length_of_last_line = len(self.document[last_line]) + selection_start = (0, 0) + selection_end = (last_line, length_of_last_line) + self.selection = Selection(selection_start, selection_end) + self.record_cursor_width() + + def action_select_all(self) -> None: + """Select all the text in the document.""" + self.select_all() + + @property + def cursor_location(self) -> Location: + """The current location of the cursor in the document. + + This is a utility for accessing the `end` of `TextArea.selection`. + """ + return self.selection.end + + @cursor_location.setter + def cursor_location(self, location: Location) -> None: + """Set the cursor_location to a new location. + + If a selection is in progress, the anchor point will remain. + """ + self.move_cursor(location, select=not self.selection.is_empty) + + @property + def cursor_at_first_line(self) -> bool: + """True if and only if the cursor is on the first line.""" + return self.selection.end[0] == 0 + + @property + def cursor_at_last_line(self) -> bool: + """True if and only if the cursor is on the last line.""" + return self.selection.end[0] == self.document.line_count - 1 + + @property + def cursor_at_start_of_line(self) -> bool: + """True if and only if the cursor is at column 0.""" + return self.selection.end[1] == 0 + + @property + def cursor_at_end_of_line(self) -> bool: + """True if and only if the cursor is at the end of a row.""" + cursor_row, cursor_column = self.selection.end + row_length = len(self.document[cursor_row]) + cursor_at_end = cursor_column == row_length + return cursor_at_end + + @property + def cursor_at_start_of_text(self) -> bool: + """True if and only if the cursor is at location (0, 0)""" + return self.selection.end == (0, 0) + + @property + def cursor_at_end_of_text(self) -> bool: + """True if and only if the cursor is at the very end of the document.""" + return self.cursor_at_last_line and self.cursor_at_end_of_line + + # ------ Cursor movement actions + def action_cursor_left(self, select: bool = False) -> None: + """Move the cursor one location to the left. + + If the cursor is at the left edge of the document, try to move it to + the end of the previous line. + + Args: + select: If True, select the text while moving. + """ + new_cursor_location = self.get_cursor_left_location() + self.move_cursor(new_cursor_location, select=select) + + def get_cursor_left_location(self) -> Location: + """Get the location the cursor will move to if it moves left. + + Returns: + The location of the cursor if it moves left. + """ + if self.cursor_at_start_of_text: + return 0, 0 + cursor_row, cursor_column = self.selection.end + length_of_row_above = len(self.document[cursor_row - 1]) + target_row = cursor_row if cursor_column != 0 else cursor_row - 1 + target_column = cursor_column - 1 if cursor_column != 0 else length_of_row_above + return target_row, target_column + + def action_cursor_right(self, select: bool = False) -> None: + """Move the cursor one location to the right. + + If the cursor is at the end of a line, attempt to go to the start of the next line. + + Args: + select: If True, select the text while moving. + """ + target = self.get_cursor_right_location() + self.move_cursor(target, select=select) + + def get_cursor_right_location(self) -> Location: + """Get the location the cursor will move to if it moves right. + + Returns: + the location the cursor will move to if it moves right. + """ + if self.cursor_at_end_of_text: + return self.selection.end + cursor_row, cursor_column = self.selection.end + target_row = cursor_row + 1 if self.cursor_at_end_of_line else cursor_row + target_column = 0 if self.cursor_at_end_of_line else cursor_column + 1 + return target_row, target_column + + def action_cursor_down(self, select: bool = False) -> None: + """Move the cursor down one cell. + + Args: + select: If True, select the text while moving. + """ + target = self.get_cursor_down_location() + self.move_cursor(target, record_width=False, select=select) + + def get_cursor_down_location(self) -> Location: + """Get the location the cursor will move to if it moves down. + + Returns: + The location the cursor will move to if it moves down. + """ + cursor_row, cursor_column = self.selection.end + if self.cursor_at_last_line: + return cursor_row, len(self.document[cursor_row]) + + target_row = min(self.document.line_count - 1, cursor_row + 1) + # Attempt to snap last intentional cell length + target_column = self.cell_width_to_column_index( + self._last_intentional_cell_width, target_row + ) + target_column = clamp(target_column, 0, len(self.document[target_row])) + return target_row, target_column + + def action_cursor_up(self, select: bool = False) -> None: + """Move the cursor up one cell. + + Args: + select: If True, select the text while moving. + """ + target = self.get_cursor_up_location() + self.move_cursor(target, record_width=False, select=select) + + def get_cursor_up_location(self) -> Location: + """Get the location the cursor will move to if it moves up. + + Returns: + The location the cursor will move to if it moves up. + """ + if self.cursor_at_first_line: + return 0, 0 + cursor_row, cursor_column = self.selection.end + target_row = max(0, cursor_row - 1) + # Attempt to snap last intentional cell length + target_column = self.cell_width_to_column_index( + self._last_intentional_cell_width, target_row + ) + target_column = clamp(target_column, 0, len(self.document[target_row])) + return target_row, target_column + + def action_cursor_line_end(self, select: bool = False) -> None: + """Move the cursor to the end of the line.""" + location = self.get_cursor_line_end_location() + self.move_cursor(location, select=select) + + def get_cursor_line_end_location(self) -> Location: + """Get the location of the end of the current line. + + Returns: + The (row, column) location of the end of the cursors current line. + """ + start, end = self.selection + cursor_row, cursor_column = end + target_column = len(self.document[cursor_row]) + return cursor_row, target_column + + def action_cursor_line_start(self, select: bool = False) -> None: + """Move the cursor to the start of the line.""" + + cursor_row, cursor_column = self.cursor_location + line = self.document[cursor_row] + + first_non_whitespace = 0 + for index, code_point in enumerate(line): + if not code_point.isspace(): + first_non_whitespace = index + break + + if cursor_column <= first_non_whitespace and cursor_column != 0: + target = self.get_cursor_line_start_location() + self.move_cursor(target, select=select) + else: + target = cursor_row, first_non_whitespace + self.move_cursor(target, select=select) + + def get_cursor_line_start_location(self) -> Location: + """Get the location of the start of the current line. + + Returns: + The (row, column) location of the start of the cursors current line. + """ + _start, end = self.selection + cursor_row, _cursor_column = end + return cursor_row, 0 + + def action_cursor_word_left(self, select: bool = False) -> None: + """Move the cursor left by a single word, skipping trailing whitespace. + + Args: + select: Whether to select while moving the cursor. + """ + if self.cursor_at_start_of_text: + return + target = self.get_cursor_word_left_location() + self.move_cursor(target, select=select) + + def get_cursor_word_left_location(self) -> Location: + """Get the location the cursor will jump to if it goes 1 word left. + + Returns: + The location the cursor will jump on "jump word left". + """ + cursor_row, cursor_column = self.cursor_location + if cursor_row > 0 and cursor_column == 0: + # Going to the previous row + return cursor_row - 1, len(self.document[cursor_row - 1]) + + # Staying on the same row + line = self.document[cursor_row][:cursor_column] + search_string = line.rstrip() + matches = list(re.finditer(self._word_pattern, search_string)) + cursor_column = matches[-1].start() if matches else 0 + return cursor_row, cursor_column + + def action_cursor_word_right(self, select: bool = False) -> None: + """Move the cursor right by a single word, skipping leading whitespace.""" + + if self.cursor_at_end_of_text: + return + + target = self.get_cursor_word_right_location() + self.move_cursor(target, select=select) + + def get_cursor_word_right_location(self) -> Location: + """Get the location the cursor will jump to if it goes 1 word right. + + Returns: + The location the cursor will jump on "jump word right". + """ + cursor_row, cursor_column = self.selection.end + line = self.document[cursor_row] + if cursor_row < self.document.line_count - 1 and cursor_column == len(line): + # Moving to the line below + return cursor_row + 1, 0 + + # Staying on the same line + search_string = line[cursor_column:] + pre_strip_length = len(search_string) + search_string = search_string.lstrip() + strip_offset = pre_strip_length - len(search_string) + + matches = list(re.finditer(self._word_pattern, search_string)) + if matches: + cursor_column += matches[0].start() + strip_offset + else: + cursor_column = len(line) + + return cursor_row, cursor_column + + def action_cursor_page_up(self) -> None: + """Move the cursor and scroll up one page.""" + height = self.content_size.height + _, cursor_location = self.selection + row, column = cursor_location + target = (row - height, column) + self.scroll_relative(y=-height, animate=False) + self.move_cursor(target) + + def action_cursor_page_down(self) -> None: + """Move the cursor and scroll down one page.""" + height = self.content_size.height + _, cursor_location = self.selection + row, column = cursor_location + target = (row + height, column) + self.scroll_relative(y=height, animate=False) + self.move_cursor(target) + + def get_column_width(self, row: int, column: int) -> int: + """Get the cell offset of the column from the start of the row. + + Args: + row: The row index. + column: The column index (codepoint offset from start of row). + + Returns: + The cell width of the column relative to the start of the row. + """ + line = self.document[row] + return cell_len(expand_tabs_inline(line[:column], self.indent_width)) + + def record_cursor_width(self) -> None: + """Record the current cell width of the cursor. + + This is used where we navigate up and down through rows. + If we're in the middle of a row, and go down to a row with no + content, then we go down to another row, we want our cursor to + jump back to the same offset that we were originally at. + """ + row, column = self.selection.end + column_cell_length = self.get_column_width(row, column) + self._last_intentional_cell_width = column_cell_length + + # --- Editor operations + def insert( + self, + text: str, + location: Location | None = None, + *, + maintain_selection_offset: bool = True, + ) -> EditResult: + """Insert text into the document. + + Args: + text: The text to insert. + location: The location to insert text, or None to use the cursor location. + maintain_selection_offset: If True, the active Selection will be updated + such that the same text is selected before and after the selection, + if possible. Otherwise, the cursor will jump to the end point of the + edit. + + Returns: + An `EditResult` containing information about the edit. + """ + if location is None: + location = self.cursor_location + return self.edit(Edit(text, location, location, maintain_selection_offset)) + + def delete( + self, + start: Location, + end: Location, + *, + maintain_selection_offset: bool = True, + ) -> EditResult: + """Delete the text between two locations in the document. + + Args: + start: The start location. + end: The end location. + maintain_selection_offset: If True, the active Selection will be updated + such that the same text is selected before and after the selection, + if possible. Otherwise, the cursor will jump to the end point of the + edit. + + Returns: + An `EditResult` containing information about the edit. + """ + top, bottom = sorted((start, end)) + return self.edit(Edit("", top, bottom, maintain_selection_offset)) + + def replace( + self, + insert: str, + start: Location, + end: Location, + *, + maintain_selection_offset: bool = True, + ) -> EditResult: + """Replace text in the document with new text. + + Args: + insert: The text to insert. + start: The start location + end: The end location. + maintain_selection_offset: If True, the active Selection will be updated + such that the same text is selected before and after the selection, + if possible. Otherwise, the cursor will jump to the end point of the + edit. + + Returns: + An `EditResult` containing information about the edit. + """ + return self.edit(Edit(insert, start, end, maintain_selection_offset)) + + def clear(self) -> None: + """Delete all text from the document.""" + document = self.document + last_line = document[-1] + document_end = (document.line_count, len(last_line)) + self.delete((0, 0), document_end, maintain_selection_offset=False) + + def action_delete_left(self) -> None: + """Deletes the character to the left of the cursor and updates the cursor location. + + If there's a selection, then the selected range is deleted.""" + + selection = self.selection + start, end = selection + + if selection.is_empty: + end = self.get_cursor_left_location() + + self.delete(start, end, maintain_selection_offset=False) + + def action_delete_right(self) -> None: + """Deletes the character to the right of the cursor and keeps the cursor at the same location. + + If there's a selection, then the selected range is deleted.""" + + selection = self.selection + start, end = selection + + if selection.is_empty: + end = self.get_cursor_right_location() + + self.delete(start, end, maintain_selection_offset=False) + + def action_delete_line(self) -> None: + """Deletes the lines which intersect with the selection.""" + start, end = self.selection + start, end = sorted((start, end)) + start_row, start_column = start + end_row, end_column = end + + # Generally editors will only delete line the end line of the + # selection if the cursor is not at column 0 of that line. + if start_row != end_row and end_column == 0 and end_row >= 0: + end_row -= 1 + + from_location = (start_row, 0) + to_location = (end_row + 1, 0) + + self.delete(from_location, to_location, maintain_selection_offset=False) + + def action_delete_to_start_of_line(self) -> None: + """Deletes from the cursor location to the start of the line.""" + from_location = self.selection.end + cursor_row, cursor_column = from_location + to_location = (cursor_row, 0) + self.delete(from_location, to_location, maintain_selection_offset=False) + + def action_delete_to_end_of_line(self) -> None: + """Deletes from the cursor location to the end of the line.""" + from_location = self.selection.end + cursor_row, cursor_column = from_location + to_location = (cursor_row, len(self.document[cursor_row])) + self.delete(from_location, to_location, maintain_selection_offset=False) + + def action_delete_word_left(self) -> None: + """Deletes the word to the left of the cursor and updates the cursor location.""" + if self.cursor_at_start_of_text: + return + + # If there's a non-zero selection, then "delete word left" typically only + # deletes the characters within the selection range, ignoring word boundaries. + start, end = self.selection + if start != end: + self.delete(start, end, maintain_selection_offset=False) + return + + to_location = self.get_cursor_word_left_location() + self.delete(self.selection.end, to_location, maintain_selection_offset=False) + + def action_delete_word_right(self) -> None: + """Deletes the word to the right of the cursor and keeps the cursor at the same location. + + Note that the location that we delete to using this action is not the same + as the location we move to when we move the cursor one word to the right. + This action does not skip leading whitespace, whereas cursor movement does. + """ + if self.cursor_at_end_of_text: + return + + start, end = self.selection + if start != end: + self.delete(start, end, maintain_selection_offset=False) + return + + cursor_row, cursor_column = end + + # Check the current line for a word boundary + line = self.document[cursor_row][cursor_column:] + matches = list(re.finditer(self._word_pattern, line)) + + current_row_length = len(self.document[cursor_row]) + if matches: + to_location = (cursor_row, cursor_column + matches[0].end()) + elif ( + cursor_row < self.document.line_count - 1 + and cursor_column == current_row_length + ): + to_location = (cursor_row + 1, 0) + else: + to_location = (cursor_row, current_row_length) + + self.delete(end, to_location, maintain_selection_offset=False) + + +@dataclass +class Edit: + """Implements the Undoable protocol to replace text at some range within a document.""" + + text: str + """The text to insert. An empty string is equivalent to deletion.""" + from_location: Location + """The start location of the insert.""" + to_location: Location + """The end location of the insert""" + maintain_selection_offset: bool + """If True, the selection will maintain its offset to the replacement range.""" + _updated_selection: Selection | None = field(init=False, default=None) + """Where the selection should move to after the replace happens.""" + + def do(self, text_area: TextArea) -> EditResult: + """Perform the edit operation. + + Args: + text_area: The `TextArea` to perform the edit on. + + Returns: + An `EditResult` containing information about the replace operation. + """ + text = self.text + + edit_from = self.from_location + edit_to = self.to_location + + # This code is mostly handling how we adjust TextArea.selection + # when an edit is made to the document programmatically. + # We want a user who is typing away to maintain their relative + # position in the document even if an insert happens before + # their cursor position. + + edit_top, edit_bottom = sorted((edit_from, edit_to)) + edit_bottom_row, edit_bottom_column = edit_bottom + + selection_start, selection_end = text_area.selection + selection_start_row, selection_start_column = selection_start + selection_end_row, selection_end_column = selection_end + + replace_result = text_area.document.replace_range(edit_from, edit_to, text) + + new_edit_to_row, new_edit_to_column = replace_result.end_location + + # TODO: We could maybe improve the situation where the selection + # and the edit range overlap with each other. + column_offset = new_edit_to_column - edit_bottom_column + target_selection_start_column = ( + selection_start_column + column_offset + if edit_bottom_row == selection_start_row + and edit_bottom_column <= selection_start_column + else selection_start_column + ) + target_selection_end_column = ( + selection_end_column + column_offset + if edit_bottom_row == selection_end_row + and edit_bottom_column <= selection_end_column + else selection_end_column + ) + + row_offset = new_edit_to_row - edit_bottom_row + target_selection_start_row = selection_start_row + row_offset + target_selection_end_row = selection_end_row + row_offset + + if self.maintain_selection_offset: + self._updated_selection = Selection( + start=(target_selection_start_row, target_selection_start_column), + end=(target_selection_end_row, target_selection_end_column), + ) + else: + self._updated_selection = Selection.cursor(replace_result.end_location) + + return replace_result + + def undo(self, text_area: TextArea) -> EditResult: + """Undo the edit operation. + + Args: + text_area: The `TextArea` to undo the insert operation on. + + Returns: + An `EditResult` containing information about the replace operation. + """ + raise NotImplementedError() + + def after(self, text_area: TextArea) -> None: + """Possibly update the cursor location after the widget has been refreshed. + + Args: + text_area: The `TextArea` this operation was performed on. + """ + if self._updated_selection is not None: + text_area.selection = self._updated_selection + text_area.record_cursor_width() + + +@runtime_checkable +class Undoable(Protocol): + """Protocol for actions performed in the text editor which can be done and undone. + + These are typically actions which affect the document (e.g. inserting and deleting + text), but they can really be anything. + + To perform an edit operation, pass the Edit to `TextArea.edit()`""" + + def do(self, text_area: TextArea) -> Any: + """Do the action. + + Args: + The `TextArea` to perform the action on. + + Returns: + Anything. This protocol doesn't prescribe what is returned. + """ + + def undo(self, text_area: TextArea) -> Any: + """Undo the action. + + Args: + The `TextArea` to perform the action on. + + Returns: + Anything. This protocol doesn't prescribe what is returned. + """ + + +@lru_cache(maxsize=128) +def build_byte_to_codepoint_dict(data: bytes) -> dict[int, int]: + """Build a mapping of utf-8 byte offsets to codepoint offsets for the given data. + + Args: + data: utf-8 bytes. + + Returns: + A `dict[int, int]` mapping byte indices to codepoint indices within `data`. + """ + byte_to_codepoint = {} + current_byte_offset = 0 + code_point_offset = 0 + + while current_byte_offset < len(data): + byte_to_codepoint[current_byte_offset] = code_point_offset + first_byte = data[current_byte_offset] + + # Single-byte character + if (first_byte & 0b10000000) == 0: + current_byte_offset += 1 + # 2-byte character + elif (first_byte & 0b11100000) == 0b11000000: + current_byte_offset += 2 + # 3-byte character + elif (first_byte & 0b11110000) == 0b11100000: + current_byte_offset += 3 + # 4-byte character + elif (first_byte & 0b11111000) == 0b11110000: + current_byte_offset += 4 + else: + raise ValueError(f"Invalid UTF-8 byte: {first_byte}") + + code_point_offset += 1 + + # Mapping for the end of the string + byte_to_codepoint[current_byte_offset] = code_point_offset + return byte_to_codepoint diff --git a/src/textual/widgets/rule.py b/src/textual/widgets/rule.py index ef4f57d56d..a9ab5d23e9 100644 --- a/src/textual/widgets/rule.py +++ b/src/textual/widgets/rule.py @@ -1,9 +1,4 @@ -from ._rule import ( - InvalidLineStyle, - InvalidRuleOrientation, - LineStyle, - RuleOrientation, -) +from ._rule import InvalidLineStyle, InvalidRuleOrientation, LineStyle, RuleOrientation __all__ = [ "InvalidLineStyle", diff --git a/src/textual/widgets/text_area.py b/src/textual/widgets/text_area.py new file mode 100644 index 0000000000..82a69e38b3 --- /dev/null +++ b/src/textual/widgets/text_area.py @@ -0,0 +1,37 @@ +from textual._text_area_theme import TextAreaTheme +from textual.document._document import ( + Document, + DocumentBase, + EditResult, + Location, + Selection, +) +from textual.document._languages import BUILTIN_LANGUAGES +from textual.document._syntax_aware_document import SyntaxAwareDocument +from textual.widgets._text_area import ( + Edit, + EndColumn, + Highlight, + HighlightName, + LanguageDoesNotExist, + StartColumn, + ThemeDoesNotExist, +) + +__all__ = [ + "BUILTIN_LANGUAGES", + "Document", + "DocumentBase", + "Edit", + "EditResult", + "EndColumn", + "Highlight", + "HighlightName", + "LanguageDoesNotExist", + "Location", + "Selection", + "StartColumn", + "SyntaxAwareDocument", + "TextAreaTheme", + "ThemeDoesNotExist", +] diff --git a/tests/document/test_document.py b/tests/document/test_document.py new file mode 100644 index 0000000000..b6e9952782 --- /dev/null +++ b/tests/document/test_document.py @@ -0,0 +1,100 @@ +import pytest + +from textual.widgets.text_area import Document + +TEXT = """I must not fear. +Fear is the mind-killer.""" + +TEXT_NEWLINE = TEXT + "\n" +TEXT_WINDOWS = TEXT.replace("\n", "\r\n") +TEXT_WINDOWS_NEWLINE = TEXT_NEWLINE.replace("\n", "\r\n") + + +@pytest.mark.parametrize( + "text", [TEXT, TEXT_NEWLINE, TEXT_WINDOWS, TEXT_WINDOWS_NEWLINE] +) +def test_text(text): + """The text we put in is the text we get out.""" + document = Document(text) + assert document.text == text + + +def test_lines_newline_eof(): + document = Document(TEXT_NEWLINE) + assert document.lines == ["I must not fear.", "Fear is the mind-killer.", ""] + + +def test_lines_no_newline_eof(): + document = Document(TEXT) + assert document.lines == [ + "I must not fear.", + "Fear is the mind-killer.", + ] + + +def test_lines_windows(): + document = Document(TEXT_WINDOWS) + assert document.lines == ["I must not fear.", "Fear is the mind-killer."] + + +def test_lines_windows_newline(): + document = Document(TEXT_WINDOWS_NEWLINE) + assert document.lines == ["I must not fear.", "Fear is the mind-killer.", ""] + + +def test_newline_unix(): + document = Document(TEXT) + assert document.newline == "\n" + + +def test_newline_windows(): + document = Document(TEXT_WINDOWS) + assert document.newline == "\r\n" + + +def test_get_selected_text_no_selection(): + document = Document(TEXT) + selection = document.get_text_range((0, 0), (0, 0)) + assert selection == "" + + +def test_get_selected_text_single_line(): + document = Document(TEXT_WINDOWS) + selection = document.get_text_range((0, 2), (0, 6)) + assert selection == "must" + + +def test_get_selected_text_multiple_lines_unix(): + document = Document(TEXT) + selection = document.get_text_range((0, 2), (1, 2)) + assert selection == "must not fear.\nFe" + + +def test_get_selected_text_multiple_lines_windows(): + document = Document(TEXT_WINDOWS) + selection = document.get_text_range((0, 2), (1, 2)) + assert selection == "must not fear.\r\nFe" + + +def test_get_selected_text_including_final_newline_unix(): + document = Document(TEXT_NEWLINE) + selection = document.get_text_range((0, 0), (2, 0)) + assert selection == TEXT_NEWLINE + + +def test_get_selected_text_including_final_newline_windows(): + document = Document(TEXT_WINDOWS_NEWLINE) + selection = document.get_text_range((0, 0), (2, 0)) + assert selection == TEXT_WINDOWS_NEWLINE + + +def test_get_selected_text_no_newline_at_end_of_file(): + document = Document(TEXT) + selection = document.get_text_range((0, 0), (2, 0)) + assert selection == TEXT + + +def test_get_selected_text_no_newline_at_end_of_file_windows(): + document = Document(TEXT_WINDOWS) + selection = document.get_text_range((0, 0), (2, 0)) + assert selection == TEXT_WINDOWS diff --git a/tests/document/test_document_delete.py b/tests/document/test_document_delete.py new file mode 100644 index 0000000000..d00fa686c9 --- /dev/null +++ b/tests/document/test_document_delete.py @@ -0,0 +1,146 @@ +import pytest + +from textual.widgets.text_area import Document, EditResult + +TEXT = """I must not fear. +Fear is the mind-killer. +I forgot the rest of the quote. +Sorry Will.""" + + +@pytest.fixture +def document(): + document = Document(TEXT) + return document + + +def test_delete_single_character(document): + replace_result = document.replace_range((0, 0), (0, 1), "") + assert replace_result == EditResult(end_location=(0, 0), replaced_text="I") + assert document.lines == [ + " must not fear.", + "Fear is the mind-killer.", + "I forgot the rest of the quote.", + "Sorry Will.", + ] + + +def test_delete_single_newline(document): + """Testing deleting newline from right to left""" + replace_result = document.replace_range((1, 0), (0, 16), "") + assert replace_result == EditResult(end_location=(0, 16), replaced_text="\n") + assert document.lines == [ + "I must not fear.Fear is the mind-killer.", + "I forgot the rest of the quote.", + "Sorry Will.", + ] + + +def test_delete_near_end_of_document(document): + """Test deleting a range near the end of a document.""" + replace_result = document.replace_range((1, 0), (3, 11), "") + assert replace_result == EditResult( + end_location=(1, 0), + replaced_text="Fear is the mind-killer.\n" + "I forgot the rest of the quote.\n" + "Sorry Will.", + ) + assert document.lines == [ + "I must not fear.", + "", + ] + + +def test_delete_clearing_the_document(document): + replace_result = document.replace_range((0, 0), (4, 0), "") + assert replace_result == EditResult( + end_location=(0, 0), + replaced_text=TEXT, + ) + assert document.lines == [""] + + +def test_delete_multiple_characters_on_one_line(document): + replace_result = document.replace_range((0, 2), (0, 7), "") + assert replace_result == EditResult( + end_location=(0, 2), + replaced_text="must ", + ) + assert document.lines == [ + "I not fear.", + "Fear is the mind-killer.", + "I forgot the rest of the quote.", + "Sorry Will.", + ] + + +def test_delete_multiple_lines_partially_spanned(document): + """Deleting a selection that partially spans the first and final lines of the selection.""" + replace_result = document.replace_range((0, 2), (2, 2), "") + assert replace_result == EditResult( + end_location=(0, 2), + replaced_text="must not fear.\nFear is the mind-killer.\nI ", + ) + assert document.lines == [ + "I forgot the rest of the quote.", + "Sorry Will.", + ] + + +def test_delete_end_of_line(document): + """Testing deleting newline from left to right""" + replace_result = document.replace_range((0, 16), (1, 0), "") + assert replace_result == EditResult( + end_location=(0, 16), + replaced_text="\n", + ) + assert document.lines == [ + "I must not fear.Fear is the mind-killer.", + "I forgot the rest of the quote.", + "Sorry Will.", + ] + + +def test_delete_single_line_excluding_newline(document): + """Delete from the start to the end of the line.""" + replace_result = document.replace_range((2, 0), (2, 31), "") + assert replace_result == EditResult( + end_location=(2, 0), + replaced_text="I forgot the rest of the quote.", + ) + assert document.lines == [ + "I must not fear.", + "Fear is the mind-killer.", + "", + "Sorry Will.", + ] + + +def test_delete_single_line_including_newline(document): + """Delete from the start of a line to the start of the line below.""" + replace_result = document.replace_range((2, 0), (3, 0), "") + assert replace_result == EditResult( + end_location=(2, 0), + replaced_text="I forgot the rest of the quote.\n", + ) + assert document.lines == [ + "I must not fear.", + "Fear is the mind-killer.", + "Sorry Will.", + ] + + +TEXT_NEWLINE_EOF = """\ +I must not fear. +Fear is the mind-killer. +""" + + +def test_delete_end_of_file_newline(): + document = Document(TEXT_NEWLINE_EOF) + replace_result = document.replace_range((2, 0), (1, 24), "") + assert replace_result == EditResult(end_location=(1, 24), replaced_text="\n") + assert document.lines == [ + "I must not fear.", + "Fear is the mind-killer.", + ] diff --git a/tests/document/test_document_insert.py b/tests/document/test_document_insert.py new file mode 100644 index 0000000000..ea706c9abf --- /dev/null +++ b/tests/document/test_document_insert.py @@ -0,0 +1,107 @@ +from textual.widgets.text_area import Document + +TEXT = """I must not fear. +Fear is the mind-killer.""" + + +def test_insert_no_newlines(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 1), " really") + assert document.lines == [ + "I really must not fear.", + "Fear is the mind-killer.", + ] + + +def test_insert_empty_string(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 1), "") + assert document.lines == ["I must not fear.", "Fear is the mind-killer."] + + +def test_insert_invalid_column(): + document = Document(TEXT) + document.replace_range((0, 999), (0, 999), " really") + assert document.lines == ["I must not fear. really", "Fear is the mind-killer."] + + +def test_insert_invalid_row_and_column(): + document = Document(TEXT) + document.replace_range((999, 0), (999, 0), " really") + assert document.lines == ["I must not fear.", "Fear is the mind-killer.", " really"] + + +def test_insert_range_newline_file_start(): + document = Document(TEXT) + document.replace_range((0, 0), (0, 0), "\n") + assert document.lines == ["", "I must not fear.", "Fear is the mind-killer."] + + +def test_insert_newline_splits_line(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 1), "\n") + assert document.lines == ["I", " must not fear.", "Fear is the mind-killer."] + + +def test_insert_newline_splits_line_selection(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 6), "\n") + assert document.lines == ["I", " not fear.", "Fear is the mind-killer."] + + +def test_insert_multiple_lines_ends_with_newline(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 1), "Hello,\nworld!\n") + assert document.lines == [ + "IHello,", + "world!", + " must not fear.", + "Fear is the mind-killer.", + ] + + +def test_insert_multiple_lines_ends_with_no_newline(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 1), "Hello,\nworld!") + assert document.lines == [ + "IHello,", + "world! must not fear.", + "Fear is the mind-killer.", + ] + + +def test_insert_multiple_lines_starts_with_newline(): + document = Document(TEXT) + document.replace_range((0, 1), (0, 1), "\nHello,\nworld!\n") + assert document.lines == [ + "I", + "Hello,", + "world!", + " must not fear.", + "Fear is the mind-killer.", + ] + + +def test_insert_range_text_no_newlines(): + """Ensuring we can do a simple replacement of text.""" + document = Document(TEXT) + document.replace_range((0, 2), (0, 6), "MUST") + assert document.lines == [ + "I MUST not fear.", + "Fear is the mind-killer.", + ] + + +TEXT_NEWLINE_EOF = """\ +I must not fear. +Fear is the mind-killer. +""" + + +def test_newline_eof(): + document = Document(TEXT_NEWLINE_EOF) + assert document.lines == [ + "I must not fear.", + "Fear is the mind-killer.", + "", + ] diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 969ee7ffb4..a1b1206dbe 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -29858,6 +29858,3249 @@ ''' # --- +# name: test_text_area_language_rendering[css] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 /* This is a comment in CSS */ + 2 + 3 /* Basic selectors and properties */ + 4 body { + 5 font-family: Arial, sans-serif; + 6 background-color: #f4f4f4; + 7 margin: 0; + 8 padding: 0; + 9 } + 10 + 11 /* Class and ID selectors */ + 12 .header { + 13 background-color: #333; + 14 color: #fff; + 15 padding: 10px0; + 16 text-align: center; + 17 } + 18 + 19 #logo { + 20 font-size: 24px; + 21 font-weight: bold; + 22 } + 23 + 24 /* Descendant and child selectors */ + 25 .nav ul { + 26 list-style-type: none; + 27 padding: 0; + 28 } + 29 + 30 .nav > li { + 31 display: inline-block; + 32 margin-right: 10px; + 33 } + 34 + 35 /* Pseudo-classes */ + 36 a:hover { + 37 text-decoration: underline; + 38 } + 39 + 40 input:focus { + 41 border-color: #007BFF; + 42 } + 43 + 44 /* Media query */ + 45 @media (max-width: 768px) { + 46 body { + 47 font-size: 16px; + 48 } + 49 + 50 .header { + 51 padding: 5px0; + 52 } + 53 } + 54 + 55 /* Keyframes animation */ + 56 @keyframes slideIn { + 57 from { + 58 transform: translateX(-100%); + 59 } + 60 to { + 61 transform: translateX(0); + 62 } + 63 } + 64 + 65 .slide-in-element { + 66 animation: slideIn 0.5s forwards; + 67 } + 68 + + + + + + ''' +# --- +# name: test_text_area_language_rendering[html] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 <!DOCTYPE html> + 2 <html lang="en"> + 3 + 4 <head> + 5 <!-- Meta tags --> + 6 <meta charset="UTF-8"> + 7 <meta name="viewport" content="width=device-width, initial-scale=1.0"> + 8 <!-- Title --> + 9 <title>HTML Test Page</title> + 10 <!-- Link to CSS --> + 11 <link rel="stylesheet" href="styles.css"> + 12 </head> + 13 + 14 <body> + 15 <!-- Header section --> + 16 <header class="header"> + 17 <h1 id="logo">HTML Test Page</h1> + 18 </header> + 19 + 20 <!-- Navigation --> + 21 <nav class="nav"> + 22 <ul> + 23 <li><a href="#">Home</a></li> + 24 <li><a href="#">About</a></li> + 25 <li><a href="#">Contact</a></li> + 26 </ul> + 27 </nav> + 28 + 29 <!-- Main content area --> + 30 <main> + 31 <article> + 32 <h2>Welcome to the Test Page</h2> + 33 <p>This is a paragraph to test the HTML structure.</p> + 34 <img src="test-image.jpg" alt="Test Image" width="300"> + 35 </article> + 36 </main> + 37 + 38 <!-- Form --> + 39 <section> + 40 <form action="/submit" method="post"> + 41 <label for="name">Name:</label> + 42 <input type="text" id="name" name="name"> + 43 <input type="submit" value="Submit"> + 44 </form> + 45 </section> + 46 + 47 <!-- Footer --> + 48 <footer> + 49 <p>© 2023 HTML Test Page</p> + 50 </footer> + 51 + 52 <!-- Script tag --> + 53 <script src="scripts.js"></script> + 54 </body> + 55 + 56 </html> + 57 + + + + + + ''' +# --- +# name: test_text_area_language_rendering[json] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 { + 2 "name": "John Doe", + 3 "age": 30, + 4 "isStudent": false, + 5 "address": { + 6 "street": "123 Main St", + 7 "city": "Anytown", + 8 "state": "CA", + 9 "zip": "12345" + 10 }, + 11 "phoneNumbers": [ + 12 { + 13 "type": "home", + 14 "number": "555-555-1234" + 15 }, + 16 { + 17 "type": "work", + 18 "number": "555-555-5678" + 19 } + 20 ], + 21 "hobbies": ["reading", "hiking", "swimming"], + 22 "pets": [ + 23 { + 24 "type": "dog", + 25 "name": "Fido" + 26 }, + 27 ], + 28 "graduationYear": null + 29 } + 30 + 31 + + + + + + ''' +# --- +# name: test_text_area_language_rendering[markdown] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 Heading + 2 ======= + 3 + 4 Sub-heading + 5 ----------- + 6 + 7 ### Heading + 8 + 9 #### H4 Heading + 10 + 11 ##### H5 Heading + 12 + 13 ###### H6 Heading + 14 + 15 + 16 Paragraphs are separated + 17 by a blank line. + 18 + 19 Two spaces at the end of a line + 20 produces a line break. + 21 + 22 Text attributes _italic_, + 23 **bold**, `monospace`. + 24 + 25 Horizontal rule: + 26 + 27 --- + 28 + 29 Bullet list: + 30 + 31 * apples + 32 * oranges + 33 * pears + 34 + 35 Numbered list: + 36 + 37 1. lather + 38 2. rinse + 39 3. repeat + 40 + 41 An [example](http://example.com). + 42 + 43 > Markdown uses email-style > characters for blockquoting. + 44 > + 45 > Lorem ipsum + 46 + 47 ![progress](https://github.com/textualize/rich/raw/master/imgs/progress.gif) + 48 + 49 + 50 ``` + 51 a=1 + 52 ``` + 53 + 54 ```python + 55 import this + 56 ``` + 57 + 58 ```somelang + 59 foobar + 60 ``` + 61 + 62 import this + 63 + 64 + 65 1. List item + 66 + 67 Code block + 68 + ▏ + + + + + ''' +# --- +# name: test_text_area_language_rendering[python] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 import math + 2 from os import path + 3 + 4 # I'm a comment :) + 5 + 6 string_var ="Hello, world!" + 7 int_var =42 + 8 float_var =3.14 + 9 complex_var =1+2j + 10 + 11 list_var = [1, 2, 3, 4, 5] + 12 tuple_var = (1, 2, 3, 4, 5) + 13 set_var = {1, 2, 3, 4, 5} + 14 dict_var = {"a": 1, "b": 2, "c": 3} + 15 + 16 deffunction_no_args(): + 17 return"No arguments" + 18 + 19 deffunction_with_args(a, b): + 20 return a + b + 21 + 22 deffunction_with_default_args(a=0, b=0): + 23 return a * b + 24 + 25 lambda_func =lambda x: x**2 + 26 + 27 if int_var ==42: + 28 print("It's the answer!") + 29 elif int_var <42: + 30 print("Less than the answer.") + 31 else: + 32 print("Greater than the answer.") + 33 + 34 for index, value inenumerate(list_var): + 35 print(f"Index: {index}, Value: {value}") + 36 + 37 counter =0 + 38 while counter <5: + 39 print(f"Counter value: {counter}") + 40 counter +=1 + 41 + 42 squared_numbers = [x**2for x inrange(10) if x %2==0] + 43 + 44 try: + 45 result =10/0 + 46 except ZeroDivisionError: + 47 print("Cannot divide by zero!") + 48 finally: + 49 print("End of try-except block.") + 50 + 51 classAnimal: + 52 def__init__(self, name): + 53 self.name = name + 54 + 55 defspeak(self): + 56 raiseNotImplementedError("Subclasses must implement this method.") + 57 + 58 classDog(Animal): + 59 defspeak(self): + 60 returnf"{self.name} says Woof!" + 61 + 62 deffibonacci(n): + 63 a, b =0, 1 + 64 for _ inrange(n): + 65 yield a + 66 a, b = b, a + b + 67 + 68 for num infibonacci(5): + 69 print(num) + 70 + 71 withopen('test.txt', 'w') as f: + 72 f.write("Testing with statement.") + 73 + 74 @my_decorator + 75 defsay_hello(): + 76 print("Hello!") + 77 + 78 say_hello() + 79 + + + + + + ''' +# --- +# name: test_text_area_language_rendering[regex] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 ^abc # Matches any string that starts with "abc" + 2 abc$ # Matches any string that ends with "abc" + 3 ^abc$ # Matches the string "abc" and nothing else + 4 a.b # Matches any string containing "a", any character, then "b" + 5 a[.]b # Matches the string "a.b" + 6 a|b # Matches either "a" or "b" + 7 a{2} # Matches "aa" + 8 a{2,} # Matches two or more consecutive "a" characters + 9 a{2,5} # Matches between 2 and 5 consecutive "a" characters + 10 a? # Matches "a" or nothing (0 or 1 occurrence of "a") + 11 a* # Matches zero or more consecutive "a" characters + 12 a+ # Matches one or more consecutive "a" characters + 13 \d # Matches any digit (equivalent to [0-9]) + 14 \D # Matches any non-digit + 15 \w # Matches any word character (equivalent to [a-zA-Z0-9_]) + 16 \W # Matches any non-word character + 17 \s # Matches any whitespace character (spaces, tabs, line break + 18 \S # Matches any non-whitespace character + 19 (?i)abc # Case-insensitive match for "abc" + 20 (?:a|b) # Non-capturing group for either "a" or "b" + 21 (?<=a)b # Positive lookbehind: matches "b" that is preceded by "a" + 22 (?<!a)b # Negative lookbehind: matches "b" that is not preceded by " + 23 a(?=b) # Positive lookahead: matches "a" that is followed by "b" + 24 a(?!b) # Negative lookahead: matches "a" that is not followed by "b + 25 + ▏ + + + + + ''' +# --- +# name: test_text_area_language_rendering[sql] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 -- This is a comment in SQL + 2 + 3 -- Create tables + 4 CREATETABLE Authors ( + 5 AuthorID INT PRIMARY KEY, + 6 Name VARCHAR(255) NOT NULL, + 7 Country VARCHAR(50) + 8 ); + 9 + 10 CREATETABLE Books ( + 11 BookID INT PRIMARY KEY, + 12 Title VARCHAR(255) NOT NULL, + 13 AuthorID INT, + 14 PublishedDate DATE, + 15 FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID) + 16 ); + 17 + 18 -- Insert data + 19 INSERTINTO Authors (AuthorID, Name, Country) VALUES (1, 'George Orwell', 'U + 20 + 21 INSERTINTO Books (BookID, Title, AuthorID, PublishedDate) VALUES (1, '1984' + 22 + 23 -- Update data + 24 UPDATE Authors SET Country ='United Kingdom'WHERE Country ='UK'; + 25 + 26 -- Select data with JOIN + 27 SELECT Books.Title, Authors.Name + 28 FROM Books + 29 JOIN Authors ON Books.AuthorID = Authors.AuthorID; + 30 + 31 -- Delete data (commented to preserve data for other examples) + 32 -- DELETE FROM Books WHERE BookID = 1; + 33 + 34 -- Alter table structure + 35 ALTER TABLE Authors ADD COLUMN BirthDate DATE; + 36 + 37 -- Create index + 38 CREATEINDEX idx_author_name ON Authors(Name); + 39 + 40 -- Drop index (commented to avoid actually dropping it) + 41 -- DROP INDEX idx_author_name ON Authors; + 42 + 43 -- End of script + 44 + + + + + + ''' +# --- +# name: test_text_area_language_rendering[toml] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 # This is a comment in TOML + 2 + 3 string = "Hello, world!" + 4 integer = 42 + 5 float = 3.14 + 6 boolean = true + 7 datetime = 1979-05-27T07:32:00Z + 8 + 9 fruits = ["apple", "banana", "cherry"] + 10 + 11 [address] + 12 street = "123 Main St" + 13 city = "Anytown" + 14 state = "CA" + 15 zip = "12345" + 16 + 17 [person.john] + 18 name = "John Doe" + 19 age = 28 + 20 is_student = false + 21 + 22 + 23 [[animals]] + 24 name = "Fido" + 25 type = "dog" + 26 + + + + + + ''' +# --- +# name: test_text_area_language_rendering[yaml] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 # This is a comment in YAML + 2 + 3 # Scalars + 4 string: "Hello, world!" + 5 integer: 42 + 6 float: 3.14 + 7 boolean: true + 8 + 9 # Sequences (Arrays) + 10 fruits: + 11 - Apple + 12 - Banana + 13 - Cherry + 14 + 15 # Nested sequences + 16 persons: + 17 - name: John + 18 age: 28 + 19 is_student: false + 20 - name: Jane + 21 age: 22 + 22 is_student: true + 23 + 24 # Mappings (Dictionaries) + 25 address: + 26 street: 123 Main St + 27 city: Anytown + 28 state: CA + 29 zip: '12345' + 30 + 31 # Multiline string + 32 description: | + 33 This is a multiline + 34 string in YAML. + 35 + 36 # Inline and nested collections + 37 colors: { red: FF0000, green: 00FF00, blue: 0000FF } + 38 + + + + + + ''' +# --- +# name: test_text_area_selection_rendering[selection0] + ''' + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + I am a line. + ▌ + I am another line. + + I am the final line. + + + + + ''' +# --- +# name: test_text_area_selection_rendering[selection1] + ''' + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + I am a line. + ▌ + I am another line. + + I am the final line. + + + + + ''' +# --- +# name: test_text_area_selection_rendering[selection2] + ''' + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + I am a line. + ▌ + I am another line. + ▌ + I am the final line. + + + + + ''' +# --- +# name: test_text_area_selection_rendering[selection3] + ''' + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + I am a line. + ▌ + I am another line. + ▌ + I am the final line. + + + + + ''' +# --- +# name: test_text_area_selection_rendering[selection4] + ''' + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + I am a line. + + I am another line. + + I am the final line. + + + + + ''' +# --- +# name: test_text_area_selection_rendering[selection5] + ''' + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + I am a line. + + I am another line. + + I am the final line. + + + + + ''' +# --- +# name: test_text_area_themes[dracula] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 defhello(name): + 2 x =123 + 3 whilenotFalse: + 4 print("hello "+ name) + 5 continue + 6 + + + + + + ''' +# --- +# name: test_text_area_themes[github_light] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 defhello(name): + 2 x=123 + 3 whilenotFalse: + 4 print("hello "+name) + 5 continue + 6 + + + + + + ''' +# --- +# name: test_text_area_themes[monokai] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 defhello(name): + 2 x =123 + 3 whilenotFalse: + 4 print("hello "+ name) + 5 continue + 6 + + + + + + ''' +# --- +# name: test_text_area_themes[vscode_dark] + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaSnapshot + + + + + + + + + + 1 defhello(name): + 2 x =123 + 3 whilenotFalse: + 4 print("hello "+ name) + 5 continue + 6 + + + + + + ''' +# --- # name: test_text_log_blank_write ''' diff --git a/tests/snapshot_tests/language_snippets.py b/tests/snapshot_tests/language_snippets.py new file mode 100644 index 0000000000..fd7a6a2954 --- /dev/null +++ b/tests/snapshot_tests/language_snippets.py @@ -0,0 +1,466 @@ +PYTHON = """\ +import math +from os import path + +# I'm a comment :) + +string_var = "Hello, world!" +int_var = 42 +float_var = 3.14 +complex_var = 1 + 2j + +list_var = [1, 2, 3, 4, 5] +tuple_var = (1, 2, 3, 4, 5) +set_var = {1, 2, 3, 4, 5} +dict_var = {"a": 1, "b": 2, "c": 3} + +def function_no_args(): + return "No arguments" + +def function_with_args(a, b): + return a + b + +def function_with_default_args(a=0, b=0): + return a * b + +lambda_func = lambda x: x**2 + +if int_var == 42: + print("It's the answer!") +elif int_var < 42: + print("Less than the answer.") +else: + print("Greater than the answer.") + +for index, value in enumerate(list_var): + print(f"Index: {index}, Value: {value}") + +counter = 0 +while counter < 5: + print(f"Counter value: {counter}") + counter += 1 + +squared_numbers = [x**2 for x in range(10) if x % 2 == 0] + +try: + result = 10 / 0 +except ZeroDivisionError: + print("Cannot divide by zero!") +finally: + print("End of try-except block.") + +class Animal: + def __init__(self, name): + self.name = name + + def speak(self): + raise NotImplementedError("Subclasses must implement this method.") + +class Dog(Animal): + def speak(self): + return f"{self.name} says Woof!" + +def fibonacci(n): + a, b = 0, 1 + for _ in range(n): + yield a + a, b = b, a + b + +for num in fibonacci(5): + print(num) + +with open('test.txt', 'w') as f: + f.write("Testing with statement.") + +@my_decorator +def say_hello(): + print("Hello!") + +say_hello() +""" + + +MARKDOWN = """\ +Heading +======= + +Sub-heading +----------- + +### Heading + +#### H4 Heading + +##### H5 Heading + +###### H6 Heading + + +Paragraphs are separated +by a blank line. + +Two spaces at the end of a line +produces a line break. + +Text attributes _italic_, +**bold**, `monospace`. + +Horizontal rule: + +--- + +Bullet list: + + * apples + * oranges + * pears + +Numbered list: + + 1. lather + 2. rinse + 3. repeat + +An [example](http://example.com). + +> Markdown uses email-style > characters for blockquoting. +> +> Lorem ipsum + +![progress](https://github.com/textualize/rich/raw/master/imgs/progress.gif) + + +``` +a=1 +``` + +```python +import this +``` + +```somelang +foobar +``` + + import this + + +1. List item + + Code block +""" + +YAML = """\ +# This is a comment in YAML + +# Scalars +string: "Hello, world!" +integer: 42 +float: 3.14 +boolean: true + +# Sequences (Arrays) +fruits: + - Apple + - Banana + - Cherry + +# Nested sequences +persons: + - name: John + age: 28 + is_student: false + - name: Jane + age: 22 + is_student: true + +# Mappings (Dictionaries) +address: + street: 123 Main St + city: Anytown + state: CA + zip: '12345' + +# Multiline string +description: | + This is a multiline + string in YAML. + +# Inline and nested collections +colors: { red: FF0000, green: 00FF00, blue: 0000FF } +""" + +TOML = """\ +# This is a comment in TOML + +string = "Hello, world!" +integer = 42 +float = 3.14 +boolean = true +datetime = 1979-05-27T07:32:00Z + +fruits = ["apple", "banana", "cherry"] + +[address] +street = "123 Main St" +city = "Anytown" +state = "CA" +zip = "12345" + +[person.john] +name = "John Doe" +age = 28 +is_student = false + + +[[animals]] +name = "Fido" +type = "dog" +""" + +SQL = """\ +-- This is a comment in SQL + +-- Create tables +CREATE TABLE Authors ( + AuthorID INT PRIMARY KEY, + Name VARCHAR(255) NOT NULL, + Country VARCHAR(50) +); + +CREATE TABLE Books ( + BookID INT PRIMARY KEY, + Title VARCHAR(255) NOT NULL, + AuthorID INT, + PublishedDate DATE, + FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID) +); + +-- Insert data +INSERT INTO Authors (AuthorID, Name, Country) VALUES (1, 'George Orwell', 'UK'); + +INSERT INTO Books (BookID, Title, AuthorID, PublishedDate) VALUES (1, '1984', 1, '1949-06-08'); + +-- Update data +UPDATE Authors SET Country = 'United Kingdom' WHERE Country = 'UK'; + +-- Select data with JOIN +SELECT Books.Title, Authors.Name +FROM Books +JOIN Authors ON Books.AuthorID = Authors.AuthorID; + +-- Delete data (commented to preserve data for other examples) +-- DELETE FROM Books WHERE BookID = 1; + +-- Alter table structure +ALTER TABLE Authors ADD COLUMN BirthDate DATE; + +-- Create index +CREATE INDEX idx_author_name ON Authors(Name); + +-- Drop index (commented to avoid actually dropping it) +-- DROP INDEX idx_author_name ON Authors; + +-- End of script +""" + +CSS = """\ +/* This is a comment in CSS */ + +/* Basic selectors and properties */ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + margin: 0; + padding: 0; +} + +/* Class and ID selectors */ +.header { + background-color: #333; + color: #fff; + padding: 10px 0; + text-align: center; +} + +#logo { + font-size: 24px; + font-weight: bold; +} + +/* Descendant and child selectors */ +.nav ul { + list-style-type: none; + padding: 0; +} + +.nav > li { + display: inline-block; + margin-right: 10px; +} + +/* Pseudo-classes */ +a:hover { + text-decoration: underline; +} + +input:focus { + border-color: #007BFF; +} + +/* Media query */ +@media (max-width: 768px) { + body { + font-size: 16px; + } + + .header { + padding: 5px 0; + } +} + +/* Keyframes animation */ +@keyframes slideIn { + from { + transform: translateX(-100%); + } + to { + transform: translateX(0); + } +} + +.slide-in-element { + animation: slideIn 0.5s forwards; +} +""" + +HTML = """\ + + + + + + + + + HTML Test Page + + + + + + + + HTML Test Page + + + + + + Home + About + Contact + + + + + + + Welcome to the Test Page + This is a paragraph to test the HTML structure. + + + + + + + + Name: + + + + + + + + + + + + + +""" + +JSON = """\ +{ + "name": "John Doe", + "age": 30, + "isStudent": false, + "address": { + "street": "123 Main St", + "city": "Anytown", + "state": "CA", + "zip": "12345" + }, + "phoneNumbers": [ + { + "type": "home", + "number": "555-555-1234" + }, + { + "type": "work", + "number": "555-555-5678" + } + ], + "hobbies": ["reading", "hiking", "swimming"], + "pets": [ + { + "type": "dog", + "name": "Fido" + }, + ], + "graduationYear": null +} + +""" + +REGEX = """\ +^abc # Matches any string that starts with "abc" +abc$ # Matches any string that ends with "abc" +^abc$ # Matches the string "abc" and nothing else +a.b # Matches any string containing "a", any character, then "b" +a[.]b # Matches the string "a.b" +a|b # Matches either "a" or "b" +a{2} # Matches "aa" +a{2,} # Matches two or more consecutive "a" characters +a{2,5} # Matches between 2 and 5 consecutive "a" characters +a? # Matches "a" or nothing (0 or 1 occurrence of "a") +a* # Matches zero or more consecutive "a" characters +a+ # Matches one or more consecutive "a" characters +\d # Matches any digit (equivalent to [0-9]) +\D # Matches any non-digit +\w # Matches any word character (equivalent to [a-zA-Z0-9_]) +\W # Matches any non-word character +\s # Matches any whitespace character (spaces, tabs, line breaks) +\S # Matches any non-whitespace character +(?i)abc # Case-insensitive match for "abc" +(?:a|b) # Non-capturing group for either "a" or "b" +(?<=a)b # Positive lookbehind: matches "b" that is preceded by "a" +(? ComposeResult: + text_area = TextArea() + text_area.cursor_blink = False + yield text_area + + +app = TextAreaSnapshot() +if __name__ == "__main__": + app.run() diff --git a/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py b/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py new file mode 100644 index 0000000000..e092f16721 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/text_area_unfocus.py @@ -0,0 +1,17 @@ +"""Tests the rendering of the TextArea for all supported languages.""" +from textual.app import App, ComposeResult +from textual.widgets import TextArea + + +class TextAreaUnfocusSnapshot(App): + AUTO_FOCUS = None + + def compose(self) -> ComposeResult: + text_area = TextArea() + text_area.cursor_blink = False + yield text_area + + +app = TextAreaUnfocusSnapshot() +if __name__ == "__main__": + app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index f0f478515e..d60b94c588 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -2,6 +2,11 @@ import pytest +from tests.snapshot_tests.language_snippets import SNIPPETS +from textual.widgets.text_area import Selection, BUILTIN_LANGUAGES +from textual.widgets import TextArea +from textual.widgets.text_area import TextAreaTheme + # These paths should be relative to THIS directory. WIDGET_EXAMPLES_DIR = Path("../../docs/examples/widgets") LAYOUT_EXAMPLES_DIR = Path("../../docs/examples/guide/layout") @@ -89,7 +94,8 @@ def test_input_validation(snap_compare): "tab", "3", # This is valid, so -valid should be applied "tab", - *"-2", # -2 is invalid, so -invalid should be applied (and :focus, since we stop here) + *"-2", + # -2 is invalid, so -invalid should be applied (and :focus, since we stop here) ] assert snap_compare(SNAPSHOT_APPS_DIR / "input_validation.py", press=press) @@ -700,6 +706,85 @@ def test_nested_fr(snap_compare) -> None: assert snap_compare(SNAPSHOT_APPS_DIR / "nested_fr.py") +@pytest.mark.parametrize("language", BUILTIN_LANGUAGES) +def test_text_area_language_rendering(language, snap_compare): + # This test will fail if we're missing a snapshot test for a valid + # language. We should have a snapshot test for each language we support + # as the syntax highlighting will be completely different for each of them. + + snippet = SNIPPETS.get(language) + + def setup_language(pilot) -> None: + text_area = pilot.app.query_one(TextArea) + text_area.load_text(snippet) + text_area.language = language + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup_language, + terminal_size=(80, snippet.count("\n") + 2), + ) + + +@pytest.mark.parametrize( + "selection", + [ + Selection((0, 0), (2, 8)), + Selection((1, 0), (0, 0)), + Selection((5, 2), (0, 0)), + Selection((0, 0), (4, 20)), + Selection.cursor((1, 0)), + Selection.cursor((2, 6)), + ], +) +def test_text_area_selection_rendering(snap_compare, selection): + text = """I am a line. + +I am another line. + +I am the final line.""" + + def setup_selection(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.load_text(text) + text_area.show_line_numbers = False + text_area.selection = selection + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup_selection, + terminal_size=(30, text.count("\n") + 1), + ) + + +@pytest.mark.parametrize("theme_name", + [theme.name for theme in TextAreaTheme.builtin_themes()]) +def test_text_area_themes(snap_compare, theme_name): + """Each theme should have its own snapshot with at least some Python + to check that the rendering is sensible. This also ensures that theme + switching results in the display changing correctly.""" + text = """\ +def hello(name): + x = 123 + while not False: + print("hello " + name) + continue +""" + + def setup_theme(pilot): + text_area = pilot.app.query_one(TextArea) + text_area.load_text(text) + text_area.language = "python" + text_area.selection = Selection((0, 1), (1, 9)) + text_area.theme = theme_name + + assert snap_compare( + SNAPSHOT_APPS_DIR / "text_area.py", + run_before=setup_theme, + terminal_size=(48, text.count("\n") + 2), + ) + + def test_digits(snap_compare) -> None: assert snap_compare(SNAPSHOT_APPS_DIR / "digits.py") diff --git a/tests/text_area/test_edit_via_api.py b/tests/text_area/test_edit_via_api.py new file mode 100644 index 0000000000..4cf8602e0a --- /dev/null +++ b/tests/text_area/test_edit_via_api.py @@ -0,0 +1,522 @@ +"""Tests editing the document using the API (replace etc.) + +The tests in this module directly call the edit APIs on the TextArea rather +than going via bindings. + +Note that more extensive testing for editing is done at the Document level. +""" +import pytest + +from textual.app import App, ComposeResult +from textual.widgets import TextArea +from textual.widgets.text_area import EditResult, Selection + +TEXT = """\ +I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +""" + +SIMPLE_TEXT = """\ +ABCDE +FGHIJ +KLMNO +PQRST +UVWXY +Z +""" + + +class TextAreaApp(App): + def compose(self) -> ComposeResult: + text_area = TextArea() + text_area.load_text(TEXT) + yield text_area + + +async def test_insert_text_start_maintain_selection_offset(): + """Ensure that we can maintain the offset between the location + an insert happens and the location of the selection.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor((0, 5)) + text_area.insert("Hello", location=(0, 0)) + assert text_area.text == "Hello" + TEXT + assert text_area.selection == Selection.cursor((0, 10)) + + +async def test_insert_text_start(): + """The document is correctly updated on inserting at the start. + If we don't maintain the selection offset, the cursor jumps + to the end of the edit and the selection is empty.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor((0, 5)) + text_area.insert("Hello", location=(0, 0), maintain_selection_offset=False) + assert text_area.text == "Hello" + TEXT + assert text_area.selection == Selection.cursor((0, 5)) + + +async def test_insert_empty_string(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + + text_area.insert("", location=(0, 3)) + + assert text_area.text == "0123456789" + + +async def test_replace_empty_string(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + + text_area.replace("", start=(0, 3), end=(0, 7)) + + assert text_area.text == "012789" + + +@pytest.mark.parametrize( + "cursor_location,insert_location,cursor_destination", + [ + ((0, 3), (0, 2), (0, 4)), # API insert just before cursor + ((0, 3), (0, 3), (0, 4)), # API insert at cursor location + ((0, 3), (0, 4), (0, 3)), # API insert just after cursor + ((0, 3), (0, 5), (0, 3)), # API insert just after cursor + ], +) +async def test_insert_character_near_cursor_maintain_selection_offset( + cursor_location, + insert_location, + cursor_destination, +): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("012345") + text_area.move_cursor(cursor_location) + text_area.insert("X", location=insert_location) + assert text_area.selection == Selection.cursor(cursor_destination) + + +async def test_insert_newlines_start(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.insert("\n\n\n") + assert text_area.text == "\n\n\n" + TEXT + assert text_area.selection == Selection.cursor((3, 0)) + + +async def test_insert_newlines_end(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.insert("\n\n\n", location=(4, 0)) + assert text_area.text == TEXT + "\n\n\n" + + +async def test_insert_windows_newlines(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + # Although we're inserting windows newlines, the configured newline on + # the Document inside the TextArea will be "\n", so when we check TextArea.text + # we expect to see "\n". + text_area.insert("\r\n\r\n\r\n") + assert text_area.text == "\n\n\n" + TEXT + + +async def test_insert_old_mac_newlines(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.insert("\r\r\r") + assert text_area.text == "\n\n\n" + TEXT + + +async def test_insert_text_non_cursor_location(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.insert("Hello", location=(4, 0)) + assert text_area.text == TEXT + "Hello" + assert text_area.selection == Selection.cursor((0, 0)) + + +async def test_insert_text_non_cursor_location_dont_maintain_offset(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((2, 3), (3, 5)) + + result = text_area.insert( + "Hello", + location=(4, 0), + maintain_selection_offset=False, + ) + + assert result == EditResult( + end_location=(4, 5), + replaced_text="", + ) + assert text_area.text == TEXT + "Hello" + + # Since maintain_selection_offset is False, the selection + # is reset to a cursor and goes to the end of the insert. + assert text_area.selection == Selection.cursor((4, 5)) + + +async def test_insert_multiline_text(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor((2, 5)) + text_area.insert("Hello,\nworld!", maintain_selection_offset=False) + expected_content = """\ +I must not fear. +Fear is the mind-killer. +Fear Hello, +world!is the little-death that brings total obliteration. +I will face my fear. +""" + assert text_area.cursor_location == (3, 6) # Cursor moved to end of insert + assert text_area.text == expected_content + + +async def test_insert_multiline_text_maintain_offset(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor((2, 5)) + result = text_area.insert("Hello,\nworld!") + + assert result == EditResult( + end_location=(3, 6), + replaced_text="", + ) + + # The insert happens at the cursor (default location) + # Offset is maintained - we inserted 1 line so cursor shifts + # down 1 line, and along by the length of the last insert line. + assert text_area.cursor_location == (3, 6) + expected_content = """\ +I must not fear. +Fear is the mind-killer. +Fear Hello, +world!is the little-death that brings total obliteration. +I will face my fear. +""" + assert text_area.text == expected_content + + +async def test_replace_multiline_text(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + # replace "Fear is the mind-killer\nFear is the little death...\n" + # with "Hello,\nworld!\n" + result = text_area.replace("Hello,\nworld!\n", start=(1, 0), end=(3, 0)) + expected_replaced_text = """\ +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +""" + assert result == EditResult( + end_location=(3, 0), + replaced_text=expected_replaced_text, + ) + + expected_content = """\ +I must not fear. +Hello, +world! +I will face my fear. +""" + assert text_area.selection == Selection.cursor((0, 0)) # cursor didnt move + assert text_area.text == expected_content + + +async def test_replace_multiline_text_maintain_selection(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + + # To begin with, the user selects the word "face" + text_area.selection = Selection((3, 7), (3, 11)) + assert text_area.selected_text == "face" + + # Text is inserted via the API in a way that shifts + # the start and end locations of the word "face" in + # both the horizontal and vertical directions. + text_area.replace( + "Hello,\nworld!\n123\n456", + start=(1, 0), + end=(3, 0), + ) + expected_content = """\ +I must not fear. +Hello, +world! +123 +456I will face my fear. +""" + # Despite this insert, the selection locations are updated + # and the word face is still highlighted. This ensures that + # if text is insert programmatically, a user that is typing + # won't lose their place - the cursor will maintain the same + # relative position in the document as before. + assert text_area.selected_text == "face" + assert text_area.selection == Selection((4, 10), (4, 14)) + assert text_area.text == expected_content + + +async def test_delete_within_line(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((0, 11), (0, 15)) + assert text_area.selected_text == "fear" + + # Delete some text before the selection location. + result = text_area.delete((0, 6), (0, 10)) + + # Even though the word has 'shifted' left, it's still selected. + assert text_area.selection == Selection((0, 7), (0, 11)) + assert text_area.selected_text == "fear" + + # We've recorded exactly what text was replaced in the EditResult + assert result == EditResult( + end_location=(0, 6), + replaced_text=" not", + ) + + expected_text = """\ +I must fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +""" + assert text_area.text == expected_text + + +async def test_delete_within_line_dont_maintain_offset(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.delete((0, 6), (0, 10), maintain_selection_offset=False) + expected_text = """\ +I must fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +""" + assert text_area.selection == Selection.cursor((0, 6)) # cursor moved + assert text_area.text == expected_text + + +async def test_delete_multiple_lines_selection_above(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + + # User has selected text on the first line... + text_area.selection = Selection((0, 2), (0, 6)) + assert text_area.selected_text == "must" + + # Some lines below are deleted... + result = text_area.delete((1, 0), (3, 0)) + + # The selection is not affected at all. + assert text_area.selection == Selection((0, 2), (0, 6)) + + # We've recorded the text that was deleted in the ReplaceResult. + # Lines of index 1 and 2 were deleted. Since the end + # location of the selection is (3, 0), the newline + # marker is included in the deletion. + expected_replaced_text = """\ +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +""" + assert result == EditResult( + end_location=(1, 0), + replaced_text=expected_replaced_text, + ) + assert ( + text_area.text + == """\ +I must not fear. +I will face my fear. +""" + ) + + +async def test_delete_empty_document(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("") + result = text_area.delete((0, 0), (1, 0)) + assert result.replaced_text == "" + assert text_area.text == "" + + +async def test_clear(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.clear() + + +async def test_clear_empty_document(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("") + text_area.clear() + + +@pytest.mark.parametrize( + "select_from,select_to", + [ + [(0, 3), (2, 1)], + [(2, 1), (0, 3)], # Ensuring independence from selection direction. + ], +) +async def test_insert_text_multiline_selection_top(select_from, select_to): + """ + An example to attempt to explain what we're testing here... + + X = edit range, * = character in TextArea, S = selection + + *********XX + XXXXX***SSS + SSSSSSSSSSS + SSSS******* + + If an edit happens at XXXX, we need to ensure that the SSS on the + same line is adjusted appropriately so that it's still highlighting + the same characters as before. + """ + app = TextAreaApp() + async with app.run_test(): + # ABCDE + # FGHIJ + # KLMNO + # PQRST + # UVWXY + # Z + text_area = app.query_one(TextArea) + text_area.load_text(SIMPLE_TEXT) + text_area.selection = Selection(select_from, select_to) + + # Check what text is selected. + expected_selected_text = "DE\nFGHIJ\nK" + assert text_area.selected_text == expected_selected_text + + result = text_area.replace( + "Hello", + start=(0, 0), + end=(0, 2), + ) + + assert result == EditResult(end_location=(0, 5), replaced_text="AB") + + # The edit range has grown from width 2 to width 5, so the + # top line of the selection was adjusted (column+=3) such that the + # same characters are highlighted: + # ... the selection is not changed after programmatic insert + # ... the same text is selected as before. + assert text_area.selected_text == expected_selected_text + + # The resulting text in the TextArea is correct. + assert text_area.text == "HelloCDE\nFGHIJ\nKLMNO\nPQRST\nUVWXY\nZ\n" + + +@pytest.mark.parametrize( + "select_from,select_to", + [ + [(0, 3), (2, 5)], + [(2, 5), (0, 3)], # Ensuring independence from selection direction. + ], +) +async def test_insert_text_multiline_selection_bottom(select_from, select_to): + """ + The edited text is within the selected text on the bottom line + of the selection. The bottom of the selection should be adjusted + such that any text that was previously selected is still selected. + """ + app = TextAreaApp() + async with app.run_test(): + # ABCDE + # FGHIJ + # KLMNO + # PQRST + # UVWXY + # Z + + text_area = app.query_one(TextArea) + text_area.load_text(SIMPLE_TEXT) + text_area.selection = Selection(select_from, select_to) + + # Check what text is selected. + assert text_area.selected_text == "DE\nFGHIJ\nKLMNO" + + result = text_area.replace( + "*", + start=(2, 0), + end=(2, 3), + ) + assert result == EditResult(end_location=(2, 1), replaced_text="KLM") + + # The 'NO' from the selection is still available on the + # bottom selection line, however the 'KLM' is replaced + # with '*'. Since 'NO' is still available, it's maintained + # within the selection. + assert text_area.selected_text == "DE\nFGHIJ\n*NO" + + # The resulting text in the TextArea is correct. + # 'KLM' replaced with '*' + assert text_area.text == "ABCDE\nFGHIJ\n*NO\nPQRST\nUVWXY\nZ\n" + + +async def test_delete_fully_within_selection(): + """User-facing selection should be best-effort adjusted when a programmatic + replacement is made to the document.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + text_area.selection = Selection((0, 2), (0, 7)) + assert text_area.selected_text == "23456" + + result = text_area.delete((0, 4), (0, 6)) + assert result == EditResult( + replaced_text="45", + end_location=(0, 4), + ) + # We deleted 45, but the other characters are still available + assert text_area.selected_text == "236" + assert text_area.text == "01236789" + + +async def test_replace_fully_within_selection(): + """Adjust the selection when a replacement happens inside it.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + text_area.selection = Selection((0, 2), (0, 7)) + assert text_area.selected_text == "23456" + + result = text_area.replace("XX", start=(0, 2), end=(0, 5)) + assert result == EditResult( + replaced_text="234", + end_location=(0, 4), + ) + assert text_area.selected_text == "XX56" diff --git a/tests/text_area/test_edit_via_bindings.py b/tests/text_area/test_edit_via_bindings.py new file mode 100644 index 0000000000..aa99a63ad9 --- /dev/null +++ b/tests/text_area/test_edit_via_bindings.py @@ -0,0 +1,418 @@ +"""Tests some edits using the keyboard. + +All tests in this module should press keys on the keyboard which edit the document, +and check that the document content is updated as expected, as well as the cursor +location. + +Note that more extensive testing for editing is done at the Document level. +""" +import pytest + +from textual.app import App, ComposeResult +from textual.widgets import TextArea +from textual.widgets.text_area import Selection + +TEXT = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +""" + +SIMPLE_TEXT = """\ +ABCDE +FGHIJ +KLMNO +PQRST +UVWXY +Z""" + + +class TextAreaApp(App): + def compose(self) -> ComposeResult: + text_area = TextArea() + text_area.load_text(TEXT) + yield text_area + + +async def test_single_keypress_printable_character(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + await pilot.press("x") + assert text_area.text == "x" + TEXT + + +async def test_single_keypress_enter(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + await pilot.press("enter") + assert text_area.text == "\n" + TEXT + + +@pytest.mark.parametrize( + "content,cursor_column,cursor_destination", + [ + ("", 0, 4), + ("x", 0, 4), + ("x", 1, 4), + ("xxx", 3, 4), + ("xxxx", 4, 8), + ("xxxxx", 5, 8), + ("xxxxxx", 6, 8), + ("💩", 1, 3), + ("💩💩", 2, 6), + ], +) +async def test_tab_with_spaces_goes_to_tab_stop( + content, cursor_column, cursor_destination +): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.indent_width = 4 + text_area.load_text(content) + text_area.cursor_location = (0, cursor_column) + + await pilot.press("tab") + + assert text_area.cursor_location[1] == cursor_destination + + +async def test_delete_left(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("Hello, world!") + text_area.move_cursor((0, 6)) + await pilot.press("backspace") + assert text_area.text == "Hello world!" + assert text_area.selection == Selection.cursor((0, 5)) + + +async def test_delete_left_start(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("Hello, world!") + await pilot.press("backspace") + assert text_area.text == "Hello, world!" + assert text_area.selection == Selection.cursor((0, 0)) + + +async def test_delete_left_end(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("Hello, world!") + text_area.move_cursor((0, 13)) + await pilot.press("backspace") + assert text_area.text == "Hello, world" + assert text_area.selection == Selection.cursor((0, 12)) + + +@pytest.mark.parametrize( + "key,selection", + [ + ("delete", Selection((1, 2), (3, 4))), + ("delete", Selection((3, 4), (1, 2))), + ("backspace", Selection((1, 2), (3, 4))), + ("backspace", Selection((3, 4), (1, 2))), + ], +) +async def test_deletion_with_non_empty_selection(key, selection): + """When there's a selection, pressing backspace or delete should delete everything + that is selected and reset the selection to a cursor at the appropriate location.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text(SIMPLE_TEXT) + text_area.selection = selection + await pilot.press(key) + assert text_area.selection == Selection.cursor((1, 2)) + assert ( + text_area.text + == """\ +ABCDE +FGT +UVWXY +Z""" + ) + + +async def test_delete_right(): + """Pressing 'delete' deletes the character to the right of the cursor.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("Hello, world!") + text_area.move_cursor((0, 13)) + await pilot.press("delete") + assert text_area.text == "Hello, world!" + assert text_area.selection == Selection.cursor((0, 13)) + + +async def test_delete_right_end_of_line(): + """Pressing 'delete' at the end of the line merges this line with the line below.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("hello\nworld!") + end_of_line = text_area.get_cursor_line_end_location() + text_area.move_cursor(end_of_line) + await pilot.press("delete") + assert text_area.selection == Selection.cursor((0, 5)) + assert text_area.text == "helloworld!" + + +@pytest.mark.parametrize( + "selection,expected_result", + [ + (Selection.cursor((0, 0)), ""), + (Selection.cursor((0, 4)), ""), + (Selection.cursor((0, 10)), ""), + (Selection((0, 2), (0, 4)), ""), + (Selection((0, 4), (0, 2)), ""), + ], +) +async def test_delete_line(selection, expected_result): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + text_area.selection = selection + + await pilot.press("ctrl+x") + + assert text_area.selection == Selection.cursor((0, 0)) + assert text_area.text == expected_result + + +@pytest.mark.parametrize( + "selection,expected_result", + [ + # Cursors + (Selection.cursor((0, 0)), "345\n678\n9\n"), + (Selection.cursor((0, 2)), "345\n678\n9\n"), + (Selection.cursor((3, 1)), "012\n345\n678\n"), + (Selection.cursor((4, 0)), "012\n345\n678\n9\n"), + # Selections + (Selection((1, 1), (1, 2)), "012\n678\n9\n"), # non-empty single line selection + (Selection((1, 2), (2, 1)), "012\n9\n"), # delete lines selection touches + ( + Selection((1, 2), (3, 0)), + "012\n9\n", + ), # cursor at column 0 of line 3, should not be deleted! + ( + Selection((3, 0), (1, 2)), + "012\n9\n", + ), # opposite direction + (Selection((0, 0), (4, 0)), ""), # delete all lines + ], +) +async def test_delete_line_multiline_document(selection, expected_result): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("012\n345\n678\n9\n") + text_area.selection = selection + + await pilot.press("ctrl+x") + + cursor_row, _ = text_area.cursor_location + assert text_area.selection == Selection.cursor((cursor_row, 0)) + assert text_area.text == expected_result + + +@pytest.mark.parametrize( + "selection,expected_result", + [ + # Cursors + (Selection.cursor((0, 0)), ""), + (Selection.cursor((0, 5)), "01234"), + (Selection.cursor((0, 9)), "012345678"), + (Selection.cursor((0, 10)), "0123456789"), + # Selections + (Selection((0, 0), (0, 9)), "012345678"), + (Selection((0, 0), (0, 10)), "0123456789"), + (Selection((0, 2), (0, 5)), "01234"), + (Selection((0, 5), (0, 2)), "01"), + ], +) +async def test_delete_to_end_of_line(selection, expected_result): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + text_area.selection = selection + + await pilot.press("ctrl+k") + + assert text_area.selection == Selection.cursor(selection.end) + assert text_area.text == expected_result + + +@pytest.mark.parametrize( + "selection,expected_result", + [ + # Cursors + (Selection.cursor((0, 0)), "0123456789"), + (Selection.cursor((0, 5)), "56789"), + (Selection.cursor((0, 9)), "9"), + (Selection.cursor((0, 10)), ""), + # Selections + (Selection((0, 0), (0, 9)), "9"), + (Selection((0, 0), (0, 10)), ""), + (Selection((0, 2), (0, 5)), "56789"), + (Selection((0, 5), (0, 2)), "23456789"), + ], +) +async def test_delete_to_start_of_line(selection, expected_result): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("0123456789") + text_area.selection = selection + + await pilot.press("ctrl+u") + + assert text_area.selection == Selection.cursor((0, 0)) + assert text_area.text == expected_result + + +@pytest.mark.parametrize( + "selection,expected_result,final_selection", + [ + (Selection.cursor((0, 0)), " 012 345 6789", Selection.cursor((0, 0))), + (Selection.cursor((0, 4)), " 2 345 6789", Selection.cursor((0, 2))), + (Selection.cursor((0, 5)), " 345 6789", Selection.cursor((0, 2))), + ( + Selection.cursor((0, 6)), + " 345 6789", + Selection.cursor((0, 2)), + ), + (Selection.cursor((0, 14)), " 012 345 ", Selection.cursor((0, 10))), + # When there's a selection and you "delete word left", it just deletes the selection + (Selection((0, 4), (0, 11)), " 01789", Selection.cursor((0, 4))), + ], +) +async def test_delete_word_left(selection, expected_result, final_selection): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text(" 012 345 6789") + text_area.selection = selection + + await pilot.press("ctrl+w") + + assert text_area.text == expected_result + assert text_area.selection == final_selection + + +@pytest.mark.parametrize( + "selection,expected_result,final_selection", + [ + (Selection.cursor((0, 0)), "\t012 \t 345\t6789", Selection.cursor((0, 0))), + (Selection.cursor((0, 4)), "\t \t 345\t6789", Selection.cursor((0, 1))), + (Selection.cursor((0, 5)), "\t\t 345\t6789", Selection.cursor((0, 1))), + ( + Selection.cursor((0, 6)), + "\t 345\t6789", + Selection.cursor((0, 1)), + ), + (Selection.cursor((0, 15)), "\t012 \t 345\t", Selection.cursor((0, 11))), + # When there's a selection and you "delete word left", it just deletes the selection + (Selection((0, 4), (0, 11)), "\t0126789", Selection.cursor((0, 4))), + ], +) +async def test_delete_word_left_with_tabs(selection, expected_result, final_selection): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("\t012 \t 345\t6789") + text_area.selection = selection + + await pilot.press("ctrl+w") + + assert text_area.text == expected_result + assert text_area.selection == final_selection + + +async def test_delete_word_left_to_start_of_line(): + """If no word boundary found when we 'delete word left', then + the deletion happens to the start of the line.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("0123\n 456789") + text_area.selection = Selection.cursor((1, 3)) + + await pilot.press("ctrl+w") + + assert text_area.text == "0123\n456789" + assert text_area.selection == Selection.cursor((1, 0)) + + +async def test_delete_word_left_at_line_start(): + """If we're at the start of a line and we 'delete word left', the + line merges with the line above (if possible).""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("0123\n 456789") + text_area.selection = Selection.cursor((1, 0)) + + await pilot.press("ctrl+w") + + assert text_area.text == "0123 456789" + assert text_area.selection == Selection.cursor((0, 4)) + + +@pytest.mark.parametrize( + "selection,expected_result,final_selection", + [ + (Selection.cursor((0, 0)), "012 345 6789", Selection.cursor((0, 0))), + (Selection.cursor((0, 4)), " 01 345 6789", Selection.cursor((0, 4))), + (Selection.cursor((0, 5)), " 012345 6789", Selection.cursor((0, 5))), + (Selection.cursor((0, 14)), " 012 345 6789", Selection.cursor((0, 14))), + # When non-empty selection, "delete word right" just deletes the selection + (Selection((0, 4), (0, 11)), " 01789", Selection.cursor((0, 4))), + ], +) +async def test_delete_word_right(selection, expected_result, final_selection): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text(" 012 345 6789") + text_area.selection = selection + + await pilot.press("ctrl+f") + + assert text_area.text == expected_result + assert text_area.selection == final_selection + + +async def test_delete_word_right_delete_to_end_of_line(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("01234\n56789") + text_area.selection = Selection.cursor((0, 3)) + + await pilot.press("ctrl+f") + + assert text_area.text == "012\n56789" + assert text_area.selection == Selection.cursor((0, 3)) + + +async def test_delete_word_right_at_end_of_line(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("01234\n56789") + text_area.selection = Selection.cursor((0, 5)) + + await pilot.press("ctrl+f") + + assert text_area.text == "0123456789" + assert text_area.selection == Selection.cursor((0, 5)) diff --git a/tests/text_area/test_languages.py b/tests/text_area/test_languages.py new file mode 100644 index 0000000000..dc8a59300a --- /dev/null +++ b/tests/text_area/test_languages.py @@ -0,0 +1,97 @@ +import pytest + +from textual.app import App, ComposeResult +from textual.widgets import TextArea +from textual.widgets.text_area import LanguageDoesNotExist + + +class TextAreaApp(App): + def compose(self) -> ComposeResult: + yield TextArea("print('hello')", language="python") + + +async def test_setting_builtin_language_via_constructor(): + class MyTextAreaApp(App): + def compose(self) -> ComposeResult: + yield TextArea("print('hello')", language="python") + + app = MyTextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + assert text_area.language == "python" + + text_area.language = "markdown" + assert text_area.language == "markdown" + + +async def test_setting_builtin_language_via_attribute(): + class MyTextAreaApp(App): + def compose(self) -> ComposeResult: + text_area = TextArea("print('hello')") + text_area.language = "python" + yield text_area + + app = MyTextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + assert text_area.language == "python" + + text_area.language = "markdown" + assert text_area.language == "markdown" + + +async def test_setting_language_to_none(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.language = None + assert text_area.language is None + + +async def test_setting_unknown_language(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + + with pytest.raises(LanguageDoesNotExist): + text_area.language = "this-language-doesnt-exist" + + +async def test_register_language(): + app = TextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + + # Get the language from py-tree-sitter-languages... + from tree_sitter_languages import get_language + + language = get_language("elm") + + # ...and register it with no highlights + text_area.register_language(language, "") + + # Ensure that registered language is now available. + assert "elm" in text_area.available_languages + + # Switch to the newly registered language + text_area.language = "elm" + + assert text_area.language == "elm" + + +async def test_register_language_existing_language(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + + # Before registering the language, we have highlights as expected. + assert len(text_area._highlights) > 0 + + # Overwriting the highlight query for Python... + text_area.register_language("python", "") + + # We've overridden the highlight query with a blank one, so there are no highlights. + assert text_area._highlights == {} diff --git a/tests/text_area/test_selection.py b/tests/text_area/test_selection.py new file mode 100644 index 0000000000..d089aecc0f --- /dev/null +++ b/tests/text_area/test_selection.py @@ -0,0 +1,296 @@ +import pytest + +from textual.app import App, ComposeResult +from textual.widgets import TextArea +from textual.widgets.text_area import Selection + +TEXT = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +""" + + +class TextAreaApp(App): + def compose(self) -> ComposeResult: + text_area = TextArea() + text_area.load_text(TEXT) + yield text_area + + +def test_default_selection(): + """The cursor starts at (0, 0) in the document.""" + text_area = TextArea() + assert text_area.selection == Selection.cursor((0, 0)) + + +async def test_cursor_location_get(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((1, 1), (2, 2)) + assert text_area.cursor_location == (2, 2) + + +async def test_cursor_location_set(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + target = (1, 2) + text_area.cursor_location = target + assert text_area.selection == Selection.cursor(target) + + +async def test_cursor_location_set_while_selecting(): + """If you set the cursor_location while a selection is in progress, + the start/anchor point of the selection will remain where it is.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((0, 0), (0, 2)) + target = (1, 2) + text_area.cursor_location = target + assert text_area.selection == Selection((0, 0), target) + + +async def test_move_cursor_select(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((1, 1), (2, 2)) + text_area.move_cursor((2, 3), select=True) + assert text_area.selection == Selection((1, 1), (2, 3)) + + +async def test_move_cursor_relative(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + + text_area.move_cursor_relative(rows=1, columns=2) + assert text_area.selection == Selection.cursor((1, 2)) + + text_area.move_cursor_relative(rows=-1, columns=-2) + assert text_area.selection == Selection.cursor((0, 0)) + + text_area.move_cursor_relative(rows=1000, columns=1000) + assert text_area.selection == Selection.cursor((4, 0)) + + +async def test_selected_text_forward(): + """Selecting text from top to bottom results in the correct selected_text.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((0, 0), (2, 0)) + assert ( + text_area.selected_text + == """\ +I must not fear. +Fear is the mind-killer. +""" + ) + + +async def test_selected_text_backward(): + """Selecting text from bottom to top results in the correct selected_text.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((2, 0), (0, 0)) + assert ( + text_area.selected_text + == """\ +I must not fear. +Fear is the mind-killer. +""" + ) + + +async def test_selected_text_multibyte(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("こんにちは") + text_area.selection = Selection((0, 1), (0, 3)) + assert text_area.selected_text == "んに" + + +async def test_selection_clamp(): + """When you set the selection reactive, it's clamped to within the document bounds.""" + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.selection = Selection((99, 99), (100, 100)) + assert text_area.selection == Selection(start=(4, 0), end=(4, 0)) + + +@pytest.mark.parametrize( + "start,end", + [ + ((0, 0), (0, 0)), + ((0, 4), (0, 3)), + ((1, 0), (0, 16)), + ], +) +async def test_get_cursor_left_location(start, end): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor(start) + assert text_area.get_cursor_left_location() == end + + +@pytest.mark.parametrize( + "start,end", + [ + ((0, 0), (0, 1)), + ((0, 16), (1, 0)), + ((3, 20), (4, 0)), + ((4, 0), (4, 0)), + ], +) +async def test_get_cursor_right_location(start, end): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor(start) + assert text_area.get_cursor_right_location() == end + + +@pytest.mark.parametrize( + "start,end", + [ + ((0, 4), (0, 0)), # jump to start + ((1, 2), (0, 2)), # go to column above + ((2, 56), (1, 24)), # snap to end of row above + ], +) +async def test_get_cursor_up_location(start, end): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor(start) + # This is required otherwise the cursor will snap back to the + # last location navigated to (0, 0) + text_area.record_cursor_width() + assert text_area.get_cursor_up_location() == end + + +@pytest.mark.parametrize( + "start,end", + [ + ((3, 4), (4, 0)), # jump to end + ((1, 2), (2, 2)), # go to column above + ((2, 56), (3, 20)), # snap to end of row below + ], +) +async def test_get_cursor_down_location(start, end): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.move_cursor(start) + # This is required otherwise the cursor will snap back to the + # last location navigated to (0, 0) + text_area.record_cursor_width() + assert text_area.get_cursor_down_location() == end + + +@pytest.mark.parametrize( + "start,end", + [ + ((0, 0), (0, 0)), + ((0, 1), (0, 0)), + ((0, 2), (0, 0)), + ((0, 3), (0, 0)), + ((0, 4), (0, 3)), + ((0, 5), (0, 3)), + ((0, 6), (0, 3)), + ((0, 7), (0, 3)), + ((0, 10), (0, 7)), + ((1, 0), (0, 10)), + ((1, 2), (1, 0)), + ((1, 4), (1, 0)), + ((1, 7), (1, 4)), + ((1, 8), (1, 7)), + ((1, 13), (1, 11)), + ((1, 14), (1, 11)), + ], +) +async def test_cursor_word_left_location(start, end): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("AB CD EFG\n HI\tJK LM ") + text_area.move_cursor(start) + assert text_area.get_cursor_word_left_location() == end + + +@pytest.mark.parametrize( + "start,end", + [ + ((0, 0), (0, 2)), + ((0, 1), (0, 2)), + ((0, 2), (0, 5)), + ((0, 3), (0, 5)), + ((0, 4), (0, 5)), + ((0, 5), (0, 10)), + ((0, 6), (0, 10)), + ((0, 7), (0, 10)), + ((0, 10), (1, 0)), + ((1, 0), (1, 6)), + ((1, 2), (1, 6)), + ((1, 4), (1, 6)), + ((1, 7), (1, 9)), + ((1, 8), (1, 9)), + ((1, 13), (1, 14)), + ((1, 14), (1, 14)), + ], +) +async def test_cursor_word_right_location(start, end): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text("AB CD EFG\n HI\tJK LM ") + text_area.move_cursor(start) + assert text_area.get_cursor_word_right_location() == end + + +@pytest.mark.parametrize( + "content,expected_selection", + [ + ("123\n456\n789", Selection((0, 0), (2, 3))), + ("123\n456\n789\n", Selection((0, 0), (3, 0))), + ("", Selection((0, 0), (0, 0))), + ], +) +async def test_select_all(content, expected_selection): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text(content) + + text_area.select_all() + + assert text_area.selection == expected_selection + + +@pytest.mark.parametrize( + "index,content,expected_selection", + [ + (1, "123\n456\n789\n", Selection((1, 0), (1, 3))), + (2, "123\n456\n789\n", Selection((2, 0), (2, 3))), + (3, "123\n456\n789\n", Selection((3, 0), (3, 0))), + (1000, "123\n456\n789\n", Selection.cursor((0, 0))), + (0, "", Selection((0, 0), (0, 0))), + ], +) +async def test_select_line(index, content, expected_selection): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.load_text(content) + + text_area.select_line(index) + + assert text_area.selection == expected_selection diff --git a/tests/text_area/test_selection_bindings.py b/tests/text_area/test_selection_bindings.py new file mode 100644 index 0000000000..76d4586df4 --- /dev/null +++ b/tests/text_area/test_selection_bindings.py @@ -0,0 +1,318 @@ +import pytest + +from textual.app import App, ComposeResult +from textual.geometry import Offset +from textual.widgets import TextArea +from textual.widgets.text_area import Document, Selection + +TEXT = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +""" + + +class TextAreaApp(App): + def compose(self) -> ComposeResult: + text_area = TextArea() + text_area.load_text(TEXT) + yield text_area + + +async def test_mouse_click(): + """When you click the TextArea, the cursor moves to the expected location.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + await pilot.click(TextArea, Offset(x=5, y=2)) + assert text_area.selection == Selection.cursor((2, 2)) + + +async def test_mouse_click_clamp_from_right(): + """When you click to the right of the document bounds, the cursor is clamped + to within the document bounds.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + await pilot.click(TextArea, Offset(x=8, y=20)) + assert text_area.selection == Selection.cursor((4, 0)) + + +async def test_mouse_click_gutter_clamp(): + """When you click the gutter, it selects the start of the line.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + await pilot.click(TextArea, Offset(x=0, y=3)) + assert text_area.selection == Selection.cursor((3, 0)) + + +async def test_cursor_movement_basic(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("01234567\n012345\n0123456789") + + await pilot.press("right") + assert text_area.selection == Selection.cursor((0, 1)) + + await pilot.press("down") + assert text_area.selection == Selection.cursor((1, 1)) + + await pilot.press("left") + assert text_area.selection == Selection.cursor((1, 0)) + + await pilot.press("up") + assert text_area.selection == Selection.cursor((0, 0)) + + +async def test_cursor_selection_right(): + """When you press shift+right the selection is updated correctly.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + await pilot.press(*["shift+right"] * 3) + assert text_area.selection == Selection((0, 0), (0, 3)) + + +async def test_cursor_selection_right_to_previous_line(): + """When you press shift+right resulting in the cursor moving to the next line, + the selection is updated correctly.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.selection = Selection.cursor((0, 15)) + await pilot.press(*["shift+right"] * 4) + assert text_area.selection == Selection((0, 15), (1, 2)) + + +async def test_cursor_selection_left(): + """When you press shift+left the selection is updated correctly.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.selection = Selection.cursor((2, 5)) + await pilot.press(*["shift+left"] * 3) + assert text_area.selection == Selection((2, 5), (2, 2)) + + +async def test_cursor_selection_left_to_previous_line(): + """When you press shift+left resulting in the cursor moving back to the previous line, + the selection is updated correctly.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.selection = Selection.cursor((2, 2)) + await pilot.press(*["shift+left"] * 3) + + # The cursor jumps up to the end of the line above. + end_of_previous_line = len(TEXT.splitlines()[1]) + assert text_area.selection == Selection((2, 2), (1, end_of_previous_line)) + + +async def test_cursor_selection_up(): + """When you press shift+up the selection is updated correctly.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.move_cursor((2, 3)) + + await pilot.press("shift+up") + assert text_area.selection == Selection((2, 3), (1, 3)) + + +async def test_cursor_selection_up_when_cursor_on_first_line(): + """When you press shift+up the on the first line, it selects to the start.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.move_cursor((0, 4)) + + await pilot.press("shift+up") + assert text_area.selection == Selection((0, 4), (0, 0)) + await pilot.press("shift+up") + assert text_area.selection == Selection((0, 4), (0, 0)) + + +async def test_cursor_selection_down(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.move_cursor((2, 5)) + + await pilot.press("shift+down") + assert text_area.selection == Selection((2, 5), (3, 5)) + + +async def test_cursor_selection_down_when_cursor_on_last_line(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("ABCDEF\nGHIJK") + text_area.move_cursor((1, 2)) + + await pilot.press("shift+down") + assert text_area.selection == Selection((1, 2), (1, 5)) + await pilot.press("shift+down") + assert text_area.selection == Selection((1, 2), (1, 5)) + + +async def test_cursor_word_right(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("ABC DEF\nGHIJK") + + await pilot.press("ctrl+right") + + assert text_area.selection == Selection.cursor((0, 3)) + + +async def test_cursor_word_right_select(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("ABC DEF\nGHIJK") + + await pilot.press("ctrl+shift+right") + + assert text_area.selection == Selection((0, 0), (0, 3)) + + +async def test_cursor_word_left(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("ABC DEF\nGHIJK") + text_area.move_cursor((0, 7)) + + await pilot.press("ctrl+left") + + assert text_area.selection == Selection.cursor((0, 4)) + + +async def test_cursor_word_left_select(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("ABC DEF\nGHIJK") + text_area.move_cursor((0, 7)) + + await pilot.press("ctrl+shift+left") + + assert text_area.selection == Selection((0, 7), (0, 4)) + + +@pytest.mark.parametrize("key", ["end", "ctrl+e"]) +async def test_cursor_to_line_end(key): + """You can use the keyboard to jump the cursor to the end of the current line.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.selection = Selection.cursor((2, 2)) + await pilot.press(key) + eol_index = len(TEXT.splitlines()[2]) + assert text_area.cursor_location == (2, eol_index) + assert text_area.selection.is_empty + + +@pytest.mark.parametrize("key", ["home", "ctrl+a"]) +async def test_cursor_to_line_home_basic_behaviour(key): + """You can use the keyboard to jump the cursor to the start of the current line.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.selection = Selection.cursor((2, 2)) + await pilot.press(key) + assert text_area.cursor_location == (2, 0) + assert text_area.selection.is_empty + + +@pytest.mark.parametrize( + "cursor_start,cursor_destination", + [ + ((0, 0), (0, 4)), + ((0, 2), (0, 0)), + ((0, 4), (0, 0)), + ((0, 5), (0, 4)), + ((0, 9), (0, 4)), + ((0, 15), (0, 4)), + ], +) +async def test_cursor_line_home_smart_home(cursor_start, cursor_destination): + """If the line begins with whitespace, pressing home firstly goes + to the start of the (non-whitespace) content. Pressing it again takes you to column + 0. If you press it again, it goes back to the first non-whitespace column.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text(" hello world") + text_area.move_cursor(cursor_start) + await pilot.press("home") + assert text_area.selection == Selection.cursor(cursor_destination) + + +async def test_cursor_page_down(): + """Pagedown moves the cursor down 1 page, retaining column index.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("XXX\n" * 200) + text_area.selection = Selection.cursor((0, 1)) + await pilot.press("pagedown") + assert text_area.selection == Selection.cursor((app.console.height - 1, 1)) + + +async def test_cursor_page_up(): + """Pageup moves the cursor up 1 page, retaining column index.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_text("XXX\n" * 200) + text_area.selection = Selection.cursor((100, 1)) + await pilot.press("pageup") + assert text_area.selection == Selection.cursor( + (100 - app.console.height + 1, 1) + ) + + +async def test_cursor_vertical_movement_visual_alignment_snapping(): + """When you move the cursor vertically, it should stay vertically + aligned even when double-width characters are used.""" + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.load_document(Document("こんにちは\n012345")) + text_area.move_cursor((1, 3), record_width=True) + + # The '3' is aligned with ん at (0, 1) + # こんにちは + # 012345 + # Pressing `up` takes us from (1, 3) to (0, 1) because record_width=True. + await pilot.press("up") + assert text_area.selection == Selection.cursor((0, 1)) + + # Pressing `down` takes us from (0, 1) to (1, 3) + await pilot.press("down") + assert text_area.selection == Selection.cursor((1, 3)) + + +async def test_select_line_binding(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + text_area.move_cursor((2, 2)) + + await pilot.press("f6") + + assert text_area.selection == Selection((2, 0), (2, 56)) + + +async def test_select_all_binding(): + app = TextAreaApp() + async with app.run_test() as pilot: + text_area = app.query_one(TextArea) + + await pilot.press("f7") + + assert text_area.selection == Selection((0, 0), (4, 0)) diff --git a/tests/text_area/test_setting_themes.py b/tests/text_area/test_setting_themes.py new file mode 100644 index 0000000000..8d165a98a9 --- /dev/null +++ b/tests/text_area/test_setting_themes.py @@ -0,0 +1,67 @@ +import pytest + +from textual._text_area_theme import TextAreaTheme +from textual.app import App, ComposeResult +from textual.widgets import TextArea +from textual.widgets._text_area import ThemeDoesNotExist + + +class TextAreaApp(App): + def compose(self) -> ComposeResult: + yield TextArea("print('hello')", language="python") + + +async def test_default_theme(): + app = TextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + assert text_area.theme is None + + +async def test_setting_builtin_themes(): + class MyTextAreaApp(App): + def compose(self) -> ComposeResult: + yield TextArea("print('hello')", language="python", theme="vscode_dark") + + app = MyTextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + assert text_area.theme == "vscode_dark" + + text_area.theme = "monokai" + assert text_area.theme == "monokai" + + +async def test_setting_theme_to_none(): + app = TextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.theme = None + assert text_area.theme is None + # When theme is None, we use the default theme. + assert text_area._theme.name == TextAreaTheme.default().name + + +async def test_setting_unknown_theme_raises_exception(): + app = TextAreaApp() + async with app.run_test(): + text_area = app.query_one(TextArea) + with pytest.raises(ThemeDoesNotExist): + text_area.theme = "this-theme-doesnt-exist" + + +async def test_registering_and_setting_theme(): + app = TextAreaApp() + + async with app.run_test(): + text_area = app.query_one(TextArea) + text_area.register_theme(TextAreaTheme("my-theme")) + + assert "my-theme" in text_area.available_themes + + text_area.theme = "my-theme" + + assert text_area.theme == "my-theme" diff --git a/tests/text_area/test_text_area_theme.py b/tests/text_area/test_text_area_theme.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tree-sitter/highlights/bash.scm b/tree-sitter/highlights/bash.scm new file mode 100644 index 0000000000..23bf03e697 --- /dev/null +++ b/tree-sitter/highlights/bash.scm @@ -0,0 +1,145 @@ +(simple_expansion) @none +(expansion + "${" @punctuation.special + "}" @punctuation.special) @none +[ + "(" + ")" + "((" + "))" + "{" + "}" + "[" + "]" + "[[" + "]]" + ] @punctuation.bracket + +[ + ";" + ";;" + (heredoc_start) + ] @punctuation.delimiter + +[ + "$" +] @punctuation.special + +[ + ">" + ">>" + "<" + "<<" + "&" + "&&" + "|" + "||" + "=" + "=~" + "==" + "!=" + ] @operator + +[ + (string) + (raw_string) + (ansi_c_string) + (heredoc_body) +] @string @spell + +(variable_assignment (word) @string) + +[ + "if" + "then" + "else" + "elif" + "fi" + "case" + "in" + "esac" + ] @conditional + +[ + "for" + "do" + "done" + "select" + "until" + "while" + ] @repeat + +[ + "declare" + "export" + "local" + "readonly" + "unset" + ] @keyword + +"function" @keyword.function + +(special_variable_name) @constant + +; trap -l +((word) @constant.builtin + (#match? @constant.builtin "^SIG(HUP|INT|QUIT|ILL|TRAP|ABRT|BUS|FPE|KILL|USR[12]|SEGV|PIPE|ALRM|TERM|STKFLT|CHLD|CONT|STOP|TSTP|TT(IN|OU)|URG|XCPU|XFSZ|VTALRM|PROF|WINCH|IO|PWR|SYS|RTMIN([+]([1-9]|1[0-5]))?|RTMAX(-([1-9]|1[0-4]))?)$")) + +((word) @boolean + (#any-of? @boolean "true" "false")) + +(comment) @comment @spell +(test_operator) @string + +(command_substitution + [ "$(" ")" ] @punctuation.bracket) + +(process_substitution + [ "<(" ")" ] @punctuation.bracket) + + +(function_definition + name: (word) @function) + +(command_name (word) @function.call) + +((command_name (word) @function.builtin) + (#any-of? @function.builtin + "alias" "bg" "bind" "break" "builtin" "caller" "cd" + "command" "compgen" "complete" "compopt" "continue" + "coproc" "dirs" "disown" "echo" "enable" "eval" + "exec" "exit" "fc" "fg" "getopts" "hash" "help" + "history" "jobs" "kill" "let" "logout" "mapfile" + "popd" "printf" "pushd" "pwd" "read" "readarray" + "return" "set" "shift" "shopt" "source" "suspend" + "test" "time" "times" "trap" "type" "typeset" + "ulimit" "umask" "unalias" "wait")) + +(command + argument: [ + (word) @parameter + (concatenation (word) @parameter) + ]) + +((word) @number + (#lua-match? @number "^[0-9]+$")) + +(file_redirect + descriptor: (file_descriptor) @operator + destination: (word) @parameter) + +(expansion + [ "${" "}" ] @punctuation.bracket) + +(variable_name) @variable + +((variable_name) @constant + (#lua-match? @constant "^[A-Z][A-Z_0-9]*$")) + +(case_item + value: (word) @parameter) + +(regex) @string.regex + +((program . (comment) @preproc) + (#lua-match? @preproc "^#!/")) diff --git a/tree-sitter/highlights/css.scm b/tree-sitter/highlights/css.scm new file mode 100644 index 0000000000..b26f0ec96c --- /dev/null +++ b/tree-sitter/highlights/css.scm @@ -0,0 +1,91 @@ +[ + "@media" + "@charset" + "@namespace" + "@supports" + "@keyframes" + (at_keyword) + (to) + (from) + ] @keyword + +"@import" @include + +(comment) @comment @spell + +[ + (tag_name) + (nesting_selector) + (universal_selector) + ] @type + +(function_name) @function + +[ + "~" + ">" + "+" + "-" + "*" + "/" + "=" + "^=" + "|=" + "~=" + "$=" + "*=" + "and" + "or" + "not" + "only" + ] @operator + +(important) @type.qualifier + +(attribute_selector (plain_value) @string) +(pseudo_element_selector "::" (tag_name) @property) +(pseudo_class_selector (class_name) @property) + +[ + (class_name) + (id_name) + (property_name) + (feature_name) + (attribute_name) + ] @property + +(namespace_name) @namespace + +((property_name) @type.definition + (#lua-match? @type.definition "^[-][-]")) +((plain_value) @type + (#lua-match? @type "^[-][-]")) + +[ + (string_value) + (color_value) + (unit) + ] @string + +[ + (integer_value) + (float_value) + ] @number + +[ + "#" + "," + "." + ":" + "::" + ";" + ] @punctuation.delimiter + +[ + "{" + ")" + "(" + "}" + ] @punctuation.bracket + +(ERROR) @error diff --git a/tree-sitter/highlights/html.scm b/tree-sitter/highlights/html.scm new file mode 100644 index 0000000000..15f2adb436 --- /dev/null +++ b/tree-sitter/highlights/html.scm @@ -0,0 +1,64 @@ +(tag_name) @tag +(erroneous_end_tag_name) @html.end_tag_error +(comment) @comment +(attribute_name) @tag.attribute +(attribute + (quoted_attribute_value) @string) +(text) @text @spell + +((element (start_tag (tag_name) @_tag) (text) @text.title) + (#eq? @_tag "title")) + +((element (start_tag (tag_name) @_tag) (text) @text.title.1) + (#eq? @_tag "h1")) + +((element (start_tag (tag_name) @_tag) (text) @text.title.2) + (#eq? @_tag "h2")) + +((element (start_tag (tag_name) @_tag) (text) @text.title.3) + (#eq? @_tag "h3")) + +((element (start_tag (tag_name) @_tag) (text) @text.title.4) + (#eq? @_tag "h4")) + +((element (start_tag (tag_name) @_tag) (text) @text.title.5) + (#eq? @_tag "h5")) + +((element (start_tag (tag_name) @_tag) (text) @text.title.6) + (#eq? @_tag "h6")) + +((element (start_tag (tag_name) @_tag) (text) @text.strong) + (#any-of? @_tag "strong" "b")) + +((element (start_tag (tag_name) @_tag) (text) @text.emphasis) + (#any-of? @_tag "em" "i")) + +((element (start_tag (tag_name) @_tag) (text) @text.strike) + (#any-of? @_tag "s" "del")) + +((element (start_tag (tag_name) @_tag) (text) @text.underline) + (#eq? @_tag "u")) + +((element (start_tag (tag_name) @_tag) (text) @text.literal) + (#any-of? @_tag "code" "kbd")) + +((element (start_tag (tag_name) @_tag) (text) @text.uri) + (#eq? @_tag "a")) + +((attribute + (attribute_name) @_attr + (quoted_attribute_value (attribute_value) @text.uri)) + (#any-of? @_attr "href" "src")) + +[ + "<" + ">" + "" + "/>" +] @tag.delimiter + +"=" @operator + +(doctype) @constant + +"" + "=" + "==" + ">" + ">=" + ">>" + ">>=" + "@" + "@=" + "|" + "|=" + "~" + "->" +] @operator + +; Keywords +[ + "and" + "in" + "is" + "not" + "or" + "del" +] @keyword.operator + +[ + "def" + "lambda" +] @keyword.function + +[ + "assert" + "async" + "await" + "class" + "exec" + "global" + "nonlocal" + "pass" + "print" + "with" + "as" +] @keyword + +[ + "return" + "yield" +] @keyword.return +(yield "from" @keyword.return) + +(future_import_statement + "from" @include + "__future__" @constant.builtin) +(import_from_statement "from" @include) +"import" @include + +(aliased_import "as" @include) + +["if" "elif" "else" "match" "case"] @conditional + +["for" "while" "break" "continue"] @repeat + +[ + "try" + "except" + "raise" + "finally" +] @exception + +(raise_statement "from" @exception) + +(try_statement + (else_clause + "else" @exception)) + +["(" ")" "[" "]" "{" "}"] @punctuation.bracket + +(interpolation + "{" @punctuation.special + "}" @punctuation.special) + +["," "." ":" ";" (ellipsis)] @punctuation.delimiter + +;; Class definitions + +(class_definition name: (identifier) @type.class) + +(class_definition + body: (block + (function_definition + name: (identifier) @method))) + +(class_definition + superclasses: (argument_list + (identifier) @type)) + +((class_definition + body: (block + (expression_statement + (assignment + left: (identifier) @field)))) + (#match? @field "^([A-Z])@!.*$")) +((class_definition + body: (block + (expression_statement + (assignment + left: (_ + (identifier) @field))))) + (#match? @field "^([A-Z])@!.*$")) + +((class_definition + (block + (function_definition + name: (identifier) @constructor))) + (#any-of? @constructor "__new__" "__init__")) + +;; Error +(ERROR) @error diff --git a/tree-sitter/highlights/regex.scm b/tree-sitter/highlights/regex.scm new file mode 100644 index 0000000000..7c671c2c04 --- /dev/null +++ b/tree-sitter/highlights/regex.scm @@ -0,0 +1,34 @@ +;; Forked from tree-sitter-regex +;; The MIT License (MIT) Copyright (c) 2014 Max Brunsfeld +[ + "(" + ")" + "(?" + "(?:" + "(?<" + ">" + "[" + "]" + "{" + "}" +] @regex.punctuation.bracket + +(group_name) @property + +;; These are escaped special characters that lost their special meaning +;; -> no special highlighting +(identity_escape) @string.regex + +(class_character) @constant + +[ + (control_letter_escape) + (character_class_escape) + (control_escape) + (start_assertion) + (end_assertion) + (boundary_assertion) + (non_boundary_assertion) +] @string.escape + +[ "*" "+" "?" "|" "=" "!" ] @regex.operator diff --git a/tree-sitter/highlights/sql.scm b/tree-sitter/highlights/sql.scm new file mode 100644 index 0000000000..03a15fe381 --- /dev/null +++ b/tree-sitter/highlights/sql.scm @@ -0,0 +1,114 @@ +(string) @string +(number) @number +(comment) @comment + +(function_call + function: (identifier) @function) + +[ + (NULL) + (TRUE) + (FALSE) +] @constant.builtin + +([ + (type_cast + (type (identifier) @type.builtin)) + (create_function_statement + (type (identifier) @type.builtin)) + (create_function_statement + (create_function_parameters + (create_function_parameter (type (identifier) @type.builtin)))) + (create_type_statement + (type_spec_composite (type (identifier) @type.builtin))) + (create_table_statement + (table_parameters + (table_column (type (identifier) @type.builtin)))) + ] + (#match? + @type.builtin + "^(bigint|BIGINT|int8|INT8|bigserial|BIGSERIAL|serial8|SERIAL8|bit|BIT|varbit|VARBIT|boolean|BOOLEAN|bool|BOOL|box|BOX|bytea|BYTEA|character|CHARACTER|char|CHAR|varchar|VARCHAR|cidr|CIDR|circle|CIRCLE|date|DATE|float8|FLOAT8|inet|INET|integer|INTEGER|int|INT|int4|INT4|interval|INTERVAL|json|JSON|jsonb|JSONB|line|LINE|lseg|LSEG|macaddr|MACADDR|money|MONEY|numeric|NUMERIC|decimal|DECIMAL|path|PATH|pg_lsn|PG_LSN|point|POINT|polygon|POLYGON|real|REAL|float4|FLOAT4|smallint|SMALLINT|int2|INT2|smallserial|SMALLSERIAL|serial2|SERIAL2|serial|SERIAL|serial4|SERIAL4|text|TEXT|time|TIME|time|TIME|timestamp|TIMESTAMP|tsquery|TSQUERY|tsvector|TSVECTOR|txid_snapshot|TXID_SNAPSHOT|enum|ENUM|range|RANGE)$")) + +(identifier) @variable + +[ + "::" + "<" + "<=" + "<>" + "=" + ">" + ">=" +] @operator + +[ + "(" + ")" + "[" + "]" +] @punctuation.bracket + +[ + ";" + "." +] @punctuation.delimiter + +[ + (type) + (array_type) +] @type + +[ + (primary_key_constraint) + (unique_constraint) + (null_constraint) +] @keyword + +[ + "AND" + "AS" + "AUTO_INCREMENT" + "CREATE" + "CREATE_DOMAIN" + "CREATE_OR_REPLACE_FUNCTION" + "CREATE_SCHEMA" + "TABLE" + "TEMPORARY" + "CREATE_TYPE" + "DATABASE" + "FROM" + "GRANT" + "GROUP_BY" + "IF_NOT_EXISTS" + "INDEX" + "INNER" + "INSERT" + "INTO" + "IN" + "JOIN" + "LANGUAGE" + "LEFT" + "LOCAL" + "NOT" + "ON" + "OR" + "ORDER_BY" + "OUTER" + "PRIMARY_KEY" + "PUBLIC" + "RETURNS" + "SCHEMA" + "SELECT" + "SESSION" + "SET" + "TABLE" + "TIME_ZONE" + "TO" + "UNIQUE" + "UPDATE" + "USAGE" + "VALUES" + "WHERE" + "WITH" + "WITHOUT" +] @keyword diff --git a/tree-sitter/highlights/toml.scm b/tree-sitter/highlights/toml.scm new file mode 100644 index 0000000000..9228d28072 --- /dev/null +++ b/tree-sitter/highlights/toml.scm @@ -0,0 +1,36 @@ +; Properties +;----------- + +(bare_key) @toml.type +(quoted_key) @string +(pair (bare_key)) @property + +; Literals +;--------- + +(boolean) @boolean +(comment) @comment @spell +(string) @string +(integer) @number +(float) @float +(offset_date_time) @toml.datetime +(local_date_time) @toml.datetime +(local_date) @toml.datetime +(local_time) @toml.datetime + +; Punctuation +;------------ + +"." @punctuation.delimiter +"," @punctuation.delimiter + +"=" @toml.operator + +"[" @punctuation.bracket +"]" @punctuation.bracket +"[[" @punctuation.bracket +"]]" @punctuation.bracket +"{" @punctuation.bracket +"}" @punctuation.bracket + +(ERROR) @toml.error diff --git a/tree-sitter/highlights/yaml.scm b/tree-sitter/highlights/yaml.scm new file mode 100644 index 0000000000..a57f464dfc --- /dev/null +++ b/tree-sitter/highlights/yaml.scm @@ -0,0 +1,53 @@ +(boolean_scalar) @boolean +(null_scalar) @constant.builtin +(double_quote_scalar) @string +(single_quote_scalar) @string +((block_scalar) @string (#set! "priority" 99)) +(string_scalar) @string +(escape_sequence) @string.escape +(integer_scalar) @number +(float_scalar) @number +(comment) @comment +(anchor_name) @type +(alias_name) @type +(tag) @type +(ERROR) @error + +[ + (yaml_directive) + (tag_directive) + (reserved_directive) +] @preproc + +(block_mapping_pair + key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @yaml.field)) +(block_mapping_pair + key: (flow_node (plain_scalar (string_scalar) @yaml.field))) + +(flow_mapping + (_ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @yaml.field))) +(flow_mapping + (_ key: (flow_node (plain_scalar (string_scalar) @yaml.field)))) + +[ + "," + "-" + ":" + ">" + "?" + "|" +] @punctuation.delimiter + +[ + "[" + "]" + "{" + "}" +] @punctuation.bracket + +[ + "*" + "&" + "---" + "..." +] @punctuation.special
This is a paragraph to test the HTML structure.