From f95f10af2881da6dd768a27afae51d757e9c5375 Mon Sep 17 00:00:00 2001 From: Alex Batisse Date: Fri, 26 Jun 2026 18:23:52 +0200 Subject: [PATCH 1/2] feat: Migrate logic to tree sitter parser --- noxfile.py | 2 +- pyproject.toml | 18 +- src/craft_ls/cli.py | 25 +- src/craft_ls/core.py | 669 +++++++++++++++++++--------------------- src/craft_ls/helpers.py | 19 ++ src/craft_ls/parser.py | 203 ++++++++++++ src/craft_ls/server.py | 237 +++++++------- src/craft_ls/types_.py | 66 +--- tests/test_core.py | 48 ++- tests/test_server.py | 83 ++++- uv.lock | 250 ++++++--------- 11 files changed, 886 insertions(+), 734 deletions(-) create mode 100644 src/craft_ls/helpers.py create mode 100644 src/craft_ls/parser.py diff --git a/noxfile.py b/noxfile.py index 594dc53..2bcf57f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -44,7 +44,7 @@ def lint(session: nox.Session) -> None: ) session.run("ruff", "check", "--fix", "src") session.run("ruff", "check", "--fix", "tests") - session.run("mypy", "src") + session.run("zuban", "mypy", "src") @nox.session() diff --git a/pyproject.toml b/pyproject.toml index 43a96a7..803b875 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,14 +14,15 @@ keywords = [ ] requires-python = ">=3.12" dependencies = [ - "pygls>=1.1.0", + "pygls>=2.1.1", "lsprotocol>=2025.0.0", - "jsonschema>=4.23.0", + "jsonschema>=4.26.0", "pyyaml>=6.0.2", "referencing>=0.36.2", "jsonref>=1.1.0", "jsonpath-ng>=1.7.0", - "more-itertools>=10.7.0", + "tree-sitter>=0.25.2", + "tree-sitter-yaml>=0.7.2", ] classifiers = [ "Development Status :: 3 - Alpha", @@ -44,7 +45,7 @@ fmt = [ ] lint = [ "ruff >=0.15.12,<0.16.0", - "mypy >= 1.14.0", + "zuban >= 0.9.0", "types-pyyaml", "types-jsonschema", ] @@ -94,15 +95,8 @@ known-first-party = ["craft_ls"] [tool.mypy] strict = true -[[tool.mypy.overrides]] -module = [ - "jsonref", - "jsonpath_ng" -] -ignore_missing_imports = true - [tool.pyright] -enableReachabilityAnalysis = false # unreliable +enableReachabilityAnalysis = false # unreliable [tool.pytest] log_cli_level = "INFO" diff --git a/src/craft_ls/cli.py b/src/craft_ls/cli.py index 77a6c89..5e3289e 100644 --- a/src/craft_ls/cli.py +++ b/src/craft_ls/cli.py @@ -9,8 +9,11 @@ from lsprotocol import types as lsp -from craft_ls.core import get_diagnostics, get_validator_and_parse, segmentize_nodes -from craft_ls.types_ import ParsedResult +from craft_ls.core import ( + get_diagnostics, + get_validator_from_tree, +) +from craft_ls.parser import parser, yaml_tree_to_dict logging.basicConfig() @@ -19,15 +22,17 @@ def check(file_name: str) -> None: """Report all violations for a file.""" file = Path(file_name) - diagnostics: list[lsp.Diagnostic] = [] - match get_validator_and_parse(file.stem, file.read_text()): - case None: - print(f"Cannot validate '{file}'", file=sys.stderr) - pass + with file.open("rb") as f: + tree = parser.parse(f.read()) - case validator, ParsedResult(instance=instance, nodes=nodes): - segments = segmentize_nodes(nodes) - diagnostics.extend(get_diagnostics(validator, instance, dict(segments))) + validator = get_validator_from_tree(file.stem, tree) + instance = yaml_tree_to_dict(tree) + + if not validator: + print(f"Cannot validate '{file}'", file=sys.stderr) + sys.exit(1) + + diagnostics: list[lsp.Diagnostic] = get_diagnostics(tree, validator, instance) if diagnostics: for diag in diagnostics: diff --git a/src/craft_ls/core.py b/src/craft_ls/core.py index 5879229..2049af7 100644 --- a/src/craft_ls/core.py +++ b/src/craft_ls/core.py @@ -1,64 +1,41 @@ -"""Parser-validator core logic.""" +"""Core logic for crafting LS responses.""" import logging import re -from collections import deque from importlib.resources import files -from itertools import chain -from typing import Iterable, cast +from typing import Any, Iterable, cast import jsonref -import yaml -from jsonpath_ng import parse +import lsprotocol.types as lsp +from jsonpath_ng import parse as jq # zuban: ignore[attr-defined] from jsonschema import Draft202012Validator, ValidationError from jsonschema.exceptions import relevance from jsonschema.protocols import Validator from jsonschema.validators import validator_for -from lsprotocol import types as lsp -from more_itertools import peekable from referencing import Registry, Resource from referencing.jsonschema import DRAFT202012 -from yaml.emitter import EmitterError -from yaml.events import ( - DocumentEndEvent, - Event, - MappingEndEvent, - MappingStartEvent, - ScalarEvent, - SequenceEndEvent, - SequenceStartEvent, - StreamEndEvent, -) -from yaml.scanner import ScannerError -from yaml.tokens import ( - BlockEndToken, - BlockMappingStartToken, - BlockSequenceStartToken, - KeyToken, - ScalarToken, - Token, - ValueToken, -) +from tree_sitter import Node, Tree +from craft_ls.helpers import sanatize_key +from craft_ls.parser import ( + query_charm_type_keys, + query_pairs, + query_snap_base_keys, +) from craft_ls.types_ import ( - CompleteParsedResult, - DocumentNode, - IncompleteParsedResult, MissingTypeCharmcraftValidator, MissingTypeSnapcraftValidator, - ParsedResult, Schema, - YamlDocument, ) SOURCE = "craft-ls" FILE_TYPES = ["snapcraft", "rockcraft", "charmcraft"] MISSING_DESC = "No description to display" +SPECIAL_SYMBOL_PARENTS = {"parts", "apps", "services"} DEFAULT_RANGE = lsp.Range( start=lsp.Position(line=0, character=0), end=lsp.Position(line=0, character=0), ) -SPECIAL_SYMBOL_PARENTS = {"parts", "apps", "services"} logger = logging.getLogger(__name__) @@ -69,7 +46,7 @@ schema = jsonref.loads( files("craft_ls.schemas").joinpath(f"{file_type}.json").read_text() ) - default_validators[file_type] = validator_for(schema)(schema) + default_validators[file_type] = validator_for(schema)(schema) # type: ignore if file_type == "charmcraft": schema = Resource.from_contents( @@ -84,222 +61,156 @@ snapcraft_registry = schema @ Registry() -def get_validator_and_parse( # noqa: C901 - file_stem: str, instance_document: str -) -> tuple[Validator, ParsedResult] | None: - """Get the most appropriate validator for the current document.""" - if file_stem not in FILE_TYPES: - return None +def get_snap_bases(tree: Tree) -> tuple[str | None, str | None]: + """Get the snapcraft base and build-base keys and values.""" + matches = query_snap_base_keys.matches(tree.root_node) + bases = {} - scanned_tokens = parse_tokens(instance_document) + for _, captures in matches: + k_nodes = captures.get("key_node", []) + v_nodes = captures.get("value_node", []) - if file_stem == "rockcraft": - return default_validators[file_stem], scanned_tokens + k_node = k_nodes[0] if k_nodes else None + v_node = v_nodes[0] if v_nodes else None - elif file_stem == "snapcraft": - base = scanned_tokens.instance.get("base", None) - build_base = scanned_tokens.instance.get("build-base", None) - validator: Draft202012Validator | MissingTypeSnapcraftValidator - match base, build_base: - case "core22", _: - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:core22") - .contents - ) - case "core24", _: - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:core24") - .contents - ) - case "core26", _: - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:core26") - .contents - ) - case "bare", "core22": - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:bare22") - .contents - ) - case "bare", "core24": - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:bare24") - .contents - ) - case "bare", "core26": - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:bare26") - .contents - ) - case _, "core22": - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:base22") - .contents - ) - case _, "core24": - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:base24") - .contents - ) - case _, "devel": - validator = Draft202012Validator( - schema=snapcraft_registry.resolver() - .lookup("urn:snapcraft:basedevel") - .contents - ) + if ( + not k_node + or not v_node + or not k_node.text + or not v_node.text + # The base keys are only at depth=0 of the yaml doc + or k_node.start_point.column != 0 + ): + continue - case _: - validator = MissingTypeSnapcraftValidator() + key_name = k_node.text.decode("utf-8").strip() + bases[key_name] = v_node.text.decode("utf-8").strip() - return cast(Validator, validator), scanned_tokens + return bases.get("base", None), bases.get("build-base", None) - else: - # by elimination, file_stem is charmcraft - if scanned_tokens.instance.get("type") != "charm": - return cast(Validator, MissingTypeCharmcraftValidator()), scanned_tokens - validator = Draft202012Validator( - schema=charmcraft_registry.resolver() - .lookup("urn:charmcraft:platformcharm") - .contents - ) - return cast(Validator, validator), scanned_tokens - - -def parse_tokens(instance_document: str) -> ParsedResult: - """Scan the document for yaml tokens.""" - tokens = [] - tokens_iter = yaml.scan(instance_document) - - try: - for event in tokens_iter: - tokens.append(event) - except ScannerError: - instance, events = robust_load(instance_document) - nodes = yaml.compose(events) - return IncompleteParsedResult(tokens=tokens, instance=instance, nodes=nodes) - - instance = cast(YamlDocument, yaml.safe_load(instance_document)) - nodes = yaml.compose(instance_document) - return CompleteParsedResult(tokens=tokens, instance=instance, nodes=nodes) - - -def robust_load(instance_document: str) -> tuple[YamlDocument, list[Event]]: - """Parse the valid portion of the stream and construct a Python object.""" - events = [] - events_iter = yaml.parse(instance_document) - - closing_sequence: deque[Event] = deque() - - try: - for event in events_iter: - match event: - case MappingStartEvent(): - closing_sequence.append(MappingEndEvent()) - case SequenceStartEvent(): - closing_sequence.append(SequenceEndEvent()) - case MappingEndEvent(): - closing_sequence.pop() - case SequenceEndEvent(): - closing_sequence.pop() - - events.append(event) - except ScannerError: - closing_sequence.extendleft([DocumentEndEvent(), StreamEndEvent()]) - - try: - truncated_file = yaml.emit(events + list(reversed(closing_sequence))) - except EmitterError: - closing_sequence.append( - ScalarEvent(anchor=None, tag=None, implicit=(True, False), value="") - ) - truncated_file = yaml.emit(events + list(reversed(closing_sequence))) - return cast(YamlDocument, yaml.safe_load(truncated_file)), truncated_file +def get_charm_type(tree: Tree) -> str | None: + """Get the charmcraft type key and value.""" + matches = query_charm_type_keys.matches(tree.root_node) + charm_type: str | None = None + for _, captures in matches: + k_nodes = captures.get("key_node", []) + v_nodes = captures.get("value_node", []) -def segmentize_nodes( - root: yaml.CollectionNode, -) -> list[tuple[tuple[str, ...], DocumentNode]]: - """Flatten graph into path segments.""" - segments: list[tuple[tuple[str, ...], DocumentNode]] = [] - nodes = list(root.value) + k_node = k_nodes[0] if k_nodes else None + v_node = v_nodes[0] if v_nodes else None - for node_pair in nodes: - segments.extend(_do_segmentize_nodes(*node_pair)) + if ( + not k_node + or not v_node + or not v_node.text + # The base keys are only at depth=0 of the yaml doc + or k_node.start_point.column != 0 + ): + continue - return segments + charm_type = v_node.text.decode("utf-8").strip() + return charm_type -def _do_segmentize_nodes( - first: yaml.CollectionNode, - second: yaml.CollectionNode, - prefix: tuple[str, ...] | None = None, -) -> list[tuple[tuple[str, ...], DocumentNode]]: - """Recursive node segmentation. - Craft tools don't usually go over three levels, so we don't have to worry about recursion limits. - """ - segments = [] - prefix = prefix or () - - match second: - case yaml.ScalarNode(end_mark=selection_end): - current_node = DocumentNode( - value=first.value, - start=first.start_mark, - end=first.end_mark, - selection_end=selection_end, +def get_snapcraft_validator(tree: Tree) -> Validator: + """Get the most appropriate snapcraft validator for the current document.""" + validator: Draft202012Validator | MissingTypeSnapcraftValidator + match get_snap_bases(tree): + case "core22", _: + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:core22") + .contents ) - segments.append((prefix + (str(first.value),), current_node)) - - case yaml.MappingNode(end_mark=selection_end, value=children): - current_node = DocumentNode( - value=first.value, - start=first.start_mark, - end=first.end_mark, - selection_end=selection_end, + case "core24", _: + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:core24") + .contents ) - segments.append((prefix + (str(first.value),), current_node)) - segments.extend( - list( - chain.from_iterable( - [ - _do_segmentize_nodes( - child[0], child[1], prefix=prefix + (str(first.value),) - ) - for child in children - ] - ) - ) + case "core26", _: + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:core26") + .contents ) - - case yaml.SequenceNode(end_mark=selection_end): - print(selection_end.line) - current_node = DocumentNode( - value=first.value, - start=first.start_mark, - end=first.end_mark, - selection_end=selection_end, + case "bare", "core22": + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:bare22") + .contents ) - segments.append((prefix + (str(first.value),), current_node)) - case other: - logger.error(other) + case "bare", "core24": + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:bare24") + .contents + ) + case "bare", "core26": + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:bare26") + .contents + ) + case _, "core22": + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:base22") + .contents + ) + case _, "core24": + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:base24") + .contents + ) + case _, "devel": + validator = Draft202012Validator( + schema=snapcraft_registry.resolver() + .lookup("urn:snapcraft:basedevel") + .contents + ) + + case _: + validator = MissingTypeSnapcraftValidator() + + return cast(Validator, validator) + + +def get_validator_from_tree(file_stem: str, tree: Tree) -> Validator | None: + """Get the most appropriate validator for the current document.""" + if file_stem not in FILE_TYPES: + return None + + if file_stem == "rockcraft": + return default_validators[file_stem] + + elif file_stem == "snapcraft": + validator = get_snapcraft_validator(tree) - return segments + else: + # by elimination, file_stem is charmcraft + if get_charm_type(tree) != "charm": + return cast(Validator, MissingTypeCharmcraftValidator()) + + validator = cast( + Validator, + Draft202012Validator( + schema=charmcraft_registry.resolver() + .lookup("urn:charmcraft:platformcharm") + .contents + ), + ) + return validator def get_diagnostics( + tree: Tree, validator: Validator, - instance: YamlDocument, - segments: dict[tuple[str, ...], DocumentNode], + instance: Any, ) -> list[lsp.Diagnostic]: """Validate a document against its schema.""" diagnostics = [] @@ -307,7 +218,6 @@ def get_diagnostics( for error in validator.iter_errors(instance): if error.context: error = sorted(error.context, key=relevance)[0] - match error: case ValidationError( validator="additionalProperties", absolute_path=path, message=message @@ -319,7 +229,7 @@ def get_diagnostics( ): keys_cleaned = [key.strip(" '") for key in keys.split(",")] ranges = [ - get_diagnostic_range(segments, list(path) + [key]) + get_diagnostic_range(tree, cast(list[str], list(path)) + [key]) for key in keys_cleaned ] @@ -340,13 +250,15 @@ def get_diagnostics( ): pattern = "'(?P.*)' key is mandatory" if path: - range_ = get_diagnostic_range(segments, path) + range_ = get_diagnostic_range(tree, cast(list[str], list(path))) elif (match := re.search(pattern, message or "")) and ( key := match.group("key") ): - # Const errore can be wrongfully handled here with an empty path, + # Const errors can be wrongfully handled here with an empty path, # so we try to handle those cases gracefully - range_ = get_diagnostic_range(segments, list(path) + [key]) + range_ = get_diagnostic_range( + tree, cast(list[str], list(path)) + [key] + ) else: range_ = DEFAULT_RANGE @@ -361,7 +273,7 @@ def get_diagnostics( case ValidationError(absolute_path=path, message=str(message)): path = cast(list[str], path) - range_ = get_diagnostic_range(segments, path) if path else DEFAULT_RANGE + range_ = get_diagnostic_range(tree, path) if path else DEFAULT_RANGE diagnostics.append( lsp.Diagnostic( @@ -379,32 +291,140 @@ def get_diagnostics( return diagnostics -def get_diagnostic_range( - document_segments: dict[tuple[str, ...], DocumentNode], diag_segments: Iterable[str] -) -> lsp.Range: +def get_diagnostic_range(tree: Tree, diag_segments: Iterable[str]) -> lsp.Range: """Link the validation error to the position in the original document.""" - if ( - not diag_segments - or (error_node := document_segments.get(tuple(diag_segments))) is None - ): + segments = list(diag_segments) + if not segments: return DEFAULT_RANGE - range = lsp.Range( - start=lsp.Position( - line=error_node.start.line, - character=error_node.start.column, + segment_idx = 0 + stack = [tree.root_node] + + while stack and segment_idx < len(segments): + current = stack.pop() + target = segments[segment_idx] + + if current.type in ("block_mapping_pair", "flow_pair"): + k = current.child_by_field_name("key") + if k and k.text and k.text.decode("utf-8").strip() == target: + segment_idx += 1 + + # Early exit if this was the last segment + if segment_idx == len(segments): + return lsp.Range( + start=lsp.Position(k.start_point.row, k.start_point.column), + end=lsp.Position(k.end_point.row, k.end_point.column), + ) + + # Then continue the descent in this value subtree + v = current.child_by_field_name("value") + stack = [v] if v else [] + + # Wrong subtree, continue horizontally + continue + + if current.children: + stack.extend(reversed(current.children)) + + return DEFAULT_RANGE + + +def get_immediate_child_pairs(node: Node) -> list[Node]: + """Get the key value pairs that are the level just below a given node.""" + captures = query_pairs.captures(node) + + if not (pairs := captures.get("pair", [])): + return pairs + + # Quite the hack, but I like it + # Immediate children are left aligned on the same col, because yaml is indent based + min_column = min(p.start_point.column for p in pairs) + return [p for p in pairs if p.start_point.column == min_column] + + +def create_symbol(pair: Node) -> lsp.DocumentSymbol | None: + """Convert a tree-sitter pair node or loose scalar key into an LSP DocumentSymbol.""" + key = pair.child_by_field_name("key") + value = pair.child_by_field_name("value") + end_node = pair + + if not key or not key.text: + return None + + name = key.text.decode("utf-8").strip() + if not name or name.startswith(("-", "---", "...")): + return None + + symbol = lsp.DocumentSymbol( + name=name, + kind=lsp.SymbolKind.Key, + range=lsp.Range( + start=lsp.Position(key.start_point.row, key.start_point.column), + end=lsp.Position(end_node.end_point.row, end_node.end_point.column), ), - end=lsp.Position( - line=error_node.selection_end.line, - character=error_node.selection_end.column, + selection_range=lsp.Range( + start=lsp.Position(key.start_point.row, key.start_point.column), + end=lsp.Position(key.end_point.row, key.end_point.column), ), ) - return range + if name in SPECIAL_SYMBOL_PARENTS and value: + children_symbols = [] + for child_pair in get_immediate_child_pairs(value): + if child_symbol := create_symbol(child_pair): + children_symbols.append(child_symbol) + + if children_symbols: + symbol.children = sorted(children_symbols, key=lambda s: s.range.start.line) + + return symbol + + +def list_symbols(tree: Tree) -> list[lsp.DocumentSymbol]: + """List first-level keys and expands special parent to get the second level. + + Simply put, we are only interested in keys up to the second level at most, and only + for things like parts, apps, etc. + """ + symbols = [] + for child_pair in get_immediate_child_pairs(tree.root_node): + child_symbol = create_symbol(child_pair) + if child_symbol: + symbols.append(child_symbol) + + return sorted(symbols, key=lambda s: s.range.start.line) + + +def get_node_path_from_token_position( + tree: Tree, position: lsp.Position +) -> tuple[str, ...] | None: + """Finds the innermost key path tracking down to the current cursor position. -def sanatize_key(key: str) -> str: - """Sanatize key.""" - return re.sub(r"""['"\\*]""", "", key) + To do so, we actually proceed the other way around. We start from the node itself + and navigate through its ancestors. + """ + point = (position.line, position.character) + leaf = tree.root_node.descendant_for_point_range(point, point) + if not leaf: + return None + + path: list[str] = [] + curr = leaf + + while curr: + if curr.type in ("block_mapping_pair", "flow_pair"): + k = curr.child_by_field_name("key") + if k and k.text: + k_str = k.text.decode("utf-8").strip() + if k_str and not k_str.startswith(("-", "---", "...")): + path.append(k_str) + + if (parent := curr.parent) is None: + break + + curr = parent + + return tuple(reversed(path)) if path else None def get_description_from_path(path: Iterable[str | int], schema: Schema) -> str: @@ -422,7 +442,7 @@ def get_description_from_path(path: Iterable[str | int], schema: Schema) -> str: ) query = f"{query}..{sub_query}" query = f"{query}.description|title" - parser = parse(query) + parser = jq(query) candidates = parser.find(schema) if candidates: @@ -431,122 +451,61 @@ def get_description_from_path(path: Iterable[str | int], schema: Schema) -> str: return MISSING_DESC -def get_exact_cursor_path(position: lsp.Position, tokens: list[Token]) -> deque[str]: # noqa: C901 - """Get the exact path to the cursor position.""" - current_path: deque[str] = deque() - iterator = peekable(tokens) - last_scalar_token: str = "" - previous: Token | None = None - token: Token | None = None - next_token: Token | None = None - - for token in iterator: - next_token = iterator.peek(None) - early_stop = ( - not next_token - or next_token.start_mark.line > position.line - or ( - next_token.start_mark.line >= position.line - and next_token.start_mark.column >= position.character - ) - ) - if early_stop: - break - - match token: - case BlockMappingStartToken() | BlockSequenceStartToken(): - if last_scalar_token: - current_path.append(last_scalar_token) - case BlockEndToken(): - if current_path: - current_path.pop() - - case KeyToken(): - last_scalar_token = "" +def get_completion_path( + tree: Tree, document_text: str, position: lsp.Position +) -> list[str]: + """Finds the YAML key path at the cursor position for autocompletion. - case ScalarToken(value=value): - if isinstance(previous, yaml.KeyToken): - last_scalar_token = value + We need to be more precise than for the hovering and know exactly if the user is typing + a key or a value. + """ + lines = document_text.splitlines() + prefix = ( + lines[position.line][: position.character] if position.line < len(lines) else "" + ) + current_indent = len(prefix) - len(prefix.lstrip()) - previous = token + keys_above: list[tuple[int, str]] = [] + stack = [tree.root_node] if tree and tree.root_node else [] - if ( - isinstance(token, (ValueToken, ScalarToken)) - and last_scalar_token - and next_token - ): - current_path.append(last_scalar_token) + while stack: + node = stack.pop() - return current_path + if node.type in ("block_mapping_pair", "flow_pair"): + key_node = node.child_by_field_name("key") + # Quite intuitively, yaml is written left to right, top to bottom + # Therefore, valid "ancestors" keys must be above the position + if key_node and key_node.start_point.row < position.line and key_node.text: + key_str = key_node.text.decode().strip() -def get_node_path_from_token_position( - position: lsp.Position, segments: dict[tuple[str, ...], DocumentNode] -) -> tuple[str, ...] | None: - """Find the innermost node path corresponding to the current position.""" - for segment, node in reversed(segments.items()): - if node.contains(position): - return segment + if key_str and not key_str.startswith(("-", "---", "...")): + col = key_node.start_point.column - return None + # drop sibling keys or keys from closed blocks + while keys_above and keys_above[-1][0] >= col: + keys_above.pop() + keys_above.append((col, key_str)) + if node.children: + stack.extend(reversed(node.children)) -def list_symbols( - instance: YamlDocument, segments: dict[tuple[str, ...], DocumentNode] -) -> list[lsp.DocumentSymbol]: - """List document symbols. + # Remove keys that are as deep or deeper than our current indentation + while keys_above and keys_above[-1][0] >= current_indent: + keys_above.pop() - We are only interested in keys up to the second level at most, so we don't need anything - fancy here. - """ - symbols = [] - for top_level_key in instance.keys(): - node = segments[(top_level_key,)] - symbol = lsp.DocumentSymbol( - name=node.value, - kind=lsp.SymbolKind.Key, - range=lsp.Range( - start=lsp.Position(node.start.line, node.start.column), - end=lsp.Position(node.end.line, node.end.column), - ), - selection_range=lsp.Range( - start=lsp.Position(node.start.line, node.start.column), - end=lsp.Position(node.selection_end.line, node.selection_end.column), - ), - ) - if top_level_key in SPECIAL_SYMBOL_PARENTS: - children_symbols = [] - for second_level_key in instance[top_level_key].keys(): - child_node = segments[(top_level_key, second_level_key)] - child_symbol = lsp.DocumentSymbol( - name=child_node.value, - kind=lsp.SymbolKind.Key, - range=lsp.Range( - start=lsp.Position( - child_node.start.line, child_node.start.column - ), - end=lsp.Position(child_node.end.line, child_node.end.column), - ), - selection_range=lsp.Range( - start=lsp.Position( - child_node.start.line, child_node.start.column - ), - end=lsp.Position( - child_node.selection_end.line, - child_node.selection_end.column, - ), - ), - ) - children_symbols.append(child_symbol) + path = [key_name for _, key_name in keys_above] - symbol.children = children_symbols - symbols.append(symbol) + # If the user is typing a value, then we add the current key to the path so + # that we can check enums etc. in the schema. + if ":" in prefix and (current_key := prefix.split(":")[0].strip()): + path.append(current_key) - return symbols + return path def get_completion_items_from_path( - segments: Iterable[str], schema: Schema, instance: YamlDocument + segments: Iterable[str], schema: Schema, instance: Any ) -> list[lsp.CompletionItem]: """Get possible values for children nodes or enum values.""" sub_instance = instance diff --git a/src/craft_ls/helpers.py b/src/craft_ls/helpers.py new file mode 100644 index 0000000..70677fc --- /dev/null +++ b/src/craft_ls/helpers.py @@ -0,0 +1,19 @@ +"""Utilities.""" + +import re +from textwrap import shorten + +from lsprotocol import types as lsp + +MSG_SIZE = 79 + + +def sanatize_key(key: str) -> str: + """Sanatize key.""" + return re.sub(r"""['"\\*]""", "", key) + + +def shorten_diagnostics_messages(diagnostics: list[lsp.Diagnostic]) -> None: + """Shorten diagnostics messages to better fit an editor view.""" + for diagnostic in diagnostics: + diagnostic.message = shorten(diagnostic.message, MSG_SIZE) diff --git a/src/craft_ls/parser.py b/src/craft_ls/parser.py new file mode 100644 index 0000000..90f48bc --- /dev/null +++ b/src/craft_ls/parser.py @@ -0,0 +1,203 @@ +"""Parser and document querying logic.""" + +from __future__ import annotations + +import logging +from typing import Any + +import lsprotocol.types as lsp +import tree_sitter_yaml as tsyaml +from pygls.workspace import PositionCodec, TextDocument +from tree_sitter import Language, Node, Parser, Query, QueryCursor, Tree + +YAML_LANGUAGE = Language(tsyaml.language()) +parser = Parser(YAML_LANGUAGE) +query_errors = Query(YAML_LANGUAGE, "(ERROR) @error-node") +query_errors_cursor = QueryCursor(query_errors) + +logger = logging.getLogger(__name__) + + +def apply_change_to_tree_and_text( + tree: Tree, + old_text: str, + change: lsp.TextDocumentContentChangePartial, + position_codec: PositionCodec, +) -> tuple[Tree, str]: + """Apply a code change to the document tree and text. + + Adapted from https://tree-sitter.github.io/tree-sitter/using-parsers/3-advanced-parsing.html. + """ + old_doc = TextDocument(uri="", source=old_text, position_codec=position_codec) + + start_char_offset = old_doc.offset_at_position(change.range.start) + old_end_char_offset = old_doc.offset_at_position(change.range.end) + + start_byte = len(old_text[:start_char_offset].encode("utf-8")) + old_end_byte = len(old_text[:old_end_char_offset].encode("utf-8")) + new_end_byte = start_byte + len(change.text.encode("utf-8")) + + start_row = change.range.start.line + start_line_start = old_text.rfind("\n", 0, start_char_offset) + 1 + start_byte_col = len(old_text[start_line_start:start_char_offset].encode("utf-8")) + start_point = (start_row, start_byte_col) + + old_end_row = change.range.end.line + old_end_line_start = old_text.rfind("\n", 0, old_end_char_offset) + 1 + old_end_byte_col = len( + old_text[old_end_line_start:old_end_char_offset].encode("utf-8") + ) + old_end_point = (old_end_row, old_end_byte_col) + + newlines = change.text.count("\n") + new_end_row = start_row + newlines + if newlines == 0: + new_byte_col = start_byte_col + len(change.text.encode("utf-8")) + else: + last_line = change.text.split("\n")[-1] + new_byte_col = len(last_line.encode("utf-8")) + new_end_point = (new_end_row, new_byte_col) + + tree.edit( + start_byte=start_byte, + old_end_byte=old_end_byte, + new_end_byte=new_end_byte, + start_point=start_point, + old_end_point=old_end_point, + new_end_point=new_end_point, + ) + + new_text = ( + old_text[:start_char_offset] + change.text + old_text[old_end_char_offset:] + ) + return tree, new_text + + +query_pairs = QueryCursor( + Query( + YAML_LANGUAGE, + """ + (block_mapping_pair) @pair + (flow_pair) @pair + """, + ) +) + +query_snap_base_keys = QueryCursor( + Query( + YAML_LANGUAGE, + """ + (block_mapping_pair + key: (_) @key_node + value: (_) @value_node + (#match? @key_node "^(base|build-base)$")) + """, + ) +) + +query_charm_type_keys = QueryCursor( + Query( + YAML_LANGUAGE, + """ + (block_mapping_pair + key: (_) @key_node + value: (_) @value_node + (#match? @key_node "^type$")) + """, + ) +) + + +def _parse_scalar(val: str) -> Any: + """Infers primitive types from scalar strings. + + Similar to yaml.safe_load, but we can instantiate/use it on every token. + """ + val = val.strip() + if not val or val in ("null", "~"): + return None + if val.lower() == "true": + return True + if val.lower() == "false": + return False + + if (val.startswith('"') and val.endswith('"')) or ( + val.startswith("'") and val.endswith("'") + ): + # properly pass "1" as a str + return val[1:-1] + + try: + return float(val) if "." in val or "e" in val else int(val) + except ValueError: + return val + + +def node_to_dict(node: Node | None) -> Any: # noqa: C901 + """Recursively transforms a raw Tree-sitter node into Python structures. + + We have 4 different types to transform: + 1. Scalars + 2. Sequences + 3. Transparent structures (not needed) + 4. Mappings, including errors + + A bit dirty, but it seems to work + """ + if not node or node.type in ("-", "---", "...", "MISSING", ":"): + return None + + if "scalar" in node.type: + return _parse_scalar(node.text.decode("utf-8") if node.text else "") + + if node.type in ("block_sequence", "flow_sequence"): + return [ + node_to_dict(c) + for c in node.children + if c.type not in (",", "[", "]", "ERROR") + ] + + if node.type == "block_sequence_item": + val_node = next( + (c for c in node.children if c.type not in ("-", "ERROR", "MISSING")), None + ) + return node_to_dict(val_node) if val_node else None + + # Bypass those ones + if ( + node.type in ("stream", "document", "block_node", "flow_node") + and node.child_count == 1 + ): + return node_to_dict(node.children[0]) + + if node.type in ("block_mapping", "flow_mapping", "ERROR"): + res = {} + for c in node.children: + if "pair" in c.type: + k = c.child_by_field_name("key") + v = c.child_by_field_name("value") + if k and k.text: + k_str = k.text.decode("utf-8").strip() + if k_str and not k_str.startswith(("-", "---", "...")): + res[k_str] = node_to_dict(v) + + elif "scalar" in c.type: + # Captures an incomplete trailing key being typed at this specific scope layer + k_str = (c.text.decode("utf-8") if c.text else "").strip() + if k_str and not k_str.startswith(("-", "---", "...")): + res[k_str] = None + + elif c.type == "ERROR": + # If there's a nested error block, recursively extract its fragments and merge them + sub_error_dict = node_to_dict(c) + if isinstance(sub_error_dict, dict): + res.update(sub_error_dict) + return res + + return _parse_scalar(node.text.decode("utf-8")) if node.text else None + + +def yaml_tree_to_dict(tree: Tree) -> dict[str, Any]: + """Top-level entrypoint to safely convert any parsed Tree into a clean dict.""" + res = node_to_dict(tree.root_node) + return res if isinstance(res, dict) else {} diff --git a/src/craft_ls/server.py b/src/craft_ls/server.py index 626e0ed..7ce1602 100644 --- a/src/craft_ls/server.py +++ b/src/craft_ls/server.py @@ -1,9 +1,10 @@ """Define the language server features.""" +from __future__ import annotations + import logging from pathlib import Path -from textwrap import shorten -from typing import cast +from typing import Iterable, cast from lsprotocol import types as lsp from pygls.lsp.server import LanguageServer @@ -11,18 +12,21 @@ from craft_ls import __version__ from craft_ls.core import ( get_completion_items_from_path, + get_completion_path, get_description_from_path, get_diagnostics, - get_exact_cursor_path, get_node_path_from_token_position, - get_validator_and_parse, + get_validator_from_tree, list_symbols, - segmentize_nodes, +) +from craft_ls.helpers import shorten_diagnostics_messages +from craft_ls.parser import ( + apply_change_to_tree_and_text, + parser, + yaml_tree_to_dict, ) from craft_ls.settings import IS_DEV_MODE -from craft_ls.types_ import IndexEntry, ParsedResult, Schema - -MSG_SIZE = 79 +from craft_ls.types_ import DocumentsIndex, IndexEntry, Schema, YamlDocument logger = logging.getLogger(__name__) @@ -30,52 +34,72 @@ class CraftLanguageServer(LanguageServer): """*craft tools language server.""" - def __init__( - self, - name: str, - version: str, - text_document_sync_kind: lsp.TextDocumentSyncKind = lsp.TextDocumentSyncKind.Incremental, - notebook_document_sync: lsp.NotebookDocumentSyncOptions | None = None, - ) -> None: + def __init__(self, name: str, version: str) -> None: super().__init__( - name, - version, - text_document_sync_kind, - notebook_document_sync, + name=name, + version=version, + text_document_sync_kind=lsp.TextDocumentSyncKind.Incremental, ) - self.index: dict[str, IndexEntry | None] = {} + self.documents_index: DocumentsIndex = {} - def parse_file(self, file_uri: str) -> IndexEntry | None: - """Parse a document into tokens, nodes and whatnot. + def parse_document( + self, + file_uri: str, + content_changes: Iterable[lsp.TextDocumentContentChangeEvent] | None = None, + ) -> IndexEntry: + """Parse a document. - The result is cached so we can access it in endpoints. + The result is cached so we can access it various LS endpoints. """ document = self.workspace.get_text_document(file_uri) - match get_validator_and_parse(Path(file_uri).stem, document.source): - case None: - self.index[file_uri] = None - - case validator, ParsedResult(tokens, instance, nodes): - segments_nodes = segmentize_nodes(nodes) - self.index[file_uri] = IndexEntry( - validator, tokens, instance, dict(segments_nodes), document.version + cached = self.documents_index.get(file_uri) + + tree = None + current_text = document.source + + # Update tree in-place to not re-parse the entire document + if cached and content_changes: + old_tree, _, _, old_text, _ = cached + tree = old_tree + + full_reparse_required = False + for change in content_changes: + if getattr(change, "range", None) is None: + full_reparse_required = True + break + + tree, current_text = apply_change_to_tree_and_text( + tree, + old_text, + cast(lsp.TextDocumentContentChangePartial, change), + document.position_codec, ) - return self.index[file_uri] - - def get_or_update_index(self, file_uri: str) -> IndexEntry | None: - """Re-parse document if needed.""" - current_version = self.workspace.get_text_document(file_uri).version - entry = self.index.get(file_uri) - match entry: - case IndexEntry(version=cached_version) as cached: - if not cached_version or cached_version != current_version: - return self.parse_file( - file_uri, - ) - return cached - case None: - return None + if full_reparse_required or tree is not None: + tree = parser.parse(current_text.encode("utf-8"), old_tree=tree) + + if tree is None: + # If the in-place upgrade failed for any reason, we fall back to re-parsing + # the entire document. + current_text = document.source + tree = parser.parse(document.source.encode("utf-8")) + + validator = get_validator_from_tree(Path(file_uri).stem, tree) + instance = cast(YamlDocument, yaml_tree_to_dict(tree)) + self.documents_index[file_uri] = IndexEntry( + tree, + validator, + instance, + current_text, + document.version, + ) + return IndexEntry( + tree, + validator, + instance, + current_text, + document.version, + ) server = CraftLanguageServer( @@ -84,15 +108,9 @@ def get_or_update_index(self, file_uri: str) -> IndexEntry | None: ) -def shorten_messages(diagnostics: list[lsp.Diagnostic]) -> None: - """Shorten diagnostics messages to better fit an editor view.""" - for diagnostic in diagnostics: - diagnostic.message = shorten(diagnostic.message, MSG_SIZE) - - @server.feature(lsp.TEXT_DOCUMENT_DID_OPEN) def on_opened(ls: CraftLanguageServer, params: lsp.DidOpenTextDocumentParams) -> None: - """Parse each document when it is opened.""" + """Parse a document when it is first opened.""" uri = params.text_document.uri diagnostics = ( [ @@ -108,60 +126,61 @@ def on_opened(ls: CraftLanguageServer, params: lsp.DidOpenTextDocumentParams) -> if IS_DEV_MODE else [] ) - - match ls.parse_file(uri): - case IndexEntry( - validator, instance=instance, segments=segments, version=version - ): - diagnostics.extend(get_diagnostics(validator, instance, segments)) - - case _: - return - - shorten_messages(diagnostics) - if diagnostics: - server.text_document_publish_diagnostics( - lsp.PublishDiagnosticsParams( - uri=uri, version=version, diagnostics=diagnostics - ) + tree, validator, instance, _, version = ls.parse_document(params.text_document.uri) + if validator: + diagnostics.extend(get_diagnostics(tree, validator, instance)) + shorten_diagnostics_messages(diagnostics) + server.text_document_publish_diagnostics( + lsp.PublishDiagnosticsParams( + uri=uri, + version=version, + diagnostics=diagnostics, ) + ) @server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE) -def on_changed(ls: CraftLanguageServer, params: lsp.DidOpenTextDocumentParams) -> None: - """Parse each document when it is changed.""" +def on_changed( + ls: CraftLanguageServer, params: lsp.DidChangeTextDocumentParams +) -> None: + """Parse a document when it is edited.""" uri = params.text_document.uri - diagnostics = [] + tree, validator, instance, _, version = ls.parse_document(uri) + if validator: + diagnostics = get_diagnostics(tree, validator, instance) + else: + diagnostics = [] + shorten_diagnostics_messages(diagnostics) + server.text_document_publish_diagnostics( + lsp.PublishDiagnosticsParams( + uri=uri, + version=version, + diagnostics=diagnostics, + ) + ) - match ls.parse_file(uri): - case IndexEntry( - validator, instance=instance, segments=segments, version=version - ): - diagnostics.extend(get_diagnostics(validator, instance, segments)) - case _: - return +@server.feature(lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL) +def document_symbols( + ls: CraftLanguageServer, params: lsp.DocumentSymbolParams +) -> list[lsp.DocumentSymbol]: + """Return all the symbols defined in the given document.""" + uri = params.text_document.uri + tree, *_ = ls.documents_index[uri] - shorten_messages(diagnostics) - server.text_document_publish_diagnostics( - lsp.PublishDiagnosticsParams(uri=uri, version=version, diagnostics=diagnostics) - ) + return list_symbols(tree) @server.feature(lsp.TEXT_DOCUMENT_HOVER) def hover(ls: CraftLanguageServer, params: lsp.HoverParams) -> lsp.Hover | None: """Get item description on hover.""" - pos = params.position uri = params.text_document.uri + pos = params.position + tree, validator, *_ = ls.documents_index[uri] - match ls.get_or_update_index(uri): - case IndexEntry(validator_found, segments=segments): - validator = validator_found - - case _: - return None - - if not (path := get_node_path_from_token_position(position=pos, segments=segments)): + if not validator or not ( + path := get_node_path_from_token_position(tree, position=pos) + ): return None description = get_description_from_path( @@ -180,21 +199,6 @@ def hover(ls: CraftLanguageServer, params: lsp.HoverParams) -> lsp.Hover | None: ) -@server.feature(lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL) -def document_symbol( - ls: CraftLanguageServer, params: lsp.DocumentSymbolParams -) -> list[lsp.DocumentSymbol]: - """Return all the symbols defined in the given document.""" - uri = params.text_document.uri - symbols_results: list[lsp.DocumentSymbol] = [] - - match ls.get_or_update_index(uri): - case IndexEntry(instance=instance, segments=segments): - symbols_results = list_symbols(instance, segments) - - return symbols_results - - @server.feature( lsp.TEXT_DOCUMENT_COMPLETION, lsp.CompletionOptions(trigger_characters=[" "]) ) @@ -202,21 +206,16 @@ def completions( ls: CraftLanguageServer, params: lsp.CompletionParams ) -> lsp.CompletionList | None: """Suggest next element based on the document structure.""" - pos = params.position uri = params.text_document.uri + pos = params.position + tree, validator, instance, text, _ = ls.documents_index[uri] items = [] - match ls.get_or_update_index(uri): - case IndexEntry(validator_found, instance=instance, tokens=tokens): - validator = validator_found - - case _: - return None - - path = get_exact_cursor_path(pos, tokens) - items = get_completion_items_from_path( - segments=path, schema=cast(Schema, validator.schema), instance=instance - ) + if validator: + path = get_completion_path(tree, text, pos) + items = get_completion_items_from_path( + segments=path, schema=cast(Schema, validator.schema), instance=instance + ) return lsp.CompletionList(is_incomplete=False, items=items) diff --git a/src/craft_ls/types_.py b/src/craft_ls/types_.py index 7a28ba9..d191346 100644 --- a/src/craft_ls/types_.py +++ b/src/craft_ls/types_.py @@ -1,75 +1,31 @@ """Types module.""" +from __future__ import annotations + from collections import deque -from dataclasses import dataclass -from typing import Any, Generator, NewType +from typing import Any, Generator, NamedTuple, NewType, TypeAlias from jsonschema import ValidationError, Validator -from lsprotocol import types as lsp -from yaml import CollectionNode, Mark, Token +from tree_sitter import Tree # We can probably do better, but that will do for now YamlDocument = NewType("YamlDocument", dict[str, Any]) Schema = NewType("Schema", dict[str, Any]) -@dataclass -class ParsedResult: - """Token parsed result container.""" - - tokens: list[Token] - instance: YamlDocument - nodes: CollectionNode - - -@dataclass -class CompleteParsedResult(ParsedResult): - """Indicate a complete document parsing.""" - - pass - - -@dataclass -class IncompleteParsedResult(ParsedResult): - """Indicate an incomplete parsing of the document.""" - - pass - - -@dataclass -class DocumentNode: - """Document node.""" - - value: str - start: Mark - end: Mark - selection_end: Mark - - def contains(self, position: lsp.Position) -> bool: - """Is position contained in node range?""" - range_start_before = self.start.line < position.line or ( - self.start.line == position.line and self.start.column <= position.character - ) - - range_end_after = self.selection_end.line > position.line or ( - self.selection_end.line == position.line - and self.selection_end.column >= position.character - ) - - return range_start_before and range_end_after - - -@dataclass -class IndexEntry: +class IndexEntry(NamedTuple): """Document index entry.""" - validator: Validator - tokens: list[Token] + tree: Tree + validator: Validator | None instance: YamlDocument - segments: dict[tuple[str, ...], DocumentNode] + text: str version: int | None +DocumentsIndex: TypeAlias = dict[str, IndexEntry] + + class MissingTypeCharmcraftValidator: """No op implementation. diff --git a/tests/test_core.py b/tests/test_core.py index 7be9029..1e66440 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,4 @@ import json -from collections import deque from itertools import chain from textwrap import dedent @@ -11,15 +10,14 @@ from craft_ls.core import ( MISSING_DESC, + get_completion_path, get_description_from_path, get_diagnostic_range, get_diagnostics, - get_exact_cursor_path, get_node_path_from_token_position, list_symbols, - parse_tokens, - segmentize_nodes, ) +from craft_ls.parser import parser # Adapted from json-schema.org schema = json.loads( @@ -71,8 +69,8 @@ currency: euro """ -parsed_document = parse_tokens(document) -document_segments = segmentize_nodes(parsed_document.nodes) + +parsed_tree = parser.parse(document.encode("utf-8")) def test_get_description_first_level_ok() -> None: @@ -120,7 +118,7 @@ def test_get_node_path_from_position_first_level_ok() -> None: position = lsp.Position(2, 5) # When - path = get_node_path_from_token_position(position, dict(document_segments)) + path = get_node_path_from_token_position(parsed_tree, position) # Then assert path == ("productId",) @@ -131,7 +129,7 @@ def test_get_node_path_from_position_nested_ok() -> None: position = lsp.Position(5, 5) # When - path = get_node_path_from_token_position(position, dict(document_segments)) + path = get_node_path_from_token_position(parsed_tree, position) # Then assert path == ("price", "amount") @@ -142,7 +140,7 @@ def test_get_node_path_from_outside_ko() -> None: position = lsp.Position(1, 5) # comment line # When - path = get_node_path_from_token_position(position, dict(document_segments)) + path = get_node_path_from_token_position(parsed_tree, position) # Then assert not path @@ -153,32 +151,32 @@ def test_get_node_path_from_value_ok() -> None: position = lsp.Position(4, 10) # to the right of "price" # When - path = get_node_path_from_token_position(position, dict(document_segments)) + path = get_node_path_from_token_position(parsed_tree, position) # Then assert path == ("price",) -def test_get_cursor_path_from_token_ok() -> None: +def test_get_completion_path_from_token_ok() -> None: # Given position = lsp.Position(4, 3) # inside "price", current path should be root # When - path = get_exact_cursor_path(position, parsed_document.tokens) + path = get_completion_path(parsed_tree, document, position) # Then - assert path == deque([]) + assert path == [] -def test_get_cursor_path_from_nested_key_ok() -> None: +def test_get_completion_path_from_nested_key_ok() -> None: # Given position = lsp.Position(5, 4) # inside "amount", current path should be "price" # When - path = get_exact_cursor_path(position, parsed_document.tokens) + path = get_completion_path(parsed_tree, document, position) # Then - assert path == deque(["price"]) + assert path == ["price"] def test_get_cursor_path_from_value_ok() -> None: @@ -186,10 +184,10 @@ def test_get_cursor_path_from_value_ok() -> None: position = lsp.Position(3, 15) # inside "bar", current path should be "productName" # When - path = get_exact_cursor_path(position, parsed_document.tokens) + path = get_completion_path(parsed_tree, document, position) # Then - assert path == deque(["productName"]) + assert path == ["productName"] def test_values_are_not_flagged() -> None: @@ -207,11 +205,10 @@ def test_values_are_not_flagged() -> None: currency: euro """ ) - segments = dict(segmentize_nodes(parse_tokens(document).nodes)) - parsed_document.nodes + tree = parser.parse(document.encode("utf-8")) # When - range_ = get_diagnostic_range(segments, ["productName"]) + range_ = get_diagnostic_range(tree, ["productName"]) # Then assert range_.start.line == 3 @@ -232,10 +229,11 @@ def test_multiple_unexpected_keys() -> None: baz: buz """ ) - segments = dict(segmentize_nodes(parse_tokens(document).nodes)) + tree = parser.parse(document.encode("utf-8")) + instance = yaml.safe_load(document) # When - diagnostics = get_diagnostics(validator, yaml.safe_load(document), segments) + diagnostics = get_diagnostics(tree, validator, instance) # Then assert len(diagnostics) == 2 @@ -259,10 +257,10 @@ def test_list_symbols_correct_levels() -> None: non-included-key: 50 """ ) - segments = dict(segmentize_nodes(yaml.compose(document))) + tree = parser.parse(document.encode("utf-8")) # When - symbols = list_symbols(yaml.safe_load(document), segments) + symbols = list_symbols(tree) # Then assert len(symbols) == 3 diff --git a/tests/test_server.py b/tests/test_server.py index efcab94..4c0c39d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -29,7 +29,7 @@ async def client(lsp_client: LanguageClient): async def test_diagnostic_on_open(client: LanguageClient): """Ensure that the server implements diagnostics correctly.""" # Given - test_uri = "file:///path/to/snapcraft.yaml" + uri = "file:///path/to/snapcraft.yaml" text_content = dedent( """ name: my_snap @@ -43,7 +43,7 @@ async def test_diagnostic_on_open(client: LanguageClient): client.text_document_did_open( params=types.DidOpenTextDocumentParams( text_document=types.TextDocumentItem( - uri=test_uri, + uri=uri, language_id="yaml", version=1, text=text_content, @@ -53,5 +53,82 @@ async def test_diagnostic_on_open(client: LanguageClient): await client.wait_for_notification(types.TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS) # Then - assert (diagnostics := client.diagnostics.get(test_uri, [])) + assert (diagnostics := client.diagnostics.get(uri, [])) assert any(MISSING_TYPE_MSG in diagnostic.message for diagnostic in diagnostics) + + +@pytest.mark.asyncio +async def test_completion_at_root_key(client: LanguageClient): + """Verify incomplete keys successfully suggest top-level fields (like confinement).""" + # Given + uri = "file:///workspace/snapcraft.yaml" + text_content = dedent( + """ + base: core24 + confi + """ + ) + + # When + client.text_document_did_open( + params=types.DidOpenTextDocumentParams( + text_document=types.TextDocumentItem( + uri=uri, + language_id="yaml", + version=1, + text=text_content, + ) + ) + ) + + # Trigger completion right at the tip of 'confi' at line 1 (+offset 1)) col 5 + response = await client.text_document_completion_async( + types.CompletionParams( + text_document=types.TextDocumentIdentifier(uri=uri), + position=types.Position(line=2, character=5), + ) + ) + + # Then + assert response is not None + labels = [item.label for item in getattr(response, "items", [])] + assert "confinement" in labels + + +@pytest.mark.asyncio +async def test_completion_inside_value(client: LanguageClient): + """Verify value placements isolate sub-enums instead of root parameters.""" + # Given + uri = "file:///workspace/snapcraft.yaml" + text_content = dedent( + """ + base: core24 + confinement: st + """ + ) + + # When + client.text_document_did_open( + params=types.DidOpenTextDocumentParams( + text_document=types.TextDocumentItem( + uri=uri, + language_id="yaml", + version=1, + text=text_content, + ) + ) + ) + + # Trigger completion past the colon at 'st' at line 1 (+offset 1)) col 15 + response = await client.text_document_completion_async( + types.CompletionParams( + text_document=types.TextDocumentIdentifier(uri=uri), + position=types.Position(line=2, character=15), + ) + ) + + # Then + assert response is not None + labels = [item.label for item in getattr(response, "items", [])] + assert "strict" in labels + assert "confinement" not in labels diff --git a/uv.lock b/uv.lock index 8eada2a..9e8be49 100644 --- a/uv.lock +++ b/uv.lock @@ -4,24 +4,24 @@ requires-python = ">=3.12" [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] name = "cattrs" -version = "25.3.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/00/2432bb2d445b39b5407f0a90e01b9a271475eea7caf913d7a86bcb956385/cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a", size = 509321, upload-time = "2025-10-07T12:26:08.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/2b/a40e1488fdfa02d3f9a653a61a5935ea08b3c2225ee818db6a76c7ba9695/cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff", size = 70738, upload-time = "2025-10-07T12:26:06.603Z" }, + { url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" }, ] [[package]] @@ -41,10 +41,11 @@ dependencies = [ { name = "jsonref" }, { name = "jsonschema" }, { name = "lsprotocol" }, - { name = "more-itertools" }, { name = "pygls" }, { name = "pyyaml" }, { name = "referencing" }, + { name = "tree-sitter" }, + { name = "tree-sitter-yaml" }, ] [package.dev-dependencies] @@ -52,10 +53,10 @@ fmt = [ { name = "ruff" }, ] lint = [ - { name = "mypy" }, { name = "ruff" }, { name = "types-jsonschema" }, { name = "types-pyyaml" }, + { name = "zuban" }, ] unit = [ { name = "hypothesis" }, @@ -67,21 +68,22 @@ unit = [ requires-dist = [ { name = "jsonpath-ng", specifier = ">=1.7.0" }, { name = "jsonref", specifier = ">=1.1.0" }, - { name = "jsonschema", specifier = ">=4.23.0" }, + { name = "jsonschema", specifier = ">=4.26.0" }, { name = "lsprotocol", specifier = ">=2025.0.0" }, - { name = "more-itertools", specifier = ">=10.7.0" }, - { name = "pygls", specifier = ">=1.1.0" }, + { name = "pygls", specifier = ">=2.1.1" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "referencing", specifier = ">=0.36.2" }, + { name = "tree-sitter", specifier = ">=0.25.2" }, + { name = "tree-sitter-yaml", specifier = ">=0.7.2" }, ] [package.metadata.requires-dev] fmt = [{ name = "ruff", specifier = ">=0.15.12,<0.16.0" }] lint = [ - { name = "mypy", specifier = ">=1.14.0" }, { name = "ruff", specifier = ">=0.15.12,<0.16.0" }, { name = "types-jsonschema" }, { name = "types-pyyaml" }, + { name = "zuban", specifier = ">=0.9.0" }, ] unit = [ { name = "hypothesis", specifier = ">=6.127.3" }, @@ -91,14 +93,14 @@ unit = [ [[package]] name = "hypothesis" -version = "6.148.7" +version = "6.151.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/5e/6a506e81d4dfefed2e838b6beaaae87b2e411dda3da0a3abf94099f194ae/hypothesis-6.148.7.tar.gz", hash = "sha256:b96e817e715c5b1a278411e3b9baf6d599d5b12207ba25e41a8f066929f6c2a6", size = 471199, upload-time = "2025-12-05T02:12:38.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/e1/ef365ff480903b929d28e057f57b76cae51a30375943e33374ec9a165d9c/hypothesis-6.151.9.tar.gz", hash = "sha256:2f284428dda6c3c48c580de0e18470ff9c7f5ef628a647ee8002f38c3f9097ca", size = 463534, upload-time = "2026-02-16T22:59:23.09Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/55/fa5607e4a4af96dfa0e7efd81bbd130735cedd21aac70b25e06191bff92f/hypothesis-6.148.7-py3-none-any.whl", hash = "sha256:94dbd58ebf259afa3bafb1d3bf5761ac1bde6f1477de494798cbf7960aabbdee", size = 538127, upload-time = "2025-12-05T02:12:35.54Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f7/5cc291d701094754a1d327b44d80a44971e13962881d9a400235726171da/hypothesis-6.151.9-py3-none-any.whl", hash = "sha256:7b7220585c67759b1b1ef839b1e6e9e3d82ed468cfc1ece43c67184848d7edd9", size = 529307, upload-time = "2026-02-16T22:59:20.443Z" }, ] [[package]] @@ -112,14 +114,11 @@ wheels = [ [[package]] name = "jsonpath-ng" -version = "1.7.0" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ply" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/86/08646239a313f895186ff0a4573452038eed8c86f54380b3ebac34d32fb2/jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c", size = 37838, upload-time = "2024-10-11T15:41:42.404Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/58/250751940d75c8019659e15482d548a4aa3b6ce122c515102a4bfdac50e3/jsonpath_ng-1.8.0.tar.gz", hash = "sha256:54252968134b5e549ea5b872f1df1168bd7defe1a52fed5a358c194e1943ddc3", size = 74513, upload-time = "2026-02-24T14:42:06.182Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/5a/73ecb3d82f8615f32ccdadeb9356726d6cae3a4bbc840b437ceb95708063/jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6", size = 30105, upload-time = "2024-11-20T17:58:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/33c7d78a3fb70d545fd5411ac67a651c81602cc09c9cf0df383733f068c5/jsonpath_ng-1.8.0-py3-none-any.whl", hash = "sha256:b8dde192f8af58d646fc031fac9c99fe4d00326afc4148f1f043c601a8cfe138", size = 67844, upload-time = "2026-02-28T00:53:19.637Z" }, ] [[package]] @@ -133,7 +132,7 @@ wheels = [ [[package]] name = "jsonschema" -version = "4.25.1" +version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -141,9 +140,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [[package]] @@ -158,58 +157,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] -[[package]] -name = "librt" -version = "0.7.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/e7/b805d868d21f425b7e76a0ea71a2700290f2266a4f3c8357fcf73efc36aa/librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92", size = 55688, upload-time = "2025-12-15T16:51:31.571Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/69a2b02e62a14cfd5bfd9f1e9adea294d5bcfeea219c7555730e5d068ee4/librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108", size = 57141, upload-time = "2025-12-15T16:51:32.714Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6b/05dba608aae1272b8ea5ff8ef12c47a4a099a04d1e00e28a94687261d403/librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94", size = 165322, upload-time = "2025-12-15T16:51:33.986Z" }, - { url = "https://files.pythonhosted.org/packages/8f/bc/199533d3fc04a4cda8d7776ee0d79955ab0c64c79ca079366fbc2617e680/librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab", size = 174216, upload-time = "2025-12-15T16:51:35.384Z" }, - { url = "https://files.pythonhosted.org/packages/62/ec/09239b912a45a8ed117cb4a6616d9ff508f5d3131bd84329bf2f8d6564f1/librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba", size = 189005, upload-time = "2025-12-15T16:51:36.687Z" }, - { url = "https://files.pythonhosted.org/packages/46/2e/e188313d54c02f5b0580dd31476bb4b0177514ff8d2be9f58d4a6dc3a7ba/librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9", size = 183960, upload-time = "2025-12-15T16:51:37.977Z" }, - { url = "https://files.pythonhosted.org/packages/eb/84/f1d568d254518463d879161d3737b784137d236075215e56c7c9be191cee/librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74", size = 177609, upload-time = "2025-12-15T16:51:40.584Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/060bbc1c002f0d757c33a1afe6bf6a565f947a04841139508fc7cef6c08b/librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f", size = 199269, upload-time = "2025-12-15T16:51:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/ff/7f/708f8f02d8012ee9f366c07ea6a92882f48bd06cc1ff16a35e13d0fbfb08/librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286", size = 43186, upload-time = "2025-12-15T16:51:43.149Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a5/4e051b061c8b2509be31b2c7ad4682090502c0a8b6406edcf8c6b4fe1ef7/librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20", size = 49455, upload-time = "2025-12-15T16:51:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d2/90d84e9f919224a3c1f393af1636d8638f54925fdc6cd5ee47f1548461e5/librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a", size = 42828, upload-time = "2025-12-15T16:51:45.498Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" }, - { url = "https://files.pythonhosted.org/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" }, - { url = "https://files.pythonhosted.org/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" }, - { url = "https://files.pythonhosted.org/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" }, - { url = "https://files.pythonhosted.org/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" }, - { url = "https://files.pythonhosted.org/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" }, - { url = "https://files.pythonhosted.org/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" }, - { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" }, - { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" }, - { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" }, - { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" }, - { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" }, - { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" }, - { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" }, - { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" }, - { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" }, - { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" }, - { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" }, - { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" }, - { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" }, - { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" }, - { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" }, - { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" }, -] - [[package]] name = "lsprotocol" version = "2025.0.0" @@ -223,73 +170,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/f0/92f2d609d6642b5f30cb50a885d2bf1483301c69d5786286500d15651ef2/lsprotocol-2025.0.0-py3-none-any.whl", hash = "sha256:f9d78f25221f2a60eaa4a96d3b4ffae011b107537facee61d3da3313880995c7", size = 76250, upload-time = "2025-06-17T21:30:19.455Z" }, ] -[[package]] -name = "more-itertools" -version = "10.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, -] - -[[package]] -name = "mypy" -version = "1.19.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -301,27 +188,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "ply" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, -] - [[package]] name = "pygls" -version = "2.0.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "cattrs" }, { name = "lsprotocol" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/50/2bfc32f3acbc8941042919b59c9f592291127b55d7331b72e67ce7b62f08/pygls-2.0.0.tar.gz", hash = "sha256:99accd03de1ca76fe1e7e317f0968ebccf7b9955afed6e2e3e188606a20b4f07", size = 55796, upload-time = "2025-10-17T19:22:47.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/2e/7bbe061d175c0baddde8fc9edb908a4c31ba5d9165b8c68e3439c3a9f138/pygls-2.1.1.tar.gz", hash = "sha256:1da03ba9053201bb337dcdd8d121df70feb2a91e1a0dcc74de5da79755b1a201", size = 55091, upload-time = "2026-03-25T11:19:10.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/09/14feafc13bebb9c85b29b374889c1549d3700cb572f2d43a1bb940d70315/pygls-2.0.0-py3-none-any.whl", hash = "sha256:b4e54bba806f76781017ded8fd07463b98670f959042c44170cd362088b200cc", size = 69533, upload-time = "2025-10-17T19:22:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1a/208293b6c350f5abea6941d5606080d4a492644052504f5312e5de30a902/pygls-2.1.1-py3-none-any.whl", hash = "sha256:510a6dea2476177230c7d851125e5948efdf3fdb9ebfd8543fc434972f8faed4", size = 68975, upload-time = "2026-03-25T11:19:11.374Z" }, ] [[package]] @@ -552,16 +430,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] +[[package]] +name = "tree-sitter" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941, upload-time = "2025-09-25T17:37:34.813Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699, upload-time = "2025-09-25T17:37:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125, upload-time = "2025-09-25T17:37:37.725Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418, upload-time = "2025-09-25T17:37:38.922Z" }, + { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250, upload-time = "2025-09-25T17:37:40.039Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156, upload-time = "2025-09-25T17:37:41.132Z" }, + { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984, upload-time = "2025-09-25T17:37:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926, upload-time = "2025-09-25T17:37:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712, upload-time = "2025-09-25T17:37:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873, upload-time = "2025-09-25T17:37:45.477Z" }, + { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313, upload-time = "2025-09-25T17:37:46.68Z" }, + { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370, upload-time = "2025-09-25T17:37:47.993Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157, upload-time = "2025-09-25T17:37:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975, upload-time = "2025-09-25T17:37:49.922Z" }, + { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776, upload-time = "2025-09-25T17:37:50.898Z" }, + { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732, upload-time = "2025-09-25T17:37:51.877Z" }, + { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456, upload-time = "2025-09-25T17:37:52.925Z" }, + { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772, upload-time = "2025-09-25T17:37:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522, upload-time = "2025-09-25T17:37:55.833Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864, upload-time = "2025-09-25T17:37:57.453Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470, upload-time = "2025-09-25T17:37:58.431Z" }, +] + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" }, + { url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" }, +] + [[package]] name = "types-jsonschema" -version = "4.25.1.20251009" +version = "4.26.0.20260325" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/da/5b901088da5f710690b422137e8ae74197fb1ca471e4aa84dd3ef0d6e295/types_jsonschema-4.25.1.20251009.tar.gz", hash = "sha256:75d0f5c5dd18dc23b664437a0c1a625743e8d2e665ceaf3aecb29841f3a5f97f", size = 15661, upload-time = "2025-10-09T02:54:36.963Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/bf/97b3438f0a3834d7d8e515fbccd4e1ca957465e094f0b260162a5cf9b951/types_jsonschema-4.26.0.20260325.tar.gz", hash = "sha256:84c319ba1af5463394d99accd96db543b7cb0eeab0938c652c18129536672002", size = 16441, upload-time = "2026-03-25T04:08:12.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/6a/e5146754c0dfc272f176db9c245bc43cc19030262d891a5a85d472797e60/types_jsonschema-4.25.1.20251009-py3-none-any.whl", hash = "sha256:f30b329037b78e7a60146b1146feb0b6fb0b71628637584409bada83968dad3e", size = 15925, upload-time = "2025-10-09T02:54:35.847Z" }, + { url = "https://files.pythonhosted.org/packages/61/ec/65a4a55a024c9eb7fe08c207c0a94a537db0db50fea61ad565fa6b39220f/types_jsonschema-4.26.0.20260325-py3-none-any.whl", hash = "sha256:032a952fd32d9e06b71bdce5a5b4005dd58a074f6cb2899e96b633cbe1c28f40", size = 16080, upload-time = "2026-03-25T04:08:11.108Z" }, ] [[package]] @@ -581,3 +504,22 @@ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac8 wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] + +[[package]] +name = "zuban" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/4c/e656a15040892d57613797497a830ded23a1393e26790d10d1544b6c3d7f/zuban-0.9.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:88bc1de65b4c872b2be39ee8f6667bdf2445e97edff57fd36de1c8196cb9f080", size = 11490459, upload-time = "2026-06-23T08:39:19.454Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ae/effe7e2ae69b100f45bfc0fdfe791960cc5fdf0fd9f912f9dfe383831708/zuban-0.9.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6013a9e01bdd5a4806147dc305c4ccd699b5a3675a6eb753bc90c942da3e1bd2", size = 11210900, upload-time = "2026-06-23T08:39:22.361Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ef/d769640c1bb02aa63f4232e4e0800a54f83c9fecd3fb706de06c441ce221/zuban-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e66ceffddaa6b2c10cf7f737e2bd36c19043746b46564dfe73e9b781d1ed6ee3", size = 28430892, upload-time = "2026-06-23T08:39:24.785Z" }, + { url = "https://files.pythonhosted.org/packages/33/5a/794e266304476f2f1270f1c65eb3802905e5c7a3ec4cb9e2f80c43ddabbd/zuban-0.9.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eac519c610f19473f9cd307e1601e935567aac19b0732a50826590303d3f811c", size = 28625883, upload-time = "2026-06-23T08:39:27.592Z" }, + { url = "https://files.pythonhosted.org/packages/4a/83/a0c95efca0f59fb0a694bdffcc5926adac99e218d38bc2791110655055ef/zuban-0.9.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95d1620ad4d88c00cdcbcbd623c50647af5d7e67a5cf6270f6c7e0bc27acb2bc", size = 29832269, upload-time = "2026-06-23T08:39:30.195Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f3/170ac2bb7dfa94651d415d6ee791fe2bfdc65a7adc8dfe4ac1c9496c82b1/zuban-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65bbde3810865595756c25447dd854991f910486253dfbcad7518314c501c8fd", size = 30901959, upload-time = "2026-06-23T08:39:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/75/3234eecd716e2808b65c29ccfe42191433d9d8359dc793f88a1dbb0d3ffa/zuban-0.9.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bca1d0be28048ec6c9b4873461cba83c5250046814ba0079f6a7f89a47433cd1", size = 28593375, upload-time = "2026-06-23T08:39:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b0/64de2f8ef6cd265b55e9237d131907d659d6c1af0d4b2656cdd078713013/zuban-0.9.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bb6c3bf9a28768e6c4c2e47359439b818b21d84fc644ec46b4f3f2e165824506", size = 29034957, upload-time = "2026-06-23T08:39:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/ff/3d/5b97101e94f71a35acf79cf17470e57679ed2adbf7acb0d4ef1869fc35bf/zuban-0.9.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1617ebcb962f18c5c63a80620dd1540593ae33932c244beafd9f9d050adbabbb", size = 29710311, upload-time = "2026-06-23T08:39:45.1Z" }, + { url = "https://files.pythonhosted.org/packages/5f/43/79fa5f9c1f4fc99d27d59f1409e1503b885c0dd8fd701c0c3766dea5d140/zuban-0.9.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ba529a06f090e19ce5f87174f617435a3669ef37e5ead17970117d9cb43bd82", size = 29075890, upload-time = "2026-06-23T08:39:47.983Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b9/b11c0b83bb4f544b53ac2b62b766e73537dbea89774c3aef0a65f18eb378/zuban-0.9.0-py3-none-win32.whl", hash = "sha256:cf4b1d71da43a1efdb29863c9b23d3855658f00f8f86ad7932504803d93a8072", size = 10044779, upload-time = "2026-06-23T08:39:50.906Z" }, + { url = "https://files.pythonhosted.org/packages/25/73/ebc3a4cfc08216cde168e968ad7f8289c94c8ede78adf18dd15b52d855ab/zuban-0.9.0-py3-none-win_amd64.whl", hash = "sha256:4889a911b72269258c54c94a250450ee721e6c7c6ad058c416286e83fa3f3685", size = 10806625, upload-time = "2026-06-23T08:39:53.433Z" }, +] From a245890dbea02a32fa8d1d4869ae5d16e5cf2cb1 Mon Sep 17 00:00:00 2001 From: Alex Batisse Date: Fri, 26 Jun 2026 18:33:51 +0200 Subject: [PATCH 2/2] Fix flake dependencies --- flake.lock | 12 ++++++------ flake.nix | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index a2c0d98..5acd86d 100644 --- a/flake.lock +++ b/flake.lock @@ -2,12 +2,12 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1767364772, - "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", - "type": "github" + "lastModified": 1781074563, + "narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=", + "rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca", + "revCount": 1014179, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/nixpkgs-weekly/0.1.1014179%2Brev-9ae611a455b90cf061d8f332b977e387bda8e1ca/019ef33c-03d6-7c80-8293-acac0bf146bb/source.tar.gz" }, "original": { "id": "nixpkgs", diff --git a/flake.nix b/flake.nix index 67c6261..0b145c9 100644 --- a/flake.nix +++ b/flake.nix @@ -30,7 +30,8 @@ pyyaml jsonref referencing - more-itertools + tree-sitter + tree-sitter-yaml ]; };