diff --git a/docs/api/scroll_view.md b/docs/api/scroll_view.md
new file mode 100644
index 000000000..461d9de6f
--- /dev/null
+++ b/docs/api/scroll_view.md
@@ -0,0 +1 @@
+::: textual.scroll_view.ScrollView
diff --git a/docs/api/strip.md b/docs/api/strip.md
new file mode 100644
index 000000000..44051a14a
--- /dev/null
+++ b/docs/api/strip.md
@@ -0,0 +1 @@
+::: textual.strip.Strip
diff --git a/docs/examples/guide/widgets/checker01.py b/docs/examples/guide/widgets/checker01.py
new file mode 100644
index 000000000..66b6a0963
--- /dev/null
+++ b/docs/examples/guide/widgets/checker01.py
@@ -0,0 +1,43 @@
+from rich.segment import Segment
+from rich.style import Style
+
+from textual.app import App, ComposeResult
+from textual.strip import Strip
+from textual.widget import Widget
+
+
+class CheckerBoard(Widget):
+ """Render an 8x8 checkerboard."""
+
+ def render_line(self, y: int) -> Strip:
+ """Render a line of the widget. y is relative to the top of the widget."""
+
+ row_index = y // 4 # A checkerboard square consists of 4 rows
+
+ if row_index >= 8: # Generate blank lines when we reach the end
+ return Strip.blank(self.size.width)
+
+ is_odd = row_index % 2 # Used to alternate the starting square on each row
+
+ white = Style.parse("on white") # Get a style object for a white background
+ black = Style.parse("on black") # Get a style object for a black background
+
+ # Generate a list of segments with alternating black and white space characters
+ segments = [
+ Segment(" " * 8, black if (column + is_odd) % 2 else white)
+ for column in range(8)
+ ]
+ strip = Strip(segments, 8 * 8)
+ return strip
+
+
+class BoardApp(App):
+ """A simple app to show our widget."""
+
+ def compose(self) -> ComposeResult:
+ yield CheckerBoard()
+
+
+if __name__ == "__main__":
+ app = BoardApp()
+ app.run()
diff --git a/docs/examples/guide/widgets/checker02.py b/docs/examples/guide/widgets/checker02.py
new file mode 100644
index 000000000..b498ef204
--- /dev/null
+++ b/docs/examples/guide/widgets/checker02.py
@@ -0,0 +1,55 @@
+from rich.segment import Segment
+
+from textual.app import App, ComposeResult
+from textual.strip import Strip
+from textual.widget import Widget
+
+
+class CheckerBoard(Widget):
+ """Render an 8x8 checkerboard."""
+
+ COMPONENT_CLASSES = {
+ "checkerboard--white-square",
+ "checkerboard--black-square",
+ }
+
+ DEFAULT_CSS = """
+ CheckerBoard .checkerboard--white-square {
+ background: #A5BAC9;
+ }
+ CheckerBoard .checkerboard--black-square {
+ background: #004578;
+ }
+ """
+
+ def render_line(self, y: int) -> Strip:
+ """Render a line of the widget. y is relative to the top of the widget."""
+
+ row_index = y // 4 # four lines per row
+
+ if row_index >= 8:
+ return Strip.blank(self.size.width)
+
+ is_odd = row_index % 2
+
+ white = self.get_component_rich_style("checkerboard--white-square")
+ black = self.get_component_rich_style("checkerboard--black-square")
+
+ segments = [
+ Segment(" " * 8, black if (column + is_odd) % 2 else white)
+ for column in range(8)
+ ]
+ strip = Strip(segments, 8 * 8)
+ return strip
+
+
+class BoardApp(App):
+ """A simple app to show our widget."""
+
+ def compose(self) -> ComposeResult:
+ yield CheckerBoard()
+
+
+if __name__ == "__main__":
+ app = BoardApp()
+ app.run()
diff --git a/docs/examples/guide/widgets/checker03.py b/docs/examples/guide/widgets/checker03.py
new file mode 100644
index 000000000..03ca19381
--- /dev/null
+++ b/docs/examples/guide/widgets/checker03.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+from textual.app import App, ComposeResult
+from textual.geometry import Size
+from textual.strip import Strip
+from textual.scroll_view import ScrollView
+
+from rich.segment import Segment
+
+
+class CheckerBoard(ScrollView):
+ COMPONENT_CLASSES = {
+ "checkerboard--white-square",
+ "checkerboard--black-square",
+ }
+
+ DEFAULT_CSS = """
+ CheckerBoard .checkerboard--white-square {
+ background: #A5BAC9;
+ }
+ CheckerBoard .checkerboard--black-square {
+ background: #004578;
+ }
+ """
+
+ def __init__(self, board_size: int) -> None:
+ super().__init__()
+ self.board_size = board_size
+ # Each square is 4 rows and 8 columns
+ self.virtual_size = Size(board_size * 8, board_size * 4)
+
+ def render_line(self, y: int) -> Strip:
+ """Render a line of the widget. y is relative to the top of the widget."""
+
+ scroll_x, scroll_y = self.scroll_offset # The current scroll position
+ y += scroll_y # The line at the top of the widget is now `scroll_y`, not zero!
+ row_index = y // 4 # four lines per row
+
+ white = self.get_component_rich_style("checkerboard--white-square")
+ black = self.get_component_rich_style("checkerboard--black-square")
+
+ if row_index >= self.board_size:
+ return Strip.blank(self.size.width)
+
+ is_odd = row_index % 2
+
+ segments = [
+ Segment(" " * 8, black if (column + is_odd) % 2 else white)
+ for column in range(self.board_size)
+ ]
+ strip = Strip(segments, self.board_size * 8)
+ # Crop the strip so that is covers the visible area
+ strip = strip.crop(scroll_x, scroll_x + self.size.width)
+ return strip
+
+
+class BoardApp(App):
+ def compose(self) -> ComposeResult:
+ yield CheckerBoard(100)
+
+
+if __name__ == "__main__":
+ app = BoardApp()
+ app.run()
diff --git a/docs/examples/guide/widgets/checker04.py b/docs/examples/guide/widgets/checker04.py
new file mode 100644
index 000000000..1243a3e6d
--- /dev/null
+++ b/docs/examples/guide/widgets/checker04.py
@@ -0,0 +1,106 @@
+from __future__ import annotations
+
+from textual import events
+from textual.app import App, ComposeResult
+from textual.geometry import Offset, Region, Size
+from textual.reactive import var
+from textual.strip import Strip
+from textual.scroll_view import ScrollView
+
+from rich.segment import Segment
+from rich.style import Style
+
+
+class CheckerBoard(ScrollView):
+ COMPONENT_CLASSES = {
+ "checkerboard--white-square",
+ "checkerboard--black-square",
+ "checkerboard--cursor-square",
+ }
+
+ DEFAULT_CSS = """
+ CheckerBoard > .checkerboard--white-square {
+ background: #A5BAC9;
+ }
+ CheckerBoard > .checkerboard--black-square {
+ background: #004578;
+ }
+ CheckerBoard > .checkerboard--cursor-square {
+ background: darkred;
+ }
+ """
+
+ cursor_square = var(Offset(0, 0))
+
+ def __init__(self, board_size: int) -> None:
+ super().__init__()
+ self.board_size = board_size
+ # Each square is 4 rows and 8 columns
+ self.virtual_size = Size(board_size * 8, board_size * 4)
+
+ def on_mouse_move(self, event: events.MouseMove) -> None:
+ """Called when the user moves the mouse over the widget."""
+ mouse_position = event.offset + self.scroll_offset
+ self.cursor_square = Offset(mouse_position.x // 8, mouse_position.y // 4)
+
+ def watch_cursor_square(
+ self, previous_square: Offset, cursor_square: Offset
+ ) -> None:
+ """Called when the cursor square changes."""
+
+ def get_square_region(square_offset: Offset) -> Region:
+ """Get region relative to widget from square coordinate."""
+ x, y = square_offset
+ region = Region(x * 8, y * 4, 8, 4)
+ # Move the region in to the widgets frame of reference
+ region = region.translate(-self.scroll_offset)
+ return region
+
+ # Refresh the previous cursor square
+ self.refresh(get_square_region(previous_square))
+
+ # Refresh the new cursor square
+ self.refresh(get_square_region(cursor_square))
+
+ def render_line(self, y: int) -> Strip:
+ """Render a line of the widget. y is relative to the top of the widget."""
+
+ scroll_x, scroll_y = self.scroll_offset # The current scroll position
+ y += scroll_y # The line at the top of the widget is now `scroll_y`, not zero!
+ row_index = y // 4 # four lines per row
+
+ white = self.get_component_rich_style("checkerboard--white-square")
+ black = self.get_component_rich_style("checkerboard--black-square")
+ cursor = self.get_component_rich_style("checkerboard--cursor-square")
+
+ if row_index >= self.board_size:
+ return Strip.blank(self.size.width)
+
+ is_odd = row_index % 2
+
+ def get_square_style(column: int, row: int) -> Style:
+ """Get the cursor style at the given position on the checkerboard."""
+ if self.cursor_square == Offset(column, row):
+ square_style = cursor
+ else:
+ square_style = black if (column + is_odd) % 2 else white
+ return square_style
+
+ segments = [
+ Segment(" " * 8, get_square_style(column, row_index))
+ for column in range(self.board_size)
+ ]
+ strip = Strip(segments, self.board_size * 8)
+ # Crop the strip so that is covers the visible area
+ strip = strip.crop(scroll_x, scroll_x + self.size.width)
+ return strip
+
+
+class BoardApp(App):
+ def compose(self) -> ComposeResult:
+ yield CheckerBoard(100)
+
+
+if __name__ == "__main__":
+ app = BoardApp()
+ app.run()
diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md
index d30e30c96..8c8f20c3b 100644
--- a/docs/guide/widgets.md
+++ b/docs/guide/widgets.md
@@ -200,4 +200,191 @@ TODO: Explanation of compound widgets
## Line API
-TODO: Explanation of line API
+A downside of widgets that return Rich renderables is that Textual will redraw the entire widget when its state is updated or it changes size.
+If a widget is large enough to require scrolling, or updates frequently, then this redrawing can make your app feel less responsive.
+Textual offers an alternative API which reduces the amount of work required to refresh a widget, and makes it possible to update portions of a widget (as small as a single character) without a full redraw. This is known as the *line API*.
+
+!!! note
+
+ The Line API requires a little more work that typical Rich renderables, but can produce powerful widgets such as the builtin [DataTable](./../widgets/data_table.md) which can handle thousands or even millions of rows.
+
+### Render Line method
+
+To build a widget with the line API, implement a `render_line` method rather than a `render` method. The `render_line` method takes a single integer argument `y` which is an offset from the top of the widget, and should return a [Strip][textual.strip.Strip] object containing that line's content.
+Textual will call this method as required to get content for every row of characters in the widget.
+
+
+--8<-- "docs/images/render_line.excalidraw.svg"
+
+
+Let's look at an example before we go in to the details. The following Textual app implements a widget with the line API that renders a checkerboard pattern. This might form the basis of a chess / checkers game. Here's the code:
+
+=== "checker01.py"
+
+ ```python title="checker01.py" hl_lines="12-31"
+ --8<-- "docs/examples/guide/widgets/checker01.py"
+ ```
+
+=== "Output"
+
+ ```{.textual path="docs/examples/guide/widgets/checker01.py"}
+ ```
+
+
+The `render_line` method above calculates a `Strip` for every row of characters in the widget. Each strip contains alternating black and white space characters which form the squares in the checkerboard.
+
+You may have noticed that the checkerboard widget makes use of some objects we haven't covered before. Let's explore those.
+
+#### Segment and Style
+
+A [Segment](https://rich.readthedocs.io/en/latest/protocol.html#low-level-render) is a class borrowed from the [Rich](https://github.com/Textualize/rich) project. It is small object (actually a named tuple) which bundles a string to be displayed and a [Style](https://rich.readthedocs.io/en/latest/style.html) which tells Textual how the text should look (color, bold, italic etc).
+
+Let's look at a simple segment which would produce the text "Hello, World!" in bold.
+
+```python
+greeting = Segment("Hello, World!", Style(bold=True))
+```
+
+This would create the following object:
+
+
+--8<-- "docs/images/segment.excalidraw.svg"
+
+
+Both Rich and Textual work with segments to generate content. A Textual app is the result of combining hundreds, or perhaps thousands, of segments,
+
+#### Strips
+
+A [Strip][textual.strip.Strip] is a container for a number of segments covering a single *line* (or row) in the Widget. A Strip will contain at least one segment, but often many more.
+
+A `Strip` is constructed from a list of `Segment` objects. Here's now you might construct a strip that displays the text "Hello, World!", but with the second word in bold:
+
+```python
+segments = [
+ Segment("Hello, "),
+ Segment("World", Style(bold=True)),
+ Segment("!")
+]
+strip = Strip(segments)
+```
+
+The first and third `Segment` omit a style, which results in the widget's default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a widget it would produce the text: Hello, **World**!
+
+The `Strip` constructor has an optional second parameter, which should be the *cell length* of the strip. The strip above has a length of 13, so we could have constructed it like this:
+
+```python
+strip = Strip(segments, 13)
+```
+
+Note that the cell length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to omit the length parameter so that Textual calculates it automatically.
+
+### Component classes
+
+When applying styles to widgets we can use CSS to select the child widgets. Widgets rendered with the line API don't have children per-se, but we can still use CSS to apply styles to parts of our widget by defining *component classes*. Component classes are associated with a widget by defining a `COMPONENT_CLASSES` class variable which should be a `set` of strings containing CSS class names.
+
+In the checkerboard example above we hard-coded the color of the squares to "white" and "black". But what if we want to create a checkerboard with different colors? We can do this by defining two component classes, one for the "white" squares and one for the "dark" squares. This will allow us to change the colors with CSS.
+
+The following example replaces our hard-coded colors with component classes.
+
+=== "checker02.py"
+
+ ```python title="checker02.py" hl_lines="11-13 16-23 35-36"
+ --8<-- "docs/examples/guide/widgets/checker02.py"
+ ```
+
+=== "Output"
+
+ ```{.textual path="docs/examples/guide/widgets/checker02.py"}
+ ```
+
+The `COMPONENT_CLASSES` class variable above adds two class names: `checkerboard--white-square` and `checkerboard--black-square`. These are set in the `DEFAULT_CSS` but can modified in the app's `CSS` class variable or external CSS.
+
+!!! tip
+
+ Component classes typically begin with the name of the widget followed by *two* hyphens. This is a convention to avoid potential name clashes.
+
+The `render_line` method calls [get_component_rich_style][textual.widget.Widget.get_component_rich_style] to get `Style` objects from the CSS, which we apply to the segments to create a more colorful looking checkerboard.
+
+###Β Scrolling
+
+A Line API widget can be made to scroll by extending the [ScrollView][textual.scroll_view.ScrollView] class (rather than `Widget`).
+The `ScrollView` class will do most of the work, but we will need to manage the following details:
+
+1. The `ScrollView` class requires a *virtual size*, which is the size of the scrollable content and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars.
+2. We need to update the `render_line` method to generate strips for the visible area of the widget, taking into account the current position of the scrollbars.
+
+Let's add scrolling to our checkerboard example. A standard 8 x 8 board isn't sufficient to demonstrate scrolling so we will make the size of the board configurable and set it to 100 x 100, for a total of 10,000 squares.
+
+=== "checker03.py"
+
+ ```python title="checker03.py" hl_lines="4 26-30 35-36 52-53"
+ --8<-- "docs/examples/guide/widgets/checker03.py"
+ ```
+
+=== "Output"
+
+ ```{.textual path="docs/examples/guide/widgets/checker03.py"}
+ ```
+
+The virtual size is set in the constructor to match the total size of the board, which will enable scrollbars (unless you have your terminal zoomed out very far). You can update the `virtual_size` attribute dynamically as required, but our checkerboard isn't going to change size so we only need to set it once.
+
+The `render_line` method gets the *scroll offset* which is an [Offset][textual.geometry.Offset] containing the current position of the scrollbars. We add `scroll_offset.y` to the `y` argument because `y` is relative to the top of the widget, and we need a Y coordinate relative to the scrollable content.
+
+We also need to compensate for the position of the horizontal scrollbar. This is done in the call to `strip.crop` which *crops* the strip to the visible area between `scroll_x` and `scroll_x + self.size.width`.
+
+!!! tip
+
+ [Strip][textual.strip.Strip] objects are immutable, so methods will return a new Strip rather than modifying the original.
+
+
+--8<-- "docs/images/scroll_view.excalidraw.svg"
+
+
+### Region updates
+
+The Line API makes it possible to refresh parts of a widget, as small as a single character.
+Refreshing smaller regions makes updates more efficient, and keeps your widget feeling responsive.
+
+To demonstrate this we will update the checkerboard to highlight the square under the mouse pointer.
+Here's the code:
+
+=== "checker04.py"
+
+ ```python title="checker04.py" hl_lines="18 28-30 33 41-44 46-63 74 81-92"
+ --8<-- "docs/examples/guide/widgets/checker04.py"
+ ```
+
+=== "Output"
+
+ ```{.textual path="docs/examples/guide/widgets/checker04.py"}
+ ```
+
+We've added a style to the checkerboard which is the color of the highlighted square, with a default of "darkred".
+We will need this when we come to render the highlighted square.
+
+We've also added a [reactive variable](./reactivity.md) called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives us reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor.
+
+The `on_mouse_move` handler takes the mouse coordinates from the [MouseMove][textual.events.MouseMove] object and calculates the coordinate of the square underneath the mouse. There's a little math here, so let's break it down.
+
+- The event contains the coordinates of the mouse relative to the top left of the widget, but we need the coordinate relative to the top left of board which depends on the position of the scrollbars.
+We can perform this conversion by adding `self.scroll_offset` to `event.offset`.
+- Once we have the board coordinate underneath the mouse we divide the x coordinate by 8 and divide the y coordinate by 4 to give us the coordinate of a square.
+
+If the cursor square coordinate calculated in `on_mouse_move` changes, Textual will call `watch_cursor_square` with the previous coordinate and new coordinate of the square. This method works out the regions of the widget to update and essentially does the reverse of the steps we took to go from mouse coordinates to square coordinates.
+The `get_square_region` function calculates a [Region][textual.geometry.Region] object for each square and uses them as a positional argument in a call to [refresh][textual.widget.Widget.refresh]. Passing Regions to `refresh` tells Textual to update only the cells underneath those regions, and not the entire region.
+
+!!! note
+
+ Textual is smart about performing updates. If you refresh multiple regions (even if they overlap), Textual will combine them in to as few non-overlapping regions as possible.
+
+The final step is to update the `render_line` method to use the cursor style when rendering the square underneath the mouse.
+
+You should find that if you move the mouse over the widget now, it will highlight the square underneath the mouse pointer in red.
+
+###Β Line API examples
+
+The following builtin widgets use the Line API. If you are building advanced widgets, it may be worth looking through the code for inspiration!
+
+- [DataTable](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_data_table.py)
+- [TextLog](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_text_log.py)
+- [Tree](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_tree.py)
diff --git a/docs/images/render_line.excalidraw.svg b/docs/images/render_line.excalidraw.svg
new file mode 100644
index 000000000..591956da0
--- /dev/null
+++ b/docs/images/render_line.excalidraw.svg
@@ -0,0 +1,16 @@
+
+
+ eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGtT28hcdTAwMTL9nl9BsV/2Vlx1MDAwNe109zxTtXVcdTAwMGJIIISER4BcdTAwMTBya4tcdTAwMTK2bFx1MDAxNORcdTAwMDe2eG7lv99cdTAwMWWHWPJDxjY2ce5d71x1MDAwNowk2+OZc7pPP0Z/v1haWk7vmtHyq6Xl6LZcdTAwMTQmcblcdTAwMTXeLL/0x6+jVjtu1PlcdTAwMTR2/m43rlqlzpXnadpsv/rjj1rYuojSZlx1MDAxMpai4DpuX4VJO70qx42g1Kj9XHUwMDExp1Gt/W//cyesRX82XHUwMDFitXLaXG6yXHUwMDBmWYnKcdpoff+sKIlqUT1t87v/h/9eWvq78zM3ulZUSsN6NYk6L+icylx1MDAwNqjR9Vx1MDAxZt1p1DuDtWhJkJa6e0Hcfs1cdTAwMWaXRmU+W+EhR9lcdTAwMTl/aFndXHUwMDFlnVx1MDAxY16fNnfM0d398cmhkqdlzD61XHUwMDEyJ8lBepd8n4mwdH7Vyo2pnbZcdTAwMWFcdTAwMTfRcVxcTs/5vOw73n1du8GTkL2q1biqntejtv/+0D3aaIalOL3zX0J0XHUwMDBmfp+DV0vZkVv+a0VcdTAwMDVOklx1MDAwMuuUMMZcdJd9sn89KFxurFJGgUGhrXR941pvJLxcdTAwMTI8rt9UhcolmY3sLCxdVHl49XL3mrRcdTAwMTXW282wxeuVXXfz8I3JUICCXHUwMDAwVffUeVx1MDAxNFfPU1x1MDAwZiPUgVLOKtLKOevQZMOIOstcdTAwMDFaXHUwMDFhRONM9u38hze3ylx1MDAxZGT8lZ+wevlhwupXSZKN159404+mPKJyK920Z9VduFx1MDAxMVxmiFx1MDAxOGpn764qm4er3e/UXHUwMDAzv7DVatwsd898e3iWjeiqWVx1MDAwZdOHL2GURGLgWYvd80lcXL/oXHUwMDFmbNIoXWQw7Fx1MDAxY/32clxu+IOAQvzzSljhQMmx8Z9cXLy7PD1vbev9N3uJhlx1MDAwYrmLd1x1MDAxYlx1MDAwNfgvtVx1MDAxYe32ynmYls6LOIAz4lx1MDAwMIhHSeBE4LRgiFx1MDAxYqFcdTAwMDXwXHUwMDFh9JDAuMA5XHUwMDA3vCokrdVAhSRcdTAwMTCdx/QkUFJcdTAwMDaakCRcdFx1MDAwNVx1MDAwMEOoQFpcdTAwMDRcdTAwMTaYplKTpy3qXHUwMDAxKpBTXGYkUnZmVFx1MDAxOFx1MDAwMVaDRln9LGA1aFx1MDAwYrHqnCG2XGZkx1x1MDAwNqvaxvVSZecmxNXjk1x1MDAxYqrCadIuMtZ9gPt5MIWA0JA2glx1MDAxNFx0hFx1MDAwMZhK6yRcdTAwMWFUxlhw84QpXHUwMDA1XHUwMDAytEKrXGaw0bWDOEVcdTAwMWJoLZgvWqAmxvQgTlx1MDAxZCn2KFx1MDAxYWZnslx1MDAxZsOpmVx1MDAxNU6jJImb7eGKQkNcdTAwMTFKnbcsykpcdTAwMWNcdTAwMWKk+1uV6+279t37xsV++nqrtd3ePL+cXHUwMDA2pPB8IPUwJJBsw7RcdTAwMDX2IL0gtS5QhtFAiEJYJfqFzkQg/a1cdTAwMTIqVDhcYlCgQFxuROU08i822nJcdTAwMTChgIHSWjJcbkEgXHUwMDExylx1MDAwMVEh2bxaySrwf1xuoCbnVvpcdTAwMDCK1pFGY8dcdTAwMDcolurV++Rz+aB+bj/o9kH49cRcdTAwMTS5/EVcdTAwMDGo4oVcdTAwMTdCXHUwMDFha0AjsLPsXHUwMDA1qFxyXHUwMDE4XHUwMDExvFx1MDAxNk4oyVx1MDAwMNZPXHUwMDA06JlcdTAwMTBqXlx1MDAwMCW2v0Yw1Z5ccqD2OVx1MDAwMKpcdTAwMGI1qXKs0YhyjHxcZqDHe1x1MDAxN/d7n87XXHUwMDEy8UnvbO2+PvhcdTAwMWNcdTAwMDItOEBRXHUwMDA20kipXHUwMDE0aFx1MDAwMM1cdTAwMTBQvVxiNYFcdTAwMTGCueqIp4qVwJNcdTAwMTBcbnjGmnZeXGLl8TlhrNC/XHUwMDFlQkdqUetcbr08XHUwMDAwSZBKuPFBWj7Y3CuX3lx1MDAxZlx1MDAxZX01N1dcdTAwMWZut1Vauf70XFwgXHUwMDE1U4FUXHUwMDA0QILYO3JULpSPm7BcdTAwMDekoFxcXHUwMDAw0jlEXHUwMDA2MVx1MDAxOYbIk1BqnFx1MDAxMlx1MDAxNcQhrt5cdTAwMDdEikNcdTAwMDNyUqNcdTAwMWOePtBcdTAwMWO/SZSKjFx1MDAxNPwzXHUwMDA35Vx1MDAxZoZUasPhnZ5cdTAwMDSmWVrgXHUwMDA3ZOjhyLdi9HZfMySp8PHy+uaotH178WXjrb7/fLe+d4234yVcdTAwMTVejnrfuSYrXGZcdTAwMWJkSbPiXFxcdTAwMWHdpsPohrYw9EMjrVx1MDAwNDOBSzhxXHUwMDFm31x1MDAxZidcdTAwMDdvjqp2rXK1trL1QZVcdTAwMGWnS9M9o1Mgli0sSViyoPWhXHUwMDE39NFcclx1MDAwMz5swElcdTAwMDFI4Pqj0tlcdTAwMDV/mMuOZFx1MDAxNFP9lGLyK+/DZmj650SeR0HOVoVtuppcdTAwMDDkXHUwMDE5mFx1MDAxYfX0IL7vXHUwMDAwVfRcdTAwMWPdXGJrcXLXg4dcdTAwMGX6X3Vmulx1MDAxYaVcdTAwMDFPfzlqnfKnRb/f/Sn+lV+yduRcdTAwMGb7V9uel68mcdVcdTAwMTNmOYkqvUxK41KYdE+njWZ2tsTDXHT57Vpb5f6v1WjF1bhcdTAwMWUmh49cZm0qVlNxqFxmXHUwMDFjXHUwMDBikdMwfvJRXHUwMDFklsT2znFsWnt2o11cdTAwMGJ33ea7rV+C1Up4SrNzXCKle4NlNq1cdTAwMDEosFx1MDAxNkmwi2NcdTAwMTU2P1ZcdTAwMGZLNlx1MDAwZbKa1Vx1MDAxY0p2o5l5mSepL0vr7urg8qDkbPJ2O6xcXGxdfjibXHUwMDE5qVnTimzZflxuqWFxSVxyU5JajojehHLkI5qxSZ2YKt6n+9H1p9219fhzJV7Zw5tfgNQqQMGKmONcdTAwMTFcdTAwMTamprekhopcdTAwMDJhQLP168R4pm9gs/TUQ2K2IZ6anOPh2GdcInWj9CVsnVTfXHUwMDFlxXs7d+5g/8Jd7u/OjtSan/1cXFLj4pJcdTAwMWFcdTAwMWYh9fdcdFx1MDAxZsJqsLk0Wb+vJiU5XG5cdTAwMTg/aThaqy0qq1lWM29cdTAwMWRcdTAwMWFAzf6YSd6rwDWfVtrn45RcdTAwMDUgnJ9cdTAwMDJcdTAwMDewgVWGPOJRO8rlKLPUXGZcdTAwMDRoXHUwMDA1x2eWQ3Difznp8KNkzr5cdTAwMWOJ9CS0XHUwMDFmXGZ68eHIiKB3XHUwMDE0X0lLmspcdLfTsJWuxfVyXFyv9lx1MDAwZeyhJWRrjGCvw/DSlVx1MDAxZuWKXGKUlZJNtjI8JG0zseVnJmzyNTqwqKzzVXOSzFxioVx1MDAwNr480+3xQX1U5eZXiCvq6HiXPtRcdTAwMGWqq9VwtWBQXHUwMDA2wPB/1pLjNVdcdTAwMDNjXHUwMDAyXG5cYqWUwvjAz6EzXHUwMDAzY0rCdrreqNXilOd+r1x1MDAxMdfT/jnuTOaqJ/95XHUwMDE0XHUwMDBlmFx1MDAxNv5O+XP9VqLp37HX7GfPljJcdTAwMWF1/ug+/+vl0KuLsf39bD+qs/d7kf89uYFcdTAwMTNS9Fx1MDAxZv5h4CRcdTAwMThfUp+guDxauC6uhbNcdTAwMDFLe9ZcdTAwMDO+PFwiOf7qXHUwMDE1LiBcdTAwMDOn0WhpNIl8d8LMXHJcdTAwMWPKwFdg0DphhVOZQ8/6IJitvCp8gXS+ijOsJUghaWEmalx0mrl94zHgJL08k9q30WFvzpSIXHUwMDAwhFx1MDAwNuGs9Wl9XHUwMDBlrVx1MDAxNeSu+m5LePk77S+KULMxXHUwMDE5/OpjWbeTnd23qzWx6u7bZ7T1+vBetdebw4eEmlx1MDAxOExaW7Ik+HPNwJBAXHUwMDA0JFx1MDAxNS8w+1XprFx1MDAwNPdrm7dcImT7x8ogqGdm3vKxwEDzXGbrXHUwMDE29M1EY9u30Vx1MDAxYX5x7Vx1MDAxYrJEQ1x1MDAwNGRWSqFtb1x1MDAwZVx1MDAxNTXbN+NcdTAwMWJalCAgkvOLzNin+5KEVk5LadlMXHLpoFx1MDAwMVx1MDAxNVx1MDAwMFx1MDAwM1x1MDAwNSQrXHUwMDEydJZoiIWz1lxuMmaSTq+ZW7ipI64xLdzoXHUwMDFjQI85YelGzCpjkWWEM4NcdTAwMDbO16TYhnhcdTAwMDPoW+zUYMVmLFx1MDAwYrd1e5rcrVSPcUW3PsLnzXfNK7k3fEhsctF6YUasZyzpIUNcIoau0lxuoNP++ovbt0Jg+8fKIKYnNHCFWSco7lxyRI7IWDVOXHUwMDEyoI7U51x1MDAxM5m3wu7Actg+j2bZyc3YXHUwMDE2qHjWmY/ev/dFp8h+lGkhnWKZl/dcdTAwMDYzzzrl2oeyrFM21q7xXHUwMDEyXGZcdTAwMDRHM+yu6mJptkXQl6Ped751J2JdlKVTp8tmge452s1mZaWGXHUwMDFm2ayDtFx1MDAxNTd//087qvopfbnUfVx1MDAxMlx1MDAwNMFfXHUwMDA1SS3d8y7zTmo9MsKRpmNkP4fLtSn221x1MDAwZtZGPvKewH7EryVtv0nCS6rJXHUwMDBioq9cdTAwMWI3t7VcInm0IP1cdTAwMWMrXCKQaIT1qlx1MDAxM1xmWtvfXHUwMDE3XHUwMDA3Tlx1MDAwN1xuXHUwMDA0XHUwMDBiVO8/zdOa4Ofdz+FNvlx1MDAwNtLuacroiVxyXHUwMDFk8yxfkW9cdTAwMDM3z9PsxIFjYfLXi+lOz8H4RZ1K+0vdnF9cXH84PLtf+1irt+tuM1l4dqBwznI4L1x1MDAxOXhGqL6aXHUwMDBlSfa+TivB/6N4Ws/onLnB4ouv6Nm18Vx1MDAxM7gxzypcdTAwMTD5lqBcXNpnTk1JJFx1MDAwYiud2vq9RGqCRurRSZNcdTAwMDWVnDogjUL4rVNWXHSw/d1cdTAwMGJcIjDo87iW8WjNXHUwMDFjXHUwMDBinWNKTuLoz29cdTAwMTN6XHUwMDE2xTlX62+F31fzjzKciTIs7k8q7jpcdTAwMTSKpFx1MDAxNdaNnzNcdTAwMWKdNFhQhqtcdTAwMDBcdTAwMTUq0dnAxXymfrdnXHUwMDAyXHJW++SF1ajnV1x1MDAxMVx1MDAxOJPhljQoXprniSnn6sNcdTAwMThcXEr/XHUwMDEz+82X4a44Ka6N76PNV8dcdTAwMWYjOJhcdTAwMWS92fq0cXK11npT3r9d/bJn381U146i9zBh+yi9/T5O5dCXu5Umlvn9/Fx1MDAwZVgtXHUwMDEyKVx1MDAwZfeU0UZcdTAwMTdcdTAwMGLbMbb/j1x1MDAxMLaghm0hXHUwMDFk7FRija2cmSysW9Rd/lJcdTAwMTgzVWVwqk6l90yypdW9raXjTl/QXCJ0KPVcdTAwMGZpJINcdTAwMGIrW7r4XHUwMDBlXHUwMDA2KKRcdTAwMDSfa1x1MDAxZb+LePSSz3lX+FRcdTAwMTT2XHJcdTAwMTOGY0Gyxlx1MDAxN4Ogd1x1MDAxYlx1MDAwZVx1MDAwN4zOXHUwMDE3iDhe1Fx1MDAxMmGEh35cdTAwMWGBpVx1MDAwYoS0vqeRQ1x1MDAwMVx1MDAxNlx1MDAwNEPobGWgrZagJZtcdTAwMTOgXFz+6MF3I1x1MDAwN21mlvtcdTAwMDVGsk/lXHUwMDBiXHUwMDAys69ajXZcdTAwMDZLPUVwYVx00Vx1MDAxOFx1MDAxMKT81FHuqodcIjhcdTAwMDbGMZitXHUwMDAzniRp7WCPz1h1q9H7zHpcdTAwMDYloNM6gdJcdTAwMDdcdTAwMWHo01x1MDAxZYOjooC1oUFeclx1MDAwNpiU4tcuXVx1MDAxNULYP/rBm73Zi/zv6dJrVHxjXHUwMDBi35BmnZ5AhVT3bk72t1blUUlhfFgtl0qfP5ZnqkImtGDqUVx1MDAwYlx1MDAxNiAozfAxvu8kn9TsaFx1MDAxMCNcdTAwMDLpXHUwMDA0XHUwMDBiXHUwMDE0R8hRRv+wZpZbU96OknWK/OpcdTAwMDNcZuudfjS5ZslqY1xyPq2ncpFza7xcdTAwMDIzvOFLkTBXuW6HPkpcdTAwMTAvgGOvP75T39i5rq2vJPK02Tyrne5v7X51Z5s/OfKWj0rzwJBjny4sXHUwMDE4Ry5/XHUwMDAzle+0kFx1MDAwMet2sEjOXGIj5rjfb9x6rtTkd8o9z81cYizHXHUwMDAyk2w7/Sc6ntY7gSws/liw3v+Pn1x1MDAwM/uyefYubFx1MDAxZdy//Xik11x1MDAwZeMvd9vVm2e7XHUwMDFiw1TOybeFS/Y9yMFcdTAwMDSgXHUwMDEykEv6d7BcInRgWFx1MDAxM1xinlx1MDAwN806ySx0XVx1MDAxNFhnWpA0/43uI6sz/j4z8/YgUlx1MDAxNXpcdTAwMTDQYNCx3lx1MDAxON+FvK9cdTAwMWbbo1J8vH5v65XD083W12azseguhFx1MDAwMoaLUuyzfVecXHUwMDEzvVx1MDAxYlZcYjj08Dd11PyDr/rpXHUwMDBlRFx1MDAxOVL+plGz24Y20oFImKhE+I9cdTAwMDOZ2oFcdTAwMTTfY1x1MDAxNaThZfftx2MzcVVUrrd0RUNyrfZKXHUwMDFm4q9n9rqojPIsLuRRXHUwMDFh+lx1MDAxNFxmXGJGtkO/P6xv31x1MDAxOElcdTAwMGWjjXaC40q0+Z7kXHUwMDA19Fx1MDAxZlx1MDAxY4uSr3z9XFz34UvN83dcdTAwMWaFXHJhKCx6XHUwMDFkYMbXPXftjav7hjsyNqkmZXO6oe7fvVl098FoIHKotZ9vwVx1MDAxZaJcdTAwMWa4Olx1MDAxMP5mR75Q5XdcdTAwMGL99Po+XG7lt1SoXHUwMDE5llx1MDAwN0Z6XHUwMDEwjrqeelOQ/ztcdTAwMGby4iG5sFx1MDAxYzabXHUwMDA3Kc9o10LwWsXlh2nJPmf5Oo5u1obdWa/z8O/aobbnUNQxN99efPsvXHUwMDFht7uQIn0=
+
+
+
+ widget.render_line(y=0) widget.render_line(y=1) widget.render_line(y=2) Strip([segment, segment, ...]) Strip([segment, segment, ...]) Strip([segment, segment, ...]) Line API Widget Strip([segment, segment, ...]) Strip([segment, segment, ...]) Strip([segment, segment, ...])
diff --git a/docs/images/scroll_view.excalidraw.svg b/docs/images/scroll_view.excalidraw.svg
new file mode 100644
index 000000000..e2fc7f3f3
--- /dev/null
+++ b/docs/images/scroll_view.excalidraw.svg
@@ -0,0 +1,16 @@
+
+
+ eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nOVdaXNcIkmS/d6/QlbzcZuccI97zMbWdKFcdTAwMTPd6GB3TMYpkFKAOCSktv7v645UXCI5XHUwMDEyoYvKYtVtqiqSI4h87v6eh4fHX38sLf3oPDbLP/619KPcK+bDWqmVf/jxJz9+X261a406XcL+v9uNbqvYf2a102m2//XPf97mWzflTjPMXHUwMDE3y8F9rd3Nh+1Ot1RrXHUwMDA0xcbtP2ud8m37v/n3Xv62/O9m47bUaVx1MDAwNYNcdTAwMGZJlUu1TqP1/FnlsHxbrnfa9O7/Q/9eWvqr/zsyula52MnXr8Jy/1x1MDAwNf1Lg1x1MDAwMVx1MDAxYWVHXHUwMDFm3WvU+4NcdTAwMDVcdTAwMTRotdBavj6j1l6jz+uUS3S5QmMuXHUwMDBmrvBDP453K1AvX/dcdTAwMGWLK6nWib/e1M3LjcHHVmpheNx5XGafp1wiX6x2W5FBtTutxk35rFbqVPnTR1x1MDAxZX99XbtBszB4VavRvarWy+320GtcdTAwMWHNfLHWeaTHpHh98HlcdTAwMTL+tTR4pEf/SjlcZjRcdTAwMDJ4kFx1MDAwZZXE14v8auVcXKC0tVJIROE1yJFhrTZCulx1MDAxMzSsf4j+z2BghXzx5opGVy9ccp6D6FxuZTN4zsPLlzUmXHUwMDEwylqtUGlnhFevz6iWa1fVXHUwMDBlPcVi4KQ2wlx1MDAxOIHCOTdcdTAwMThnu9y/XHUwMDFmXGJSKbruXHUwMDA3N5Q/vrlV6mPjP9FcdTAwMTmrl15mrN5ccsPBiPnCelx1MDAwNE+D13SbpfzzbVx1MDAwN2O1Rk9DoLl5vVx1MDAxZdbqN6NvXHUwMDE3Noo3XHUwMDAzpPRcdTAwMWb9+89cdTAwMGZAlO5KXHUwMDFjRK1cdTAwMDaNXHUwMDAy3OxcYl3LVm6ue8t7d1x1MDAxOXl2eFxcObnPZdeWXHUwMDEzjlBtXHUwMDAy0MpcdTAwMWGp0ErrnDfjXHUwMDE4ldJrXHRcdTAwMDRTacEkXHUwMDE2ozR85b0wesEgaoWLgyhISSZcdORDZsZo+WRzm1x1MDAwNl8olNTRQWbL7excdTAwMWPcqIRjNFx1MDAwNTJcdTAwMTDgnfZgwVx1MDAxOedgXHUwMDE4pNrIQFx1MDAxOSfoXHUwMDEyoFOj40pcdTAwMGVEQUvp6D9YOIjqOIhaa5SQxsLMXGI93y/s7VxcaFdoXHUwMDFjXHUwMDFj3ubP7zeJ37R/LUJBvOlGVWC1oWl3XHUwMDA2vFFgh1x1MDAwMGqkXHUwMDBmiFx1MDAwM3hPXHUwMDFlSmilMbFcYkWwVtFQXHUwMDE3XHUwMDBloVx1MDAxMEtFUSjiNqD97IH+NpXLy3RxOXucq96U16pcdTAwMDfrXHUwMDFiISTdiTpcYoiFKidccjqg71xmw1x1MDAxMFVcIiBcdTAwMGaqtFx1MDAwMkVE1erEQlx1MDAxNCyRXHUwMDE0TTFg0bio9bFcXFx1MDAxNFx1MDAxY01cdTAwMTdFNz27XHUwMDFi9TZvdNi4vu5cdTAwMTazcFx1MDAxOXZLjX27l3CMXHUwMDAy6sBcdTAwMWKhKcxcdTAwMTNcdTAwMDR8RFx1MDAxMD2HeU1cXNRcYutQ9mNKciHafy25lPm5UT9cdTAwMWa5JHxcdTAwMWNEtfDOXHUwMDAy+plcdTAwMDHaetquLtv1PVxym5uynFp7Slx1MDAxZlRPk1x1MDAxZefpzlKgJ1x1MDAwNmdR05/aXHJLesJtgI6UvDGoPGKSqahw4KxcdTAwMTBcdTAwMGJcdTAwMDdRXHUwMDE5n3RykmKbfIdcdTAwMTNdllx1MDAwNd+53Xpyjerm8Xr2XGbyp/WkK/pcdTAwMTRgIDmvRLLeoSBHaUYwqomMXG5E68lFXHUwMDE5n9ysXHUwMDEzgDWk6oWaX6SfXHUwMDBmRq0wcVx1MDAxOPXgPOiogHhcdTAwMGKjK141fKdzs1Jo3+CtKUH7ot5MOEbBXHUwMDEzXHUwMDA2lVx1MDAwNVx1MDAwMUpcdTAwMDLFe1x1MDAxY470ylx1MDAxOYKGoD+EQYr3oJKLUUnwcdZEXHUwMDAy42JglD4pXHUwMDBlo8zMlNJmdozuhL3qWlXmMlx1MDAxZFFcdTAwMGXXzcNj2OydJFx1MDAxY6NSyoCYptBcdTAwMTTKhTM4mrwniFxuY+mOWCFRJDgxSjZGMUBoOb/M6JzcqME4iFwiKE30Rjo9M0b1zXpYKFXu08f3WpRy5+FlfT1MOEbRqEA68j9cdTAwMWWU0TKShntcdTAwMTZMXHUwMDEwOMKuQk5MYYKdKPlcdTAwMTJA4qNcdTAwMGKHUFx1MDAxZJu7d2hQ0q2ZPeu0eqMuOnBdusruXHUwMDE1W6dcdTAwMGYtma3m80lcdTAwMTdM7EWV8kQzXHRcdTAwMDFcdTAwMDCRtYyfWSdA4ywvjoIzXHSGqJVcdTAwMTL418JBVMk4iKKj2dL+XHUwMDFkq0v6+mJze9dm95dVuYHb+6enzZxNOkTBm4Cw4STxXHUwMDFhXHUwMDAxUo5hXHUwMDE0XHUwMDAzLSjWkGJcImbuklx1MDAwYlGyMbpjOMdF+jlRUVx1MDAxZFx1MDAwYlFtke5cdTAwMWH42SU9bi7bo3C7lrtfbV5f7eNtRVxc51x1MDAxMlx1MDAxZeaVkIEha1x1MDAxNN4rXHUwMDBmVo2GeVwiXHUwMDAxSMhcdTAwMTDSeUy0oDfaWWW1XTQn6lxcbF6UvIvxRkaXSN+C6F7n8r6mbnqnzZW73HLnMXN8VF3/XHUwMDFkIGqEJS6qnZMjqXvOOVx1MDAxMVd1gqhcdTAwMDCr/eSKJS6FsVx1MDAxNtTCIVTGMlFcdTAwMDM0a1rI2X3oxe7DRqHaq4Rdd7ZV92erjfr+RdLDPNHMwFiHJJPAOI3KjkDUXHUwMDA2XHUwMDAyXHUwMDFk31x1MDAxOGe4jiixXHUwMDEwReEleVHQi5ZysjLWiyryoUJqNbtcdTAwMTNcci+ecvumqFx1MDAwZm/NQ72zv3N73To/TLhcdTAwMTNNOZJD9D3BSclcdJtcdTAwMTGEelx1MDAxMVgwSoNcdTAwMDaLTiVXLCktkWJeRFksXHUwMDA2QJ1cdTAwMTdxXHUwMDAwXHUwMDA1Vlx1MDAxMHTv9OxcYr16KKZPa21wl9nH7VpVmePc+i9Ois5Q6GRJXGZJ+qaEQZr3KD6eMVxugdJkrFZcdTAwMDOp/YiLSlx1MDAxYUbpXCLxXHUwMDE05Vx1MDAxNm3908ZjlL+wgWhZxZvVou2KKp6c7Oid5mrT5DLd6nphM+lOXHUwMDE0REA6wztcdTAwMDOoud4uUpPAb+BcYj2kokjqk2Am8CS3jMRcdTAwMTlnhV84JlxusVFcdTAwMWWFXHUwMDE2Xoj3rCx5XHUwMDExVjvdwoYt75ZqXCJzXuymdlx1MDAxYlx0Ryj5UGk8ek1cXFR7gcNy3itcdTAwMTF4hSRDXHUwMDE0XG6Z4Fx1MDAxMlx1MDAxMpLxxEXkwiXtnYivXHUwMDE1JXySdphcdTAwMWSehUZcdTAwMTWuWts7t08rXHUwMDA3t1f3ze398/VMwuGZsi6w1pIs1I6IJrnRXHUwMDExfMpAopTOoqbJwORWiqIgLi1cdTAwMTDEwvFQXHUwMDFiW2+PLJSMtbPT0OPMRerk5nxv67540d3aL4dcdTAwMGZcdTAwMGb791x0hyigXHJcdTAwMTR7SFCS5LBcdTAwMWGN8Fx1MDAwNGAgnU86RHlcdTAwMGLJLbe3aLVfQFx1MDAxN+ohNtukpLXo4Vx1MDAxZEp+fcs9XFyd7tQz9+XD7PLKSemyW096OpRcdTAwMDHKy1x1MDAxMp5cdTAwMWOQ5FxyIUNcdTAwMDAl7qlcdTAwMDMhNTlZrZXxNsGLSob/035cdTAwMDG1fHwps1x1MDAwNE9cIvZcdTAwMWRL8/dcdTAwMWJcdTAwMGad86Otq8xarry6fX2xXazmz1x1MDAxM1x1MDAwZdFcdTAwMTS5UMF1MsJpY6xcdTAwMWYpZSaM2sAp+lx1MDAxMUT0SEol14tcdTAwMDJdsDSChcuHmtgw7zQ4XHUwMDE33Y79pk7avYHbTmpzt3l3XHUwMDEwbuyk6j19eZD0ZFx1MDAxM3jL2+eFQCSkRpcwnnNNXHUwMDE4KND9XHJ3hGKdXFylXHUwMDA0QCYk1OIlRH188VxiOO2dXHUwMDE3XHUwMDA2Zlx1MDAwZvS5Wlx1MDAxNcqNVLpWz2RrOrO50fBcdTAwMGa/WMvPUuBkXHUwMDAyQqDXllx1MDAxM0p+dFWJMCqJXHIxepBcdTAwMTdtXHUwMDEyi1FjUJOkc4vmRJ2MdaIgOIXt5DtcbpzOT1x1MDAwZotccn9YujqrXHUwMDFkZU+0bj21Vs5cdTAwMTJcdTAwMWXo0fDKJil2pYiWXHUwMDEy3Vx1MDAxY07ZO7qsSMqT4JdcdTAwMWGT7EXRWuJcIkYvmlpyJjbhpFhcdTAwMWPCe7YsZc+3m1x1MDAxYrWtjaNMa2tXn3SOz07bSW+TI6VcdTAwMGVcdTAwMWNcYqKhxEhp1ofVkldcdTAwMTA4a/t1I+RpXHUwMDEznFx1MDAxMVx1MDAwNcnKwZk59iCZV1x1MDAwNV5spT2QNDBWKJw9zvuV8/rpffWyU8BcdTAwMTW3WXkqrJVcdTAwMWbuXHUwMDEyXHUwMDBlUfAuIDFvhffaaFx1MDAwMGlGMKpcdTAwMDKKn2iIqYJEmVxcQe/J0Kywi0dFMX7fp1x1MDAxMZr3jNvZNydcdTAwMWbs7dhuSp7ByeN1/cGmsVPYLCdcdTAwMWOiSlx1MDAxMDxIXHTT/UUhhVx1MDAxZNlcckJyMTCaVD1xVMNro8mFqDZcbr3Ri1bH7OPT9oCO63tRzVx1MDAwZdH1Qlhv3e1cdTAwMTfSXHUwMDBltleObjZuXHUwMDFl1sKjxEOUXHUwMDAzPVFRYclcdTAwMGaBXHUwMDE4dqL9tKi3gvyr8kbK5Fx1MDAxNo9cdTAwMDCS/Vxit4iCPt6LUtBcdTAwMTDc9WB2Lrpmd+5ztfW1p/AqbNbq4DauNm5cdTAwMTNcdTAwMGVR8p+BIMmOdPspkFx1MDAwYmdHMcqro16C1nR7olQvcSBVvDuVSfOCgVSa+I54zit05Edmz91X7/Nb6V1cdTAwMTf2Llxua2m7nXdHO+W4XHUwMDFhp1x1MDAxMbBcckN0tFx1MDAxZfP1VaV8u1p+XHUwMDA3Ru0sbUVZXHRpZTn1NJq6573JXHUwMDA2eEcl31x1MDAxOTDuUyVOnVa+3m7mW4SBcZxqrVx1MDAwMmOs05ZmXurIkskrTlx0oUG/o5TWRJ9hUs8xxWu5qL5cdTAwMGWnL1x1MDAxN1x1MDAwNsCK3O+Gvdjcv8Dl5ulx+7RSa/m6yVxmmndccqEw32o1XHUwMDFlfrxe+fvPae+bOd+9XHUwMDE2+TOzVz0uioNmoVx1MDAxM+7ordne9+VvybAucDZ+M4vgLXJcdTAwMTbf0c5v+bKS71xcXHUwMDFk7Fx1MDAxZjdSVVkopo+PN1RcXD5iqnmNrj99azM/bblcdTAwMTZNXHUwMDExTyalN8yjjXRcdTAwMDFcdTAwMDV+0CwriG/Hl2HTs/LqjVx1MDAwMFCpVMatSipHI1BI4ZY+30VcdTAwMTbTX62KXCJT4HkvXHUwMDAzyU2DUX3+umHVaMlcdTAwMWJWv45FT7Wq89LpKt5V7vaq+96Yxoo5yJz8juhcdTAwMTdcdTAwMTCf6rA0o85F6/LfQr8tbu7Ui4VO53Hn3pnlp9Ku6vaSvqShdSB5r1x1MDAwNVx1MDAxMoSUVsRyhlxyQEEgyd+DI1x1MDAwNjy0pTtpXGbIglRCLdy6MFxijMcoXHRJ40HNzn+2lrP3XHUwMDBm4U1W5S5219IrWbVij4pJhyh4XHUwMDE1oNFWcl9cdTAwMGIyWTWGUFx1MDAwMVx1MDAwNlx1MDAwNXlp3tGdZI7OXHUwMDA1XHUwMDE4Xi9cdTAwMWNH91x1MDAxMYowuqbRryrRZvZUhzvdyJz27uzmyurxzr06XHUwMDBm5ermL27DNsvCMFx1MDAwNqSYvSdcdTAwMDLurbejPYFcdTAwMTVcdTAwMDaSN1x1MDAxYzpP0TvJzVm80t5TXHUwMDE0WDg3qlxcrFx1MDAxYjVWSVx1MDAxMlc4e6TvPS7fhPlwfTt31MvlTn0h3HWtxPNcXCG04jVGr7mkeyTOXHUwMDEz0SWKS1x1MDAxNJIgqpz93I7YXHUwMDE4omtcdTAwMDOtaFx1MDAxMJKbwGk1QT7CM9tGYl7cKFx1MDAwNqL7dV52XHUwMDFiXG6Dyir4ukW3aURcdTAwMTex97jTbsPxg6rD9uPBtj+utWcjulPl429EoMthWGu2J9tcdTAwMTTxxjibQsKIXHUwMDA2Zdzs4nE1tfm0dbyXlb1cdTAwMDPZvs4101x1MDAwZvthXFxcdTAwMDJxqlHNz+1cdTAwMWKiXHUwMDFlfI5cdTAwMDa3XHUwMDFmXHUwMDEz0c6Fz05fXHUwMDEzMUHJus1+cot5Ja9R47hNsXS0xtHryb+p6IlcdTAwMWSvNuUoKqDtb9Hs96yXoyZcdTAwMDVcdTAwMWVcdTAwMTVyt/D5iMdP2FSSsK+mtPXkXHLsxsvZXHUwMDE3yVx1MDAxZkXtonq/bVx1MDAwMVx1MDAxZXZ6XHUwMDE1n013u+eYbOhbctVAmDFeSEUqeVQ2qn5iXVvlUVxu87l64kq+QN7ke9BvKfJcdTAwMTn9lfnIZMBTx+f1nOZ2rO9pUrPSOzg9Or9q1zOt+onRqdst7dLJxqc3XHUwMDAxeUyJwnpBXHUwMDE4XHUwMDFjQadcZlx1MDAxY0hcdTAwMDOWa+GI9Hxcbp2AXHUwMDA15ybw8a9AJyrlXHUwMDAwo0dcdTAwMDH8Nuh8K/NcdTAwMTa79qiNJOeJcnbJeLeVrm0yc7BcdTAwMTdP9e4y3jzI9C9u8zVcdTAwMDMhN4RBL/uiUWvrRurd+SwuuiOSdJxcIsKb3I3rXHUwMDAwaHjBZ+G6KHlcdTAwMTWfeFPsRH20XHUwMDEx9FtcdTAwMThN27ua9duVxure6nlnuXZb3ln7xZ27Z0trXHUwMDEwj9GOlCNnf4drNblHXHLvXHUwMDE1XCKhJrj1QoJrNWl00jrxO0b56aduxm9sXHUwMDAzT1x1MDAxOFx1MDAxNVx1MDAxNFneUU58mOnKXZu9W1x1MDAxM+srOzv32Vx1MDAxNYzd2fblYV58XGKh2lx1MDAwNki+UlxuirROR49cdTAwMTjtl8FpRVxi9U5JQb/d51LDxXJJlfLjXHUwMDAwlcR1pVx1MDAxNUSB0ViylkhkXHUwMDFiJDYwMF4765ltSHKYalx1MDAxNKLIXUeV/Uo3+nJholxuM2m9d5KRT1ukXdcvU3p143LvcDZcdTAwMTX257T33czVTHhSf+q08+Ji9XBv/ab31PgqdUdwILo+XHUwMDFm51x1MDAxZp/aIOZsXHUwMDE0yvdcdTAwMWPEuFJO5eHo2nWaZ49yZb1X2c2sJZ6gXHUwMDEwOVx0LFx1MDAxYc3HL1x07q0w3v1JXGLD59xcdTAwMTlF/jW5PfRcXL9kxSyc85+6Ic9xu1x1MDAwZfOOKuiLsL0s072101x1MDAwYpHN71x1MDAxYtvJp5dlsp0/qdxcdTAwMDClXHUwMDA04tDcjlSPIJS9v6BcdTAwMTBoiK55Jz/ZcFxcXHUwMDE1TbkyIVx0XHUwMDAx3lx1MDAwN9JbXHUwMDAwj9yHSkyo3yDvT+BFQzpUOOWjNVx1MDAwNT8hXG5cXH6iXHUwMDE27oCmaD/xXHUwMDExiEq2SVx1MDAxZF1cZn1cdTAwMTOhd729w4vl/HZnZ2VHnl2J1XVI/uI16lx1MDAwMKxcInhwP3mMXHUwMDFlY/HcidRcdTAwMDSSXHUwMDBiMFx1MDAxNOk8XHUwMDFmLeBJmlx1MDAxN1x1MDAwNU6kWLt4zSHIaOMwalx1MDAxNUU4mozZI31ntbZXrbWrdrmi0o3Udc2ellx1MDAwYknHKFx1MDAxYVx1MDAxOWiB5JpcZoAzY/2cTUBcdTAwMTSagihywjfBzXKl5lx1MDAxM7VcZs4tXHUwMDEzXHUwMDExab/4rdkygFg2yrkh3nw/O0Srayd+67GweplXXHUwMDA3T83zfPmweFx1MDAxOXcqeEJcdTAwMDK9dC6g6GmRmDc5Slx1MDAxY85DXHUwMDE4TVx1MDAxMVihUJI7zmvxPTJcdTAwMGZkoIhE8mJcdTAwMDFZgvGRj1x1MDAxOVx1MDAwNHqv+Cwzy1x1MDAwZV8hUZLxxTbB8mHo1d8p875Vjs3TXHUwMDAyplfBWURcdTAwMDPvqDG6gHSx2Mvu11x1MDAwZdPiuuuf0ie4XHUwMDFld1x1MDAxY2lC1jPYXHUwMDA0LHeC9rxcdTAwMWRFXHUwMDFhXHUwMDFjWXAjscY9KZSxRDC9/tySRizXhcCQtCBkU5RcdTAwMTAmmltcdTAwMWGsaSA3vFx1MDAxNMJb0d8zM8Z1uaU198iYz3Lz91pcdTAwMDBxh1xiw/2kXHUwMDA1dMq9zkTw2/hcXFx1MDAwNDvDfmO5mbF/WMrc7V9cdTAwMWTpjebVQ+8207kut+1jsrfAXHUwMDEw5lx1MDAwM97agqiJXHUwMDE1Kj16yKly/eI7XHUwMDE0ktgpfvKs6Im1S6gn8JHoyVx1MDAxNi+bWYWWnjuKzVx1MDAwNdrftbnlNH1oiqliPsy1ZK+3msvmunj8y4LGXHUwMDAwmY1657j21K8uckOPpvO3tfBxXGJdfVuiXHUwMDAx3tdanW4+vGzTXHUwMDBig5c7XHUwMDE3uf3tMlxyoP+Oeuily2Htik3vR1iuXGbbZKdGXHUwMDEz83q504is4Vx1MDAxNGkoeXq71lZp9Cs1WrWrWj1cdTAwMWaeTFx1MDAxOdZU3/A80Vx1MDAxM5yDjO9cdTAwMWLG68rkqmdcdTAwMTfY01x1MDAwMfVcdTAwMGLKXHUwMDFh3/RcZlrYgMNcdJLxK1x1MDAxN21o1H+51IHU5Fx1MDAxYVR/Z4P83Fx1MDAwMtXkosZAcDrfSevpfy0mXHUwMDE0NVwi14EpxedcdTAwMWIrjUpGT1x1MDAxNXshhY6rXHUwMDAwXFy0XHUwMDE3cZKES2TW8q3OSq1eqtWv6OLAdfwoP3/01lxmXHUwMDExpm+zxS6PMiVcdTAwMDLPRf2aj6ZWpP5cdTAwMDbHXFzwXHUwMDE05JvPtFuS6lx1MDAwM+2lN8R7lHt5xqtcdTAwMGb7Ua6X3lx1MDAxZdT0nZ/RQUFAZkM3RIO1Rlx1MDAxYTM+Jkk0qK+6pNbWXHUwMDFib8aGXHUwMDE05tud1cbtba1DU3/QqNU7o1Pcn8tltupqOT/mL+grRa+Nmn+T33E4Plxm/rY0sJH+P17//p8/Jz47XHUwMDE1i+H+1XH4XHUwMDBl3vCP6J/vdl3g4lx1MDAwZsxcdTAwMTQ0rzp6XHUwMDE4+Fu+a3rQSqTvXHUwMDAyXGacMoq4hTPK4OjiOlx1MDAwMZ9cdTAwMTNcIkKTojTuk1x1MDAxNXRcdTAwMTNJTUBKQTjuXHUwMDEyQoJcdTAwMTYnkXn0JvBoXHUwMDE0N1j2Q2vJr9VcdTAwMWbgSVx1MDAxMkj8f+a6ROCclyTDvFx1MDAwM7qFkSbdr16CXHUwMDBi6lxy3TuCOadcdTAwMDOMn+64hr/F7+Q/YnHEP2NcYnqn94hcdTAwMTNFXHUwMDE4aVx1MDAxYzbqPKRA8lTvKM5pZFx1MDAxZs9ubaPcLGbO2/uq53Xq9EOFXHUwMDBm89NEzlx1MDAwNzTd5LWVdlx1MDAwZYRcdTAwMTjO2fJOzUDxjk3pnJRcbj/XXmWy+1AzaVwiXHUwMDEwXHUwMDEyrHZfeLTJK4K+dmP+VE10eUfM/na1dLV2SVx1MDAxYXPz5GB/q1hdXHUwMDA0TfR8N5MmiZ5H9TFagT5eXHUwMDExkSP22pnZN8xOx1NcdTAwMTJphVTE5/hcdTAwMDRuzV1cdTAwMDEs4qhnQKJcdTAwMWRcdTAwMTSUXHUwMDE09y6Unz1cdTAwMTJpomdcdTAwMDBhXHUwMDAyR1x1MDAxZu1IgFx1MDAxMmsk/TXuKIhCoyT2Yzw3K3U+kjP5mUyxzlxuObfVxm/lXHUwMDE101x1MDAwM8wrZfBcdTAwMDF6a9FYllx1MDAxZdxcdTAwMDd7smhcdTAwMTKcXHUwMDA146JcdTAwMWPBZbdK/oypXy2Jnlx1MDAwN4UuXHUwMDAwzjtznLGSXHUwMDBmMpqsmoRcdTAwMTe8QEKciIZlzLhO+51ITTyE+Sc1jt530pp474XxR7p5x4fcvKPubHrUSqL3sjJwJHZI+TG43UhCXHUwMDA3+KhccuuE1EZ5bsvyXHLbVEFDXHUwMDAwXHUwMDE0XCJcYsaOTFxm3KR1voC7oDrSqJJbbMrIdtSfzotgQaFmfodf/1r3XHUwMDE1cU5AhFx1MDAxNEi1XCJcdTAwMGIk7uw35lC48Vx1MDAxZlqFkmjhc9Ob6f7r91VG8Vh6vjxcdTAwMDajd3qROHFkpm3MRXDeR1Olb/ZHrW489MJcXObx8WlVy+WM0vXK2q/0XCL+LS+S0nyomXL9XHUwMDAzd4zGkcMktFS8nmRRXHUwMDBiukNexi+WXHUwMDE2PeYx/43iqH/ajv9KdTStfppl+mBcZt+tM0hJVIJ2sdVcYsPLRqXSLidi6WXCqD5cdTAwMTapdXzrNG1cdTAwMDRZdZS8vmVj03dFJzFSp6znXHUwMDEzqsnXW66vJa8/ZGOqf4C1JetcdTAwMTJGO4VTemd+2MhcdTAwMDBVYJXnXHUwMDE0XHUwMDFjSC8hsmFrXHUwMDEwqo1cZri3XHUwMDA3elSWrHIsg8l1Q4pcdTAwMGL95tLyXHUwMDA0rDPqK0L1aFB7O4hPb1oywuWl53RcdTAwMWVqcpXCXHJK/pZcdTAwMDbJTU/S0mrF7W608vL3ZvuxQOpfXHUwMDFkhdBcdTAwMTdF6eh+37H6fetcdTAwMWRzg9mj9NHZzX796v60dt29yOS1PCnBdpjsKI3OkGrkQ1x1MDAxNLRA6e1o3SmRJ6GF8WSeXHUwMDE433huXHUwMDA29zG1rSmYXHRcdTAwMWK2JoRqkoOMdfi6XHUwMDFh/VdcdTAwMWP9ilx1MDAxNjFzTDg+Lv176SXiPiaBXHUwMDAyXGaN52PB30yR6aRrjFx1MDAxNPJcdTAwMWR1XHUwMDE3U+91XCJNXHUwMDE3hFxulCNpY6Xpn1x1MDAxYTmyOYyrZT2FXGLe4m7UlMqLT1uv8oHzvHvGcz9cdTAwMTIhJ3BcdTAwMDBSYVx1MDAwZcBcdHR82ICa0Fx1MDAwMseK/smyZi6rmGA1XHUwMDA1ky/gXHUwMDAwcZF+eihYiq5iUpwnXHTCfcOF9laPXHUwMDA3elBcdTAwMDFdI3fBrWelNm682OFr2Vx1MDAwN9FGQYzCsjDiqr5J3IN71HE9oHRa0vB/b+5cdTAwMTFcdTAwMGJg/kmNYfeLyIeRscVcdTAwMTc0XHUwMDBloKDrZ+9cdTAwMGaDXHUwMDA3qdZaeLjse2F2I1u73c6Bc8l2YOSZXHUwMDAydl7kw4zhxZAh/+UlXHUwMDA2SE5cXDllLZeifJ//slx1MDAxM1x1MDAxY9Yk9tE/1O1cdTAwMGKXQ6aRj0/sXHUwMDBlf5t8XHUwMDEwnfaRpv7fTT56g2DfS1x1MDAwMvlcdTAwMThcdTAwMWHPx8hcdTAwMDeXp8VcdTAwMTkvzbAw7zr+aPrNTqTxXHUwMDAy9yVBpygmeTJgXHUwMDFjyT2QTlx1MDAwYlx1MDAxMNGT/Vx1MDAxYe99fO1DibSHqHzCeJ1cdTAwMGZQsCZUxnKX7HFTNuTdjdJeKvLfWsFY41x1MDAwN7DAgfUri8a/xfhm5Fx1MDAxZdNDQSSGXHUwMDBiJTiuXHRyr9xBYFx1MDAwMlx1MDAxM+CVTor0XHUwMDFj4ZWy3HOByPHH2Mf0ziZcdTAwMDOyM2FcdTAwMTTc5Vx1MDAwMFx1MDAwNaBzgLxs/nuTjTjA8k9qXGarX8Q1tI7lXHUwMDFhUvSLe9/RwnzdbKz2uqc5dyGOb25qVX1cdTAwMWFmk801NK9cdTAwMDKR7XnBp11ZO9xcdTAwMWKaZjvgynVNXCKK7suUXHUwMDBl+5+lXHUwMDFhOKk63Y4lRHmbL1x1MDAxOdxcdTAwMTdcdTAwMWVcdTAwMGU8jWp8b8NcdTAwMThUVnxIaX2aaiz91//Wn5dcdTAwMWGmV1mZ4beZJ/uYNMRcdTAwMGZcdTAwMTJcdTAwMTLw8VvUKOKAtO85pWA6Jlx1MDAxMmni3lx1MDAwN55z4ZJcdTAwMWKB2JGaK1wiXCKBRd7VzkdcdTAwMTR4XHUwMDFiT0g+rSbIv5NIXHUwMDA3329RJGDSKqTl4lx1MDAxNW208dKSjejx7Shk/PRiO5+OfsinWOiP2OiMjGR6wFiKZlx1MDAxZZDPylx1MDAwND6uwFC8x/GdXHUwMDFmKuBSW3IqgMhV31x1MDAxZt2MMnVcdTAwMGL+0JCI8lx1MDAxMNEl5Vx1MDAwZp6X7u3YkFxcoPlwXGbNjVx1MDAwZpif/N51V6l4XHUwMDA09y+PgfeLKFxuQPwxcyRcInjh/Fx1MDAxZOdg1dxGXHUwMDBmM+kw18hgunS0JY5cbvvbiXZg4HSgNVx1MDAxZj/FvJDsYPR4XHUwMDAwXHUwMDEzOEt6SkpyXHUwMDBicko70lx1MDAxOc7BmurB7IQ2uZFcXPvPTlx1MDAxZlxc1+Hn2C/p06slkfa/QyxiXHUwMDEwKX6yiJVGvlVabjYn0oXI28yDLryO5dmo/nix21x1MDAxZvlm87hDs/Tq42j+a6WXrzp4x1x1MDAxZve18sPK5OV8XtH/48VQ2VwiynxcdTAwMWL++vuPv/9cdTAwMGZcYuGAVyJ9
+
+
+
+ virtual_size.height virtual_size.width self.scroll_offset y = scroll_y x = scroll_x x = scroll_x + self.size.width BoardApp
diff --git a/docs/images/segment.excalidraw.svg b/docs/images/segment.excalidraw.svg
new file mode 100644
index 000000000..a1a521b85
--- /dev/null
+++ b/docs/images/segment.excalidraw.svg
@@ -0,0 +1,16 @@
+
+
+ eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1aW1PbRlx1MDAxOH3nVzDuSztcdTAwMTM2e790ptOhJFx1MDAxNEIuUEJoaTuMsNa2gixcdTAwMTlJ5pJO/nu/XHUwMDE1RpIl7CBjUqd+XHUwMDAwe3el/Xb3nPNdpH/W1tc72c3Idn5cXO/Y665cdTAwMTdcdTAwMDZ+4l11nrn2S5ukQVx1MDAxY0FcdTAwMTfNf6fxOOnmI1x1MDAwN1k2Sn98/nzoJec2XHUwMDFihV7XossgXHUwMDFke2Gajf0gRt14+DzI7DD92f1961xy7U+jeOhnXHQqJ9mwfpDFye1cXDa0Q1x1MDAxYmUp3P1P+L2+/k/+t2JdYruZXHUwMDE39UObX5B3lVx1MDAwNirJ661v4yg3lkhFhGRcdTAwMWHLYkSQvoD5MutDd1x1MDAwZmy2ZY9r6vAjuvlhK+7ffPQvtlx1MDAwZcJNSo7CP8ppe0FcdTAwMThcdTAwMWVmN+HtVnjdwTipXHUwMDE4lWZJfG6PXHUwMDAzP1x1MDAxYrjZa+3Fdb6XXHUwMDBlwICiO4nH/UFkU7dcdTAwMDO4aI1HXjfIbtyNcNl6u1xy1XHX7pBcZkZSca41Zsatueh11zNKXHUwMDExx0ZLyZRcdTAwMTCMypphW3FcYmdcdTAwMDGGfYfzT2nZmdc974N5kV+MyVx1MDAxMi9KR15cdTAwMDInVo67miyZXHUwMDBiiqQglCqMXHKunszAXHUwMDA2/UHmjosgho1SynDMsJCiNMbmp0JcZnN2XHUwMDEyo4tcdTAwMWVnwmjXz1x1MDAxMfJ3ddtcIn+ybXeQKUHDJi2fy8W48S/rYKtcdTAwMDKugoN3e0dXxmNDtn/iXHUwMDFk7uyeblx1MDAxZqTvVLHgKXR6SVx1MDAxMl91ip7Pk2+loeOR791cIlx1MDAwZVx1MDAwMCk4XHUwMDExWlFDTdFcdTAwMWZcdTAwMDbROXRG4zAs2+LueVx00rXKStqxg1a2scZcdTAwMGVcdTAwMDBcdTAwMGLRilx1MDAxOf5gclxcnrw7vd5S9rV93dvjsYjt/lx0WYxcdTAwMWN0XHUwMDE2OdJcdTAwMTgk4n5ukFx1MDAwNblhkFwiXFxzXHUwMDAw1TQtXHUwMDE4QVhSMZNcclpSa7qLs4FiiaQhXHUwMDFjJuHGfVSTXHUwMDBlgiFtpm2744HEmlxuzivn80Q8mIdUXHUwMDAxu8SWhdTMXmf3gnQmRlx1MDAwNTVcZnSLPlxcwI9Aen57b7btx4R9XHUwMDE0Nt7ZXHUwMDBlg8GSXHUwMDA1fOlcdTAwMThlRFwirakmtFx1MDAwNlH+dFpNKtJbwJHyOlxmXHKmXHUwMDA0XHUwMDBiptqgcFxuXHUwMDFmrWT3evx7cPkq+nBp9/ax2Lu+Or5cdTAwMWPsLUl2OWfaXHUwMDE40lx1MDAwMswlauIoO1xmPtmc1FOt294wXGLzoyqac5SDgX91dmxcdTAwMTjGz9aP4yT0/+pUjyq1MHvtdu66zTDoO0Z0QtubpkpcdTAwMTZA8FR0Z/Go7O2CXHUwMDFkXHUwMDFl3C7Z9evriZOgXHUwMDFmRF74fpZNi3tcdTAwMTbGVL31jrWGXHUwMDEwIK3hXHUwMDBmZ+3u3ubbbSrVxYuD991cdTAwMWXTn3x/J5jB2lx1MDAxYfv+K79cIihGVIBCmqZjwVxiT7XWucsk73XVIzxcdTAwMGJhSNx5ldaeRTpBxZqV6/m/XHUwMDA1WMYwgkvFelwit0VmM4AoyjGRpEVwdfM2ftU/O9u/XHUwMDEx4uJ0g1x1MDAxZI9e71x1MDAxZmyuuuNcdTAwMTJMI6yVXHUwMDEyhlPnKNg0XHUwMDExuECSc8HInCjr0X5Mmyb4m35MSaG5Ulx1MDAxNU16Sj/2q/fJo3tcdTAwMDO6uSuOduTxsWTnLy+Wlj5cdTAwMTjNOG2B7sf5sVx1MDAxYz3fn8Wh/9P7ZGx/WFx1MDAwNT/WsGmxuLNidZ3AnEHKXHUwMDAwaWhcdTAwMTmafonAL0/UwcvRfnK+8bt9dbQpe1x1MDAxYofh6apcdTAwMTOYUYYoJNzwMVxuXHUwMDEy93K57nouOVx1MDAwMvpSXHUwMDE3PHHYjbp3XVwii+k9Low2XFyXkka6QK6N61rNaFRcdTAwMTBcdTAwMTBF9lQsJlNsdFx1MDAwNvZcdTAwMTNrsyDqo2kyVChcXIH616DwtEGL8Vx1MDAxN8/kL/gjXHR7rMpcdTAwMDFfou9V/8Ppm1x1MDAxNyPpXHUwMDFmvtl68UZEoThcdTAwMWOfrDp9hYJA0HBiZO6AWZ2+XG5RIDV0XHUwMDAwezWresVl87dCyTn8hWReSY1L2H+zTphcdTAwMTCBK1x1MDAxOfRXo2+aY2mV+Htr0VxcXHUwMDAy327vfUlkpVxmWvfAWlx1MDAwYlx1MDAwMVx1MDAxOU5cdTAwMGJcdTAwMGY8X69XlMJMXHUwMDBiJFx1MDAwNGR04Fx1MDAwZaRcdTAwMDF/PFx1MDAxZENzSZCmmFNtnthcdTAwMDVcdTAwMWKkQElcXKFEaqOwvqc8pDRSICXmLlx1MDAxY6hcdTAwMThzV7yXcFx1MDAwM6ZcdTAwMWVXu6eTlsVqlos71jTzkuyXIPJcdTAwMDHW04ZNnlHtPiDQy7ncXHUwMDFkOys3MMJcXFx1MDAxMVc9g3CSUWp4+WjG7Y03cqtFkuWlv8aqbeR/2Zr5XHUwMDA1z4o1YFxmwVx1MDAwNsPeMEyoNIRy1TCGMMjn8pJGw5rQS7OteDhcZjLY7v04iLL6tub7t+m4PrBeQ0BgNdW+uiiM3Fx1MDAxZKc1vfy2XpIm/1F8//vZvaNnYtl9Nlx1MDAxYTAub7dW/d9azqiaWclcdTAwMDYh4EJcYqVcdTAwMWUuZ/P914rKmWRcdTAwMWNcdTAwMTmqMVMu6IDMoaFmsFx1MDAwNdJhzEUkrGbX8tRcZuJcIsyNUkZcdTAwMGLIb4SsJP6lnCmEXHUwMDFkXHUwMDE1INOrXHUwMDE5M1EzJjXRXHUwMDFjyzZFg2XL2eLZ/lx1MDAwM+VsfuBblzNcdTAwMDaUYiBoXHUwMDEwVzqK8cq4W1x1MDAwNVx1MDAxMYgzdr+CPEjP5tfBpvWMXG7KNVx1MDAwMVx1MDAwNitOsKaGNawhXHUwMDAykVx1MDAxOer6LenZxmw45911JLdcdTAwMTS0WVx0lmGi3lokWFxmpuK0zZO5M9zt9a8hXHUwMDFhXHUwMDFkXHUwMDFmXnw8+qN38mv8epFcdTAwMWH/XCJitth7XHUwMDE1Tq5cdTAwMDTPdUxgXHUwMDAzwVlZpHA3oJQh8KJGXHUwMDEy6TA/O7tcdTAwMDLn7/H5YvZdr9drqph6UFWEQOKHtWStdGrxvOqJS/dSV6pQXyuvWqWMasFcXIpSQurNRfRcdTAwMDFbXHUwMDBiQCUtkqn5p7ySj+S4XHUwMDExiCqmlVCUS1x1MDAxMMKSkTldhUBaQSxuXHUwMDA0eHZiKq/GLI2wXHUwMDEwP3NcZiqtJOdcdTAwMWFCoEq4Vz6ZI65mg4VDPDZcdTAwMTJX9GzygFx1MDAwZbSVske+XHUwMDAw9aioQ2JcdTAwMTeutnlFqW3UMd9cdTAwMWJMuXnKMVx1MDAwM99GKTbCbUwz5lBIuUhAaYWpXHUwMDE0RuNcdTAwMDVjj/lv/9ViXHUwMDBmLMDlau5cdTAwMWVwSaFpwyhcdTAwMDKBMNPgrImGMFx1MDAxMoY0n5h+SyHIbGS7T1x1MDAwM9OzXCKQtclcdTAwMDRcdTAwMWRvNDrMXHUwMDAwdMVxXHUwMDAwylx1MDAwM39cIuDlKjuXgb365X76OVx1MDAwNq5N9tNcdJLNmfB57fO/MTl+mCJ9
+
+
+
+ "Hello, World" Style(bold=True) greeting.text greeting.style greeting
diff --git a/mkdocs.yml b/mkdocs.yml
index 1748de127..ad2d90d47 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -167,7 +167,9 @@ nav:
- "api/query.md"
- "api/reactive.md"
- "api/screen.md"
+ - "api/scroll_view.md"
- "api/static.md"
+ - "api/strip.md"
- "api/text_log.md"
- "api/timer.md"
- "api/tree.md"
diff --git a/poetry.lock b/poetry.lock
index 7cd75899a..c1ee65e43 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,5 +1,3 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
-
[[package]]
name = "aiohttp"
version = "3.8.3"
@@ -7,7 +5,1045 @@ description = "Async http client/server framework (asyncio)"
category = "main"
optional = false
python-versions = ">=3.6"
-files = [
+
+[package.dependencies]
+aiosignal = ">=1.1.2"
+async-timeout = ">=4.0.0a3,<5.0"
+asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""}
+attrs = ">=17.3.0"
+charset-normalizer = ">=2.0,<3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
+yarl = ">=1.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli", "aiodns", "cchardet"]
+
+[[package]]
+name = "aiosignal"
+version = "1.3.1"
+description = "aiosignal: a list of registered asynchronous callbacks"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "anyio"
+version = "3.6.2"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+idna = ">=2.8"
+sniffio = ">=1.1"
+typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
+
+[package.extras]
+doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
+trio = ["trio (>=0.16,<0.22)"]
+
+[[package]]
+name = "async-timeout"
+version = "4.0.2"
+description = "Timeout context manager for asyncio programs"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
+
+[[package]]
+name = "asynctest"
+version = "0.13.0"
+description = "Enhance the standard unittest package with features for testing asyncio libraries"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "attrs"
+version = "22.2.0"
+description = "Classes Without Boilerplate"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
+dev = ["attrs[docs,tests]"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
+tests = ["attrs[tests-no-zope]", "zope.interface"]
+tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
+
+[[package]]
+name = "black"
+version = "23.1.0"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "cached-property"
+version = "1.5.2"
+description = "A decorator for caching properties in classes."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "certifi"
+version = "2022.12.7"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "cfgv"
+version = "3.3.1"
+description = "Validate configuration and produce human readable error messages."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.1"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.extras]
+unicode-backport = ["unicodedata2"]
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+
+[[package]]
+name = "colored"
+version = "1.4.4"
+description = "Simple library for color and formatting to terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "coverage"
+version = "7.1.0"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+toml = ["tomli"]
+
+[[package]]
+name = "distlib"
+version = "0.3.6"
+description = "Distribution utilities"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.0"
+description = "Backport of PEP 654 (exception groups)"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "filelock"
+version = "3.9.0"
+description = "A platform independent file lock."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
+testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
+
+[[package]]
+name = "frozenlist"
+version = "1.3.3"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "ghp-import"
+version = "2.1.0"
+description = "Copy your docs directly to the gh-pages branch."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+python-dateutil = ">=2.8.1"
+
+[package.extras]
+dev = ["flake8", "markdown", "twine", "wheel"]
+
+[[package]]
+name = "gitdb"
+version = "4.0.10"
+description = "Git Object Database"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+smmap = ">=3.0.1,<6"
+
+[[package]]
+name = "gitpython"
+version = "3.1.30"
+description = "GitPython is a python library used to interact with Git repositories"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+gitdb = ">=4.0.1,<5"
+typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""}
+
+[[package]]
+name = "griffe"
+version = "0.25.4"
+description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+cached-property = {version = "*", markers = "python_version < \"3.8\""}
+colorama = ">=0.4"
+
+[package.extras]
+async = ["aiofiles (>=0.7,<1.0)"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
+
+[[package]]
+name = "httpcore"
+version = "0.16.3"
+description = "A minimal low-level HTTP client."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+anyio = ">=3.0,<5.0"
+certifi = "*"
+h11 = ">=0.13,<0.15"
+sniffio = ">=1.0.0,<2.0.0"
+
+[package.extras]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.23.3"
+description = "The next generation HTTP client."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+certifi = "*"
+httpcore = ">=0.15.0,<0.17.0"
+rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
+sniffio = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
+
+[[package]]
+name = "identify"
+version = "2.5.17"
+description = "File identification library for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+license = ["ukkonen"]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "importlib-metadata"
+version = "4.13.0"
+description = "Read metadata from Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+perf = ["ipython"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "jinja2"
+version = "3.0.3"
+description = "A very fast and expressive template engine."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "markdown"
+version = "3.3.7"
+description = "Python implementation of Markdown."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+
+[package.extras]
+testing = ["coverage", "pyyaml"]
+
+[[package]]
+name = "markdown-it-py"
+version = "2.1.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"]
+code-style = ["pre-commit (==2.6)"]
+compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"]
+linkify = ["linkify-it-py (>=1.0,<2.0)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.2"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mergedeep"
+version = "1.3.4"
+description = "A deep merge function for π."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "mkdocs"
+version = "1.4.2"
+description = "Project documentation with Markdown."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+click = ">=7.0"
+colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
+ghp-import = ">=1.0"
+importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
+jinja2 = ">=2.11.1"
+markdown = ">=3.2.1,<3.4"
+mergedeep = ">=1.3.4"
+packaging = ">=20.5"
+pyyaml = ">=5.1"
+pyyaml-env-tag = ">=0.1"
+typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""}
+watchdog = ">=2.0"
+
+[package.extras]
+i18n = ["babel (>=2.9.0)"]
+min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
+
+[[package]]
+name = "mkdocs-autorefs"
+version = "0.4.1"
+description = "Automatically link across pages in MkDocs."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+Markdown = ">=3.3"
+mkdocs = ">=1.1"
+
+[[package]]
+name = "mkdocs-exclude"
+version = "1.0.2"
+description = "A mkdocs plugin that lets you exclude files or trees."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+mkdocs = "*"
+
+[[package]]
+name = "mkdocs-material"
+version = "8.5.11"
+description = "Documentation that simply works"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+jinja2 = ">=3.0.2"
+markdown = ">=3.2"
+mkdocs = ">=1.4.0"
+mkdocs-material-extensions = ">=1.1"
+pygments = ">=2.12"
+pymdown-extensions = ">=9.4"
+requests = ">=2.26"
+
+[[package]]
+name = "mkdocs-material-extensions"
+version = "1.1.1"
+description = "Extension pack for Python Markdown and MkDocs Material."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mkdocs-rss-plugin"
+version = "1.5.0"
+description = "MkDocs plugin which generates a static RSS feed using git log and page.meta."
+category = "dev"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+GitPython = ">=3.1,<3.2"
+mkdocs = ">=1.1,<2"
+pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""}
+tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""}
+
+[package.extras]
+dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"]
+doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"]
+
+[[package]]
+name = "mkdocstrings"
+version = "0.20.0"
+description = "Automatic documentation from sources, for MkDocs."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+Jinja2 = ">=2.11.1"
+Markdown = ">=3.3"
+MarkupSafe = ">=1.1"
+mkdocs = ">=1.2"
+mkdocs-autorefs = ">=0.3.1"
+mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""}
+pymdown-extensions = ">=6.3"
+
+[package.extras]
+crystal = ["mkdocstrings-crystal (>=0.3.4)"]
+python = ["mkdocstrings-python (>=0.5.2)"]
+python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "0.8.3"
+description = "A Python handler for mkdocstrings."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+griffe = ">=0.24"
+mkdocstrings = ">=0.19"
+
+[[package]]
+name = "msgpack"
+version = "1.0.4"
+description = "MessagePack serializer"
+category = "main"
+optional = true
+python-versions = "*"
+
+[[package]]
+name = "multidict"
+version = "6.0.4"
+description = "multidict implementation"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mypy"
+version = "0.990"
+description = "Optional static typing for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
+typing-extensions = ">=3.10"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+install-types = ["pip"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "nanoid"
+version = "2.0.0"
+description = "A tiny, secure, URL-friendly, unique string ID generator for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "nodeenv"
+version = "1.7.0"
+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.*"
+
+[package.dependencies]
+setuptools = "*"
+
+[[package]]
+name = "packaging"
+version = "23.0"
+description = "Core utilities for Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "pathspec"
+version = "0.11.0"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "platformdirs"
+version = "2.6.2"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""}
+
+[package.extras]
+docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pre-commit"
+version = "2.21.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+virtualenv = ">=20.10.0"
+
+[[package]]
+name = "pygments"
+version = "2.14.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+plugins = ["importlib-metadata"]
+
+[[package]]
+name = "pymdown-extensions"
+version = "9.9.2"
+description = "Extension pack for Python Markdown."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+markdown = ">=3.2"
+
+[[package]]
+name = "pytest"
+version = "7.2.1"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-aiohttp"
+version = "1.0.4"
+description = "Pytest plugin for aiohttp support"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+aiohttp = ">=3.8.1"
+pytest = ">=6.1.0"
+pytest-asyncio = ">=0.17.2"
+
+[package.extras]
+testing = ["coverage (==6.2)", "mypy (==0.931)"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.20.3"
+description = "Pytest support for asyncio"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+pytest = ">=6.1.0"
+typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
+
+[[package]]
+name = "pytest-cov"
+version = "2.12.1"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+coverage = ">=5.2.1"
+pytest = ">=4.6"
+toml = "*"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2022.7.1"
+description = "World timezone definitions, modern and historical"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyyaml"
+version = "6.0"
+description = "YAML parser and emitter for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pyyaml-env-tag"
+version = "0.1"
+description = "A custom YAML tag for referencing environment variables in YAML files. "
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyyaml = "*"
+
+[[package]]
+name = "requests"
+version = "2.28.2"
+description = "Python HTTP for Humans."
+category = "dev"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<1.27"
+
+[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.3.1"
+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.1.0,<3.0.0"
+pygments = ">=2.14.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 = "67.1.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 (<5)", "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-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "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 = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "smmap"
+version = "5.0.0"
+description = "A pure Python implementation of a sliding window memory map manager"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[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 = "time-machine"
+version = "2.9.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 = "typed-ast"
+version = "1.5.4"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "typing-extensions"
+version = "4.4.0"
+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 = "urllib3"
+version = "1.26.14"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[[package]]
+name = "virtualenv"
+version = "20.17.1"
+description = "Virtual Python Environment builder"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+distlib = ">=0.3.6,<1"
+filelock = ">=3.4.1,<4"
+importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""}
+platformdirs = ">=2.4,<3"
+
+[package.extras]
+docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
+testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
+
+[[package]]
+name = "watchdog"
+version = "2.2.1"
+description = "Filesystem events monitoring"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+watchmedo = ["PyYAML (>=3.10)"]
+
+[[package]]
+name = "yarl"
+version = "1.8.2"
+description = "Yet another URL library"
+category = "main"
+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.12.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 = ["flake8 (<5)", "func-timeout", "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)"]
+
+[extras]
+dev = ["aiohttp", "click", "msgpack"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.7"
+content-hash = "8b9c57d32f9db7d59bacc1e254e46bc5ae523e9e831494c205caf1b5fe7982e4"
+
+[metadata.files]
+aiohttp = [
{file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"},
{file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"},
{file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"},
@@ -96,250 +1132,81 @@ files = [
{file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"},
{file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"},
]
-
-[package.dependencies]
-aiosignal = ">=1.1.2"
-async-timeout = ">=4.0.0a3,<5.0"
-asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""}
-attrs = ">=17.3.0"
-charset-normalizer = ">=2.0,<3.0"
-frozenlist = ">=1.1.1"
-multidict = ">=4.5,<7.0"
-typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
-yarl = ">=1.0,<2.0"
-
-[package.extras]
-speedups = ["Brotli", "aiodns", "cchardet"]
-
-[[package]]
-name = "aiosignal"
-version = "1.3.1"
-description = "aiosignal: a list of registered asynchronous callbacks"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+aiosignal = [
{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"
-
-[[package]]
-name = "anyio"
-version = "3.6.2"
-description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "dev"
-optional = false
-python-versions = ">=3.6.2"
-files = [
+anyio = [
{file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
]
-
-[package.dependencies]
-idna = ">=2.8"
-sniffio = ">=1.1"
-typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
-
-[package.extras]
-doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
-test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
-trio = ["trio (>=0.16,<0.22)"]
-
-[[package]]
-name = "async-timeout"
-version = "4.0.2"
-description = "Timeout context manager for asyncio programs"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
-
-[package.dependencies]
-typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
-
-[[package]]
-name = "asynctest"
-version = "0.13.0"
-description = "Enhance the standard unittest package with features for testing asyncio libraries"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-files = [
+asynctest = [
{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"
-version = "22.2.0"
-description = "Classes Without Boilerplate"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+attrs = [
{file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
{file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
]
-
-[package.extras]
-cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
-dev = ["attrs[docs,tests]"]
-docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
-tests = ["attrs[tests-no-zope]", "zope.interface"]
-tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
-
-[[package]]
-name = "black"
-version = "22.8.0"
-description = "The uncompromising code formatter."
-category = "dev"
-optional = false
-python-versions = ">=3.6.2"
-files = [
- {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"},
- {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"},
- {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"},
- {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"},
- {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"},
- {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"},
- {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"},
- {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"},
- {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"},
- {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"},
- {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"},
- {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"},
- {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"},
- {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"},
- {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"},
- {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"},
- {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"},
- {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"},
- {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"},
- {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"},
- {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"},
- {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
- {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
+black = [
+ {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"},
+ {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"},
+ {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"},
+ {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"},
+ {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"},
+ {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"},
+ {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"},
+ {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"},
+ {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"},
+ {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"},
+ {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"},
+ {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"},
+ {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"},
+ {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"},
+ {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"},
+ {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"},
+ {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"},
+ {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"},
+ {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"},
+ {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"},
+ {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"},
+ {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"},
+ {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"},
+ {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"},
+ {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"},
]
-
-[package.dependencies]
-click = ">=8.0.0"
-mypy-extensions = ">=0.4.3"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
-typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
-typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
-
-[[package]]
-name = "cached-property"
-version = "1.5.2"
-description = "A decorator for caching properties in classes."
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
+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"},
]
-
-[[package]]
-name = "certifi"
-version = "2022.12.7"
-description = "Python package for providing Mozilla's CA Bundle."
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
+certifi = [
{file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
{file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
]
-
-[[package]]
-name = "cfgv"
-version = "3.3.1"
-description = "Validate configuration and produce human readable error messages."
-category = "dev"
-optional = false
-python-versions = ">=3.6.1"
-files = [
+cfgv = [
{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"
-version = "2.1.1"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
-optional = false
-python-versions = ">=3.6.0"
-files = [
+charset-normalizer = [
{file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
{file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
]
-
-[package.extras]
-unicode-backport = ["unicodedata2"]
-
-[[package]]
-name = "click"
-version = "8.1.3"
-description = "Composable command line interface toolkit"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
-
-[package.dependencies]
-colorama = {version = "*", markers = "platform_system == \"Windows\""}
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
-
-[[package]]
-name = "colorama"
-version = "0.4.6"
-description = "Cross-platform colored terminal text."
-category = "main"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-files = [
+colorama = [
{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"
-version = "1.4.4"
-description = "Simple library for color and formatting to terminal"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
+colored = [
{file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"},
]
-
-[[package]]
-name = "coverage"
-version = "7.1.0"
-description = "Code coverage measurement for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+coverage = [
{file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"},
{file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"},
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"},
@@ -392,61 +1259,19 @@ files = [
{file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"},
{file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"},
]
-
-[package.extras]
-toml = ["tomli"]
-
-[[package]]
-name = "distlib"
-version = "0.3.6"
-description = "Distribution utilities"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
+distlib = [
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.1.0"
-description = "Backport of PEP 654 (exception groups)"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+exceptiongroup = [
{file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
{file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
-[[package]]
-name = "filelock"
-version = "3.9.0"
-description = "A platform independent file lock."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+filelock = [
{file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"},
{file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"},
]
-
-[package.extras]
-docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
-testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
-
-[[package]]
-name = "frozenlist"
-version = "1.3.3"
-description = "A list-like structure which implements collections.abc.MutableSequence"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+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"},
@@ -522,266 +1347,63 @@ files = [
{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"
-version = "2.1.0"
-description = "Copy your docs directly to the gh-pages branch."
-category = "main"
-optional = false
-python-versions = "*"
-files = [
+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"},
]
-
-[package.dependencies]
-python-dateutil = ">=2.8.1"
-
-[package.extras]
-dev = ["flake8", "markdown", "twine", "wheel"]
-
-[[package]]
-name = "gitdb"
-version = "4.0.10"
-description = "Git Object Database"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+gitdb = [
{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"
-
-[[package]]
-name = "gitpython"
-version = "3.1.30"
-description = "GitPython is a python library used to interact with Git repositories"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+gitpython = [
{file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"},
{file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"},
]
-
-[package.dependencies]
-gitdb = ">=4.0.1,<5"
-typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""}
-
-[[package]]
-name = "griffe"
-version = "0.25.4"
-description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+griffe = [
{file = "griffe-0.25.4-py3-none-any.whl", hash = "sha256:919f935a358b31074d16e324e26b041883c60a8cf10504655e394afc3a7caad8"},
{file = "griffe-0.25.4.tar.gz", hash = "sha256:f190edf8ef58d43c856d2d6761ec324a043ff60deb8c14359263571e8b91fe68"},
]
-
-[package.dependencies]
-cached-property = {version = "*", markers = "python_version < \"3.8\""}
-colorama = ">=0.4"
-
-[package.extras]
-async = ["aiofiles (>=0.7,<1.0)"]
-
-[[package]]
-name = "h11"
-version = "0.14.0"
-description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+h11 = [
{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\""}
-
-[[package]]
-name = "httpcore"
-version = "0.16.3"
-description = "A minimal low-level HTTP client."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+httpcore = [
{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"
-certifi = "*"
-h11 = ">=0.13,<0.15"
-sniffio = ">=1.0.0,<2.0.0"
-
-[package.extras]
-http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
-
-[[package]]
-name = "httpx"
-version = "0.23.3"
-description = "The next generation HTTP client."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+httpx = [
{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 = "*"
-httpcore = ">=0.15.0,<0.17.0"
-rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
-sniffio = "*"
-
-[package.extras]
-brotli = ["brotli", "brotlicffi"]
-cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
-http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
-
-[[package]]
-name = "identify"
-version = "2.5.15"
-description = "File identification library for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "identify-2.5.15-py2.py3-none-any.whl", hash = "sha256:1f4b36c5f50f3f950864b2a047308743f064eaa6f6645da5e5c780d1c7125487"},
- {file = "identify-2.5.15.tar.gz", hash = "sha256:c22aa206f47cc40486ecf585d27ad5f40adbfc494a3fa41dc3ed0499a23b123f"},
+identify = [
+ {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"},
+ {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"},
]
-
-[package.extras]
-license = ["ukkonen"]
-
-[[package]]
-name = "idna"
-version = "3.4"
-description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-files = [
+idna = [
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
-
-[[package]]
-name = "importlib-metadata"
-version = "4.13.0"
-description = "Read metadata from Python packages"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+importlib-metadata = [
{file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"},
{file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"},
]
-
-[package.dependencies]
-typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
-perf = ["ipython"]
-testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
-
-[[package]]
-name = "iniconfig"
-version = "2.0.0"
-description = "brain-dead simple config-ini parsing"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+iniconfig = [
{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"
-version = "3.0.3"
-description = "A very fast and expressive template engine."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+jinja2 = [
{file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
]
-
-[package.dependencies]
-MarkupSafe = ">=2.0"
-
-[package.extras]
-i18n = ["Babel (>=2.7)"]
-
-[[package]]
-name = "markdown"
-version = "3.3.7"
-description = "Python implementation of Markdown."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+markdown = [
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
]
-
-[package.dependencies]
-importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
-
-[package.extras]
-testing = ["coverage", "pyyaml"]
-
-[[package]]
-name = "markdown-it-py"
-version = "2.1.0"
-description = "Python port of markdown-it. Markdown parsing, done right!"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+markdown-it-py = [
{file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"},
{file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"},
]
-
-[package.dependencies]
-mdurl = ">=0.1,<1.0"
-typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
-
-[package.extras]
-benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"]
-code-style = ["pre-commit (==2.6)"]
-compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"]
-linkify = ["linkify-it-py (>=1.0,<2.0)"]
-plugins = ["mdit-py-plugins"]
-profiling = ["gprof2dot"]
-rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
-testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
-
-[[package]]
-name = "markupsafe"
-version = "2.1.2"
-description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+markupsafe = [
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
@@ -833,196 +1455,46 @@ files = [
{file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
{file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
]
-
-[[package]]
-name = "mdurl"
-version = "0.1.2"
-description = "Markdown URL utilities"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+mdurl = [
{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"
-version = "1.3.4"
-description = "A deep merge function for π."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+mergedeep = [
{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.4.2"
-description = "Project documentation with Markdown."
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+mkdocs = [
{file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"},
{file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"},
]
-
-[package.dependencies]
-click = ">=7.0"
-colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
-ghp-import = ">=1.0"
-importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
-jinja2 = ">=2.11.1"
-markdown = ">=3.2.1,<3.4"
-mergedeep = ">=1.3.4"
-packaging = ">=20.5"
-pyyaml = ">=5.1"
-pyyaml-env-tag = ">=0.1"
-typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""}
-watchdog = ">=2.0"
-
-[package.extras]
-i18n = ["babel (>=2.9.0)"]
-min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
-
-[[package]]
-name = "mkdocs-autorefs"
-version = "0.4.1"
-description = "Automatically link across pages in MkDocs."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+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"},
]
-
-[package.dependencies]
-Markdown = ">=3.3"
-mkdocs = ">=1.1"
-
-[[package]]
-name = "mkdocs-exclude"
-version = "1.0.2"
-description = "A mkdocs plugin that lets you exclude files or trees."
-category = "main"
-optional = false
-python-versions = "*"
-files = [
+mkdocs-exclude = [
{file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"},
]
-
-[package.dependencies]
-mkdocs = "*"
-
-[[package]]
-name = "mkdocs-material"
-version = "8.5.11"
-description = "Documentation that simply works"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+mkdocs-material = [
{file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"},
{file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"},
]
-
-[package.dependencies]
-jinja2 = ">=3.0.2"
-markdown = ">=3.2"
-mkdocs = ">=1.4.0"
-mkdocs-material-extensions = ">=1.1"
-pygments = ">=2.12"
-pymdown-extensions = ">=9.4"
-requests = ">=2.26"
-
-[[package]]
-name = "mkdocs-material-extensions"
-version = "1.1.1"
-description = "Extension pack for Python Markdown and MkDocs Material."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+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"},
]
-
-[[package]]
-name = "mkdocs-rss-plugin"
-version = "1.5.0"
-description = "MkDocs plugin which generates a static RSS feed using git log and page.meta."
-category = "dev"
-optional = false
-python-versions = ">=3.7, <4"
-files = [
+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"},
]
-
-[package.dependencies]
-GitPython = ">=3.1,<3.2"
-mkdocs = ">=1.1,<2"
-pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""}
-tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""}
-
-[package.extras]
-dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"]
-doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"]
-
-[[package]]
-name = "mkdocstrings"
-version = "0.20.0"
-description = "Automatic documentation from sources, for MkDocs."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+mkdocstrings = [
{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"
-Markdown = ">=3.3"
-MarkupSafe = ">=1.1"
-mkdocs = ">=1.2"
-mkdocs-autorefs = ">=0.3.1"
-mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""}
-pymdown-extensions = ">=6.3"
-
-[package.extras]
-crystal = ["mkdocstrings-crystal (>=0.3.4)"]
-python = ["mkdocstrings-python (>=0.5.2)"]
-python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
-
-[[package]]
-name = "mkdocstrings-python"
-version = "0.8.3"
-description = "A Python handler for mkdocstrings."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+mkdocstrings-python = [
{file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"},
{file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"},
]
-
-[package.dependencies]
-griffe = ">=0.24"
-mkdocstrings = ">=0.19"
-
-[[package]]
-name = "msgpack"
-version = "1.0.4"
-description = "MessagePack serializer"
-category = "main"
-optional = true
-python-versions = "*"
-files = [
+msgpack = [
{file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
{file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"},
{file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"},
@@ -1076,15 +1548,7 @@ files = [
{file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"},
{file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"},
]
-
-[[package]]
-name = "multidict"
-version = "6.0.4"
-description = "multidict implementation"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+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"},
@@ -1160,15 +1624,7 @@ files = [
{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"
-version = "0.990"
-description = "Optional static typing for Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+mypy = [
{file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"},
{file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"},
{file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"},
@@ -1200,290 +1656,71 @@ files = [
{file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"},
{file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"},
]
-
-[package.dependencies]
-mypy-extensions = ">=0.4.3"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
-typing-extensions = ">=3.10"
-
-[package.extras]
-dmypy = ["psutil (>=4.0)"]
-install-types = ["pip"]
-python2 = ["typed-ast (>=1.4.0,<2)"]
-reports = ["lxml"]
-
-[[package]]
-name = "mypy-extensions"
-version = "0.4.3"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
+mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
-
-[[package]]
-name = "nanoid"
-version = "2.0.0"
-description = "A tiny, secure, URL-friendly, unique string ID generator for Python"
-category = "main"
-optional = false
-python-versions = "*"
-files = [
+nanoid = [
{file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"},
{file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"},
]
-
-[[package]]
-name = "nodeenv"
-version = "1.7.0"
-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 = [
+nodeenv = [
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
]
-
-[package.dependencies]
-setuptools = "*"
-
-[[package]]
-name = "packaging"
-version = "23.0"
-description = "Core utilities for Python packages"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+packaging = [
{file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
{file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
]
-
-[[package]]
-name = "pathspec"
-version = "0.11.0"
-description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+pathspec = [
{file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
]
-
-[[package]]
-name = "platformdirs"
-version = "2.6.2"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+platformdirs = [
{file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
{file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
]
-
-[package.dependencies]
-typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""}
-
-[package.extras]
-docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
-
-[[package]]
-name = "pluggy"
-version = "1.0.0"
-description = "plugin and hook calling mechanisms for python"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
+pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
-
-[package.dependencies]
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
-
-[[package]]
-name = "pre-commit"
-version = "2.21.0"
-description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+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"},
]
-
-[package.dependencies]
-cfgv = ">=2.0.0"
-identify = ">=1.0.0"
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
-nodeenv = ">=0.11.1"
-pyyaml = ">=5.1"
-virtualenv = ">=20.10.0"
-
-[[package]]
-name = "pygments"
-version = "2.14.0"
-description = "Pygments is a syntax highlighting package written in Python."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+pygments = [
{file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
{file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
]
-
-[package.extras]
-plugins = ["importlib-metadata"]
-
-[[package]]
-name = "pymdown-extensions"
-version = "9.9.2"
-description = "Extension pack for Python Markdown."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+pymdown-extensions = [
{file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"},
{file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"},
]
-
-[package.dependencies]
-markdown = ">=3.2"
-
-[[package]]
-name = "pytest"
-version = "7.2.1"
-description = "pytest: simple powerful testing with Python"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+pytest = [
{file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"},
{file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"},
]
-
-[package.dependencies]
-attrs = ">=19.2.0"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=0.12,<2.0"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-
-[package.extras]
-testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
-
-[[package]]
-name = "pytest-aiohttp"
-version = "1.0.4"
-description = "Pytest plugin for aiohttp support"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+pytest-aiohttp = [
{file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"},
{file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"},
]
-
-[package.dependencies]
-aiohttp = ">=3.8.1"
-pytest = ">=6.1.0"
-pytest-asyncio = ">=0.17.2"
-
-[package.extras]
-testing = ["coverage (==6.2)", "mypy (==0.931)"]
-
-[[package]]
-name = "pytest-asyncio"
-version = "0.20.3"
-description = "Pytest support for asyncio"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+pytest-asyncio = [
{file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"},
{file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"},
]
-
-[package.dependencies]
-pytest = ">=6.1.0"
-typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
-
-[package.extras]
-docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
-testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
-
-[[package]]
-name = "pytest-cov"
-version = "2.12.1"
-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 = [
+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"},
]
-
-[package.dependencies]
-coverage = ">=5.2.1"
-pytest = ">=4.6"
-toml = "*"
-
-[package.extras]
-testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
-
-[[package]]
-name = "python-dateutil"
-version = "2.8.2"
-description = "Extensions to the standard Python datetime module"
-category = "main"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-files = [
+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"},
]
-
-[package.dependencies]
-six = ">=1.5"
-
-[[package]]
-name = "pytz"
-version = "2022.7.1"
-description = "World timezone definitions, modern and historical"
-category = "dev"
-optional = false
-python-versions = "*"
-files = [
+pytz = [
{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"
-version = "6.0"
-description = "YAML parser and emitter for Python"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
@@ -1525,159 +1762,43 @@ files = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
-
-[[package]]
-name = "pyyaml-env-tag"
-version = "0.1"
-description = "A custom YAML tag for referencing environment variables in YAML files. "
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+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"},
]
-
-[package.dependencies]
-pyyaml = "*"
-
-[[package]]
-name = "requests"
-version = "2.28.2"
-description = "Python HTTP for Humans."
-category = "dev"
-optional = false
-python-versions = ">=3.7, <4"
-files = [
+requests = [
{file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
{file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
]
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-charset-normalizer = ">=2,<4"
-idna = ">=2.5,<4"
-urllib3 = ">=1.21.1,<1.27"
-
-[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.2.0"
-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 = [
- {file = "rich-13.2.0-py3-none-any.whl", hash = "sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003"},
- {file = "rich-13.2.0.tar.gz", hash = "sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5"},
+rich = [
+ {file = "rich-13.3.1-py3-none-any.whl", hash = "sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9"},
+ {file = "rich-13.3.1.tar.gz", hash = "sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f"},
]
-
-[package.dependencies]
-markdown-it-py = ">=2.1.0,<3.0.0"
-pygments = ">=2.6.0,<3.0.0"
-typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
-
-[package.extras]
-jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
-
-[[package]]
-name = "setuptools"
-version = "66.1.1"
-description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "setuptools-66.1.1-py3-none-any.whl", hash = "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b"},
- {file = "setuptools-66.1.1.tar.gz", hash = "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8"},
+setuptools = [
+ {file = "setuptools-67.1.0-py3-none-any.whl", hash = "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378"},
+ {file = "setuptools-67.1.0.tar.gz", hash = "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300"},
]
-
-[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 (<5)", "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-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "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 = "main"
-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.0"
-description = "A pure Python implementation of a sliding window memory map manager"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
+smmap = [
{file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
{file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
]
-
-[[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 = "time-machine"
-version = "2.9.0"
-description = "Travel through time in your tests."
-category = "dev"
-optional = false
-python-versions = ">=3.7"
-files = [
+time-machine = [
{file = "time-machine-2.9.0.tar.gz", hash = "sha256:60222d43f6e93a926adc36ed37a54bc8e4d0d8d1c4d449096afcfe85086129c2"},
{file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fd72c0b2e7443fff6e4481991742b72c17f73735e5fdd176406ca48df187a5c9"},
{file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5657e0e6077cf15b37f0d8cf78e868113bbb3ecccc60064c40fe52d8166ca8b1"},
@@ -1732,42 +1853,15 @@ files = [
{file = "time_machine-2.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:cc6bf01211b5ea40f633d5502c5aa495b415ebaff66e041820997dae70a508e1"},
{file = "time_machine-2.9.0-cp39-cp39-win_arm64.whl", hash = "sha256:3ce445775fcf7cb4040cfdba4b7c4888e7fd98bbcccfe1dc3fa8a798ed1f1d24"},
]
-
-[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.4"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
+typed-ast = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
@@ -1793,78 +1887,23 @@ files = [
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
-
-[[package]]
-name = "typing-extensions"
-version = "4.4.0"
-description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+typing-extensions = [
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
]
-
-[[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 = "urllib3"
-version = "1.26.14"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-files = [
+urllib3 = [
{file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"},
{file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"},
]
-
-[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-
-[[package]]
-name = "virtualenv"
-version = "20.17.1"
-description = "Virtual Python Environment builder"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-files = [
+virtualenv = [
{file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"},
{file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"},
]
-
-[package.dependencies]
-distlib = ">=0.3.6,<1"
-filelock = ">=3.4.1,<4"
-importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""}
-platformdirs = ">=2.4,<3"
-
-[package.extras]
-docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
-testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
-
-[[package]]
-name = "watchdog"
-version = "2.2.1"
-description = "Filesystem events monitoring"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
+watchdog = [
{file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"},
{file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"},
{file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"},
@@ -1894,18 +1933,7 @@ files = [
{file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"},
{file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"},
]
-
-[package.extras]
-watchmedo = ["PyYAML (>=3.10)"]
-
-[[package]]
-name = "yarl"
-version = "1.8.2"
-description = "Yet another URL library"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
+yarl = [
{file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"},
{file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"},
{file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"},
@@ -1981,32 +2009,7 @@ files = [
{file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"},
{file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"},
]
-
-[package.dependencies]
-idna = ">=2.0"
-multidict = ">=4.0"
-typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
-
-[[package]]
-name = "zipp"
-version = "3.11.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"},
- {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"},
+zipp = [
+ {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"},
+ {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"},
]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
-testing = ["flake8 (<5)", "func-timeout", "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)"]
-
-[extras]
-dev = ["aiohttp", "click", "msgpack"]
-
-[metadata]
-lock-version = "2.0"
-python-versions = "^3.7"
-content-hash = "b70dc64a3c9e7a7b765252f5dd1a5de8ed6efacd0695cde32ff983b14ec55ca6"
diff --git a/pyproject.toml b/pyproject.toml
index 6d283c933..87338775a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,7 +49,7 @@ dev = ["aiohttp", "click", "msgpack"]
[tool.poetry.group.dev.dependencies]
pytest = "^7.1.3"
-black = "^22.3.0,<22.10.0" # macos wheel issue on 22.10
+black = "^23.1.0"
mypy = "^0.990"
pytest-cov = "^2.12.1"
mkdocs = "^1.3.0"
diff --git a/src/textual/_animator.py b/src/textual/_animator.py
index 330ea2dfd..6ace3dcff 100644
--- a/src/textual/_animator.py
+++ b/src/textual/_animator.py
@@ -322,7 +322,6 @@ class Animator:
)
if animation is None:
-
if not isinstance(value, (int, float)) and not isinstance(
value, Animatable
):
diff --git a/src/textual/_parser.py b/src/textual/_parser.py
index 0a725c85d..5163a6014 100644
--- a/src/textual/_parser.py
+++ b/src/textual/_parser.py
@@ -79,7 +79,6 @@ class Parser(Generic[T]):
self._awaiting = next(self._gen)
def feed(self, data: str) -> Iterable[T]:
-
if self._eof:
raise ParseError("end of file reached") from None
if not data:
@@ -104,7 +103,6 @@ class Parser(Generic[T]):
yield popleft()
while pos < data_size or isinstance(self._awaiting, _PeekBuffer):
-
_awaiting = self._awaiting
if isinstance(_awaiting, _Read1):
self._awaiting = self._gen.send(data[pos : pos + 1])
diff --git a/src/textual/_sleep.py b/src/textual/_sleep.py
index cb632e899..bb931e062 100644
--- a/src/textual/_sleep.py
+++ b/src/textual/_sleep.py
@@ -38,7 +38,6 @@ class Sleeper(Thread):
async def check_sleeps() -> None:
-
sleeper = Sleeper()
sleeper.start()
diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py
index a874b287b..4a8a74208 100644
--- a/src/textual/_styles_cache.py
+++ b/src/textual/_styles_cache.py
@@ -10,7 +10,7 @@ from rich.style import Style
from ._border import get_box, render_row
from ._filter import LineFilter
from ._opacity import _apply_opacity
-from ._segment_tools import line_crop, line_pad, line_trim
+from ._segment_tools import line_pad, line_trim
from ._typing import TypeAlias
from .color import Color
from .geometry import Region, Size, Spacing
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
from .css.styles import StylesBase
from .widget import Widget
-RenderLineCallback: TypeAlias = Callable[[int], List[Segment]]
+RenderLineCallback: TypeAlias = Callable[[int], Strip]
@lru_cache(1024 * 8)
@@ -212,7 +212,7 @@ class StylesCache:
padding: Spacing,
base_background: Color,
background: Color,
- render_content_line: RenderLineCallback,
+ render_content_line: Callable[[int], Strip],
) -> Strip:
"""Render a styled line.
@@ -313,6 +313,7 @@ class StylesCache:
content_y = y - gutter.top
if content_y < content_height:
line = render_content_line(y - gutter.top)
+ line = line.adjust_cell_length(content_width)
else:
line = [make_blank(content_width, inner)]
if inner:
diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py
index b97a1abd1..7111fca50 100644
--- a/src/textual/_xterm_parser.py
+++ b/src/textual/_xterm_parser.py
@@ -92,7 +92,6 @@ class XTermParser(Parser[events.Event]):
return None
def parse(self, on_token: TokenCallback) -> Generator[Awaitable, str, None]:
-
ESC = "\x1b"
read1 = self.read1
sequence_to_key_events = self._sequence_to_key_events
@@ -161,7 +160,6 @@ class XTermParser(Parser[events.Event]):
# Look ahead through the suspected escape sequence for a match
while True:
-
# If we run into another ESC at this point, then we've failed
# to find a match, and should issue everything we've seen within
# the suspected sequence as Key events instead.
diff --git a/src/textual/cli/previews/colors.py b/src/textual/cli/previews/colors.py
index e25c70d7f..3ae0eb669 100644
--- a/src/textual/cli/previews/colors.py
+++ b/src/textual/cli/previews/colors.py
@@ -30,7 +30,6 @@ class Content(Vertical):
class ColorsView(Vertical):
def compose(self) -> ComposeResult:
-
LEVELS = [
"darken-3",
"darken-2",
@@ -42,7 +41,6 @@ class ColorsView(Vertical):
]
for color_name in ColorSystem.COLOR_NAMES:
-
items: list[Widget] = [Label(f'"{color_name}"')]
for level in LEVELS:
color = f"{color_name}-{level}" if level else color_name
diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py
index 14f1e5404..ff66e9c84 100644
--- a/src/textual/css/_styles_builder.py
+++ b/src/textual/css/_styles_builder.py
@@ -434,7 +434,6 @@ class StylesBuilder:
process_padding_left = _process_space_partial
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
-
border_type: EdgeType = "solid"
border_color = Color(0, 255, 0)
diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py
index 7bfe1514c..1b98a56f6 100644
--- a/src/textual/css/parse.py
+++ b/src/textual/css/parse.py
@@ -36,7 +36,6 @@ SELECTOR_MAP: dict[str, tuple[SelectorType, Specificity3]] = {
@lru_cache(maxsize=1024)
def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
-
if not css_selectors.strip():
return ()
diff --git a/src/textual/css/query.py b/src/textual/css/query.py
index 3f00ee718..7e5c149cc 100644
--- a/src/textual/css/query.py
+++ b/src/textual/css/query.py
@@ -72,7 +72,6 @@ class DOMQuery(Generic[QueryType]):
exclude: str | None = None,
parent: DOMQuery | None = None,
) -> None:
-
self._node = node
self._nodes: list[QueryType] | None = None
self._filters: list[tuple[SelectorSet, ...]] = (
diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py
index 0bbb66823..5f7ff7235 100644
--- a/src/textual/css/styles.py
+++ b/src/textual/css/styles.py
@@ -327,7 +327,6 @@ class StylesBase(ABC):
# Check we are animating a Scalar or Scalar offset
if isinstance(start_value, (Scalar, ScalarOffset)):
-
# If destination is a number, we can convert that to a scalar
if isinstance(value, (int, float)):
value = Scalar(value, Unit.CELLS, Unit.CELLS)
diff --git a/src/textual/devtools/service.py b/src/textual/devtools/service.py
index 6e55456d1..cea646739 100644
--- a/src/textual/devtools/service.py
+++ b/src/textual/devtools/service.py
@@ -236,7 +236,6 @@ class ClientHandler:
message = cast(WSMessage, message)
if message.type in (WSMsgType.TEXT, WSMsgType.BINARY):
-
try:
if isinstance(message.data, bytes):
message = msgpack.unpackb(message.data)
diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py
index 2d1b5e1a0..09260be6f 100644
--- a/src/textual/drivers/linux_driver.py
+++ b/src/textual/drivers/linux_driver.py
@@ -92,7 +92,6 @@ class LinuxDriver(Driver):
self.console.file.flush()
def start_application_mode(self):
-
loop = asyncio.get_running_loop()
def send_size_event():
@@ -123,7 +122,6 @@ class LinuxDriver(Driver):
except termios.error:
pass
else:
-
newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])
@@ -208,7 +206,6 @@ class LinuxDriver(Driver):
pass # TODO: log
def _run_input_thread(self, loop) -> None:
-
selector = selectors.DefaultSelector()
selector.register(self.fileno, selectors.EVENT_READ)
diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py
index 0899f65ef..26b155f06 100644
--- a/src/textual/drivers/windows_driver.py
+++ b/src/textual/drivers/windows_driver.py
@@ -58,7 +58,6 @@ class WindowsDriver(Driver):
self.console.file.write("\x1b[?2004l")
def start_application_mode(self) -> None:
-
loop = asyncio.get_running_loop()
self._restore_console = win32.enable_application_mode()
diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py
index ac643663e..2d6736b08 100644
--- a/src/textual/layouts/horizontal.py
+++ b/src/textual/layouts/horizontal.py
@@ -18,7 +18,6 @@ class HorizontalLayout(Layout):
def arrange(
self, parent: Widget, children: list[Widget], size: Size
) -> ArrangeResult:
-
placements: list[WidgetPlacement] = []
add_placement = placements.append
x = max_height = Fraction(0)
diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py
index ccdcd1be3..f26e531c0 100644
--- a/src/textual/layouts/vertical.py
+++ b/src/textual/layouts/vertical.py
@@ -19,7 +19,6 @@ class VerticalLayout(Layout):
def arrange(
self, parent: Widget, children: list[Widget], size: Size
) -> ArrangeResult:
-
placements: list[WidgetPlacement] = []
add_placement = placements.append
parent_size = parent.outer_size
diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py
index c469b381d..d79e0764c 100644
--- a/src/textual/message_pump.py
+++ b/src/textual/message_pump.py
@@ -398,7 +398,6 @@ class MessagePump(metaclass=MessagePumpMeta):
self.app._handle_exception(error)
break
finally:
-
self._message_queue.task_done()
current_time = time()
diff --git a/src/textual/reactive.py b/src/textual/reactive.py
index 048bb56e1..93139501d 100644
--- a/src/textual/reactive.py
+++ b/src/textual/reactive.py
@@ -170,7 +170,6 @@ class Reactive(Generic[ReactiveType]):
getattr(obj, "__computes", []).clear()
def __set_name__(self, owner: Type[MessageTarget], name: str) -> None:
-
# Check for compute method
if hasattr(owner, f"compute_{name}"):
# Compute methods are stored in a list called `__computes`
diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py
index ec3cd4790..ad8a99e7e 100644
--- a/src/textual/scrollbar.py
+++ b/src/textual/scrollbar.py
@@ -92,7 +92,6 @@ class ScrollBarRender:
back_color: Color = Color.parse("#555555"),
bar_color: Color = Color.parse("bright_magenta"),
) -> Segments:
-
if vertical:
bars = ["β", "β", "β", "β", "β
", "β", "β", " "]
else:
@@ -190,7 +189,6 @@ class ScrollBarRender:
@rich.repr.auto
class ScrollBar(Widget):
-
renderer: ClassVar[Type[ScrollBarRender]] = ScrollBarRender
"""The class used for rendering scrollbars.
This can be overriden and set to a ScrollBarRender-derived class
diff --git a/src/textual/strip.py b/src/textual/strip.py
index 95cf03038..c10a649a9 100644
--- a/src/textual/strip.py
+++ b/src/textual/strip.py
@@ -6,7 +6,7 @@ from typing import Iterable, Iterator
import rich.repr
from rich.cells import cell_len, set_cell_size
from rich.segment import Segment
-from rich.style import Style
+from rich.style import Style, StyleType
from ._cache import FIFOCache
from ._filter import LineFilter
@@ -49,7 +49,7 @@ class Strip:
return "".join(segment.text for segment in self._segments)
@classmethod
- def blank(cls, cell_length: int, style: Style | None) -> Strip:
+ def blank(cls, cell_length: int, style: StyleType | None = None) -> Strip:
"""Create a blank strip.
Args:
@@ -59,7 +59,8 @@ class Strip:
Returns:
New strip.
"""
- return cls([Segment(" " * cell_length, style)], cell_length)
+ segment_style = Style.parse(style) if isinstance(style, str) else style
+ return cls([Segment(" " * cell_length, segment_style)], cell_length)
@classmethod
def from_lines(
@@ -135,6 +136,23 @@ class Strip:
self._segments == strip._segments and self.cell_length == strip.cell_length
)
+ def extend_cell_length(self, cell_length: int, style: Style | None = None) -> Strip:
+ """Extend the cell length if it is less than the given value.
+
+ Args:
+ cell_length: Required minimum cell length.
+ style: Style for padding if the cell length is extended.
+
+ Returns:
+ A new Strip.
+ """
+ if self.cell_length < cell_length:
+ missing_space = cell_length - self.cell_length
+ segments = self._segments + [Segment(" " * missing_space, style)]
+ return Strip(segments, cell_length)
+ else:
+ return self
+
def adjust_cell_length(self, cell_length: int, style: Style | None = None) -> Strip:
"""Adjust the cell length, possibly truncating or extending.
diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py
index 0be163d67..40282e03f 100644
--- a/src/textual/widgets/_directory_tree.py
+++ b/src/textual/widgets/_directory_tree.py
@@ -88,7 +88,6 @@ class DirectoryTree(Tree[DirEntry]):
id: str | None = None,
classes: str | None = None,
) -> None:
-
self.path = path
super().__init__(
path,
diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py
index ee47b3801..278ab3a88 100644
--- a/src/textual/widgets/_input.py
+++ b/src/textual/widgets/_input.py
@@ -28,7 +28,6 @@ class _InputRenderable:
def __rich_console__(
self, console: "Console", options: "ConsoleOptions"
) -> "RenderResult":
-
input = self.input
result = input._value
if input._cursor_at_end:
diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py
index 7d2059b3d..a9698a954 100644
--- a/src/textual/widgets/_static.py
+++ b/src/textual/widgets/_static.py
@@ -57,7 +57,6 @@ class Static(Widget, inherit_bindings=False):
id: str | None = None,
classes: str | None = None,
) -> None:
-
super().__init__(name=name, id=id, classes=classes)
self.expand = expand
self.shrink = shrink
diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py
index 1c8ad17a0..1c116c0b4 100644
--- a/src/textual/widgets/_text_log.py
+++ b/src/textual/widgets/_text_log.py
@@ -14,7 +14,6 @@ from ..reactive import var
from ..geometry import Size, Region
from ..scroll_view import ScrollView
from .._cache import LRUCache
-from .._segment_tools import line_crop
from ..strip import Strip
@@ -160,7 +159,6 @@ class TextLog(ScrollView, can_focus=True):
return lines
def _render_line(self, y: int, scroll_x: int, width: int) -> Strip:
-
if y >= len(self.lines):
return Strip.blank(width, self.rich_style)
diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py
index 4145e9eb0..c7bf07781 100644
--- a/src/textual/widgets/_tree.py
+++ b/src/textual/widgets/_tree.py
@@ -281,7 +281,6 @@ class TreeNode(Generic[TreeDataType]):
class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
-
BINDINGS: ClassVar[list[BindingType]] = [
Binding("enter", "select_cursor", "Select", show=False),
Binding("space", "toggle_node", "Toggle", show=False),
diff --git a/src/textual/widgets/_welcome.py b/src/textual/widgets/_welcome.py
index 3c8a6d1be..0ad5032dc 100644
--- a/src/textual/widgets/_welcome.py
+++ b/src/textual/widgets/_welcome.py
@@ -24,11 +24,10 @@ Where the fear has gone there will be nothing. Only I will remain."
class Welcome(Static):
-
DEFAULT_CSS = """
Welcome {
width: 100%;
- height: 100%;
+ height: 100%;
background: $surface;
}
@@ -44,7 +43,7 @@ class Welcome(Static):
Welcome #close {
dock: bottom;
- width: 100%;
+ width: 100%;
}
"""
diff --git a/tests/test_strip.py b/tests/test_strip.py
index 0dc082c2b..40f3975fe 100644
--- a/tests/test_strip.py
+++ b/tests/test_strip.py
@@ -83,6 +83,14 @@ def test_adjust_cell_length():
)
+def test_extend_cell_length():
+ strip = Strip([Segment("foo"), Segment("bar")])
+ assert strip.extend_cell_length(3).text == "foobar"
+ assert strip.extend_cell_length(6).text == "foobar"
+ assert strip.extend_cell_length(7).text == "foobar "
+ assert strip.extend_cell_length(9).text == "foobar "
+
+
def test_simplify():
assert Strip([Segment("foo"), Segment("bar")]).simplify() == Strip(
[Segment("foobar")]
diff --git a/tests/test_styles_cache.py b/tests/test_styles_cache.py
index fdeb72dd6..01045e322 100644
--- a/tests/test_styles_cache.py
+++ b/tests/test_styles_cache.py
@@ -10,7 +10,7 @@ from textual.geometry import Region, Size
from textual.strip import Strip
-def _extract_content(lines: list[list[Segment]]):
+def _extract_content(lines: list[Strip]) -> list[str]:
"""Extract the text content from lines."""
content = ["".join(segment.text for segment in line) for line in lines]
return content
@@ -28,9 +28,9 @@ def test_set_dirty():
def test_no_styles():
"""Test that empty style returns the content un-altered"""
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
styles = Styles()
cache = StylesCache()
@@ -54,9 +54,9 @@ def test_no_styles():
def test_border():
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
styles = Styles()
styles.border = ("heavy", "white")
@@ -85,9 +85,9 @@ def test_border():
def test_padding():
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
styles = Styles()
styles.padding = 1
@@ -116,9 +116,9 @@ def test_padding():
def test_padding_border():
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
styles = Styles()
styles.padding = 1
@@ -150,9 +150,9 @@ def test_padding_border():
def test_outline():
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
styles = Styles()
styles.outline = ("heavy", "white")
@@ -177,9 +177,9 @@ def test_outline():
def test_crop():
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
styles = Styles()
styles.padding = 1
@@ -203,17 +203,17 @@ def test_crop():
assert text_content == expected_text
-def test_dirty_cache():
+def test_dirty_cache() -> None:
"""Check that we only render content once or if it has been marked as dirty."""
content = [
- [Segment("foo")],
- [Segment("bar")],
- [Segment("baz")],
+ Strip([Segment("foo")]),
+ Strip([Segment("bar")]),
+ Strip([Segment("baz")]),
]
rendered_lines: list[int] = []
- def get_content_line(y: int) -> list[Segment]:
+ def get_content_line(y: int) -> Strip:
rendered_lines.append(y)
return content[y]
@@ -227,11 +227,13 @@ def test_dirty_cache():
Color.parse("blue"),
Color.parse("green"),
get_content_line,
+ Size(3, 3),
)
assert rendered_lines == [0, 1, 2]
del rendered_lines[:]
text_content = _extract_content(lines)
+
expected_text = [
"βββββββ",
"β β",