cmap-core 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmap_core-0.1.0/.gitignore +4 -0
- cmap_core-0.1.0/PKG-INFO +7 -0
- cmap_core-0.1.0/README.md +22 -0
- cmap_core-0.1.0/examples/basic/main.py +11 -0
- cmap_core-0.1.0/pyproject.toml +27 -0
- cmap_core-0.1.0/src/cmap_core/__init__.py +1 -0
- cmap_core-0.1.0/src/cmap_core/__main__.py +3 -0
- cmap_core-0.1.0/src/cmap_core/cmap_analyzer.py +68 -0
- cmap_core-0.1.0/src/cmap_core/cmap_ast.py +5 -0
- cmap_core-0.1.0/src/cmap_core/cmap_graph.py +67 -0
- cmap_core-0.1.0/src/cmap_core/cmap_models.py +29 -0
- cmap_core-0.1.0/src/cmap_core/cmap_render.py +14 -0
- cmap_core-0.1.0/src/cmap_core/cmap_scan.py +6 -0
- cmap_core-0.1.0/src/cmap_core/main.py +66 -0
- cmap_core-0.1.0/tests/test_analyzer.py +10 -0
- cmap_core-0.1.0/tests/test_ast.py +8 -0
- cmap_core-0.1.0/tests/test_graph.py +7 -0
- cmap_core-0.1.0/uv.lock +154 -0
cmap_core-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# CMap
|
|
2
|
+
|
|
3
|
+
CMap (Code Map) is a terminal-first Python code analysis tool. It scans Python source with the standard-library AST and exposes symbols, calls, imports, call traces, dependency information, and circular dependency detection.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync
|
|
9
|
+
uv run pytest
|
|
10
|
+
uv run cmap-core --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cmap-core scan .
|
|
17
|
+
cmap-core trace . main
|
|
18
|
+
cmap-core callers . parse
|
|
19
|
+
cmap-core callees . main
|
|
20
|
+
cmap-core deps .
|
|
21
|
+
cmap-core circular .
|
|
22
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cmap-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Code Map: inspect Python code structure and relationships from the terminal."
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = ["typer>=0.16,<1", "rich>=13.9,<15"]
|
|
7
|
+
|
|
8
|
+
[project.scripts]
|
|
9
|
+
cmap-core = "cmap_core.main:app"
|
|
10
|
+
|
|
11
|
+
[dependency-groups]
|
|
12
|
+
dev = ["pytest>=8,<9"]
|
|
13
|
+
|
|
14
|
+
[tool.pytest.ini_options]
|
|
15
|
+
testpaths = ["tests"]
|
|
16
|
+
addopts = "-q"
|
|
17
|
+
|
|
18
|
+
[tool.ruff]
|
|
19
|
+
target-version = "py312"
|
|
20
|
+
line-length = 88
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["hatchling"]
|
|
24
|
+
build-backend = "hatchling.build"
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["src/cmap_core"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .cmap_models import Call, CodeMap, Import, Symbol
|
|
4
|
+
|
|
5
|
+
class Analyzer(ast.NodeVisitor):
|
|
6
|
+
def __init__(self, path: Path) -> None:
|
|
7
|
+
self.path = path
|
|
8
|
+
self.result = CodeMap()
|
|
9
|
+
self.scope: list[str] = []
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def current(self) -> str:
|
|
13
|
+
return ".".join(self.scope) or "<module>"
|
|
14
|
+
|
|
15
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
16
|
+
self._visit_function(node)
|
|
17
|
+
|
|
18
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
19
|
+
self._visit_function(node)
|
|
20
|
+
|
|
21
|
+
def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
|
22
|
+
parent = self.current if self.scope else None
|
|
23
|
+
symbol_id = f"{self.path}:{'.'.join((*self.scope, node.name))}"
|
|
24
|
+
self.result.symbols.append(Symbol(symbol_id, node.name, "function", str(self.path), node.lineno, parent))
|
|
25
|
+
self.scope.append(node.name)
|
|
26
|
+
self.generic_visit(node)
|
|
27
|
+
self.scope.pop()
|
|
28
|
+
|
|
29
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
|
30
|
+
parent = self.current if self.scope else None
|
|
31
|
+
symbol_id = f"{self.path}:{'.'.join((*self.scope, node.name))}"
|
|
32
|
+
self.result.symbols.append(Symbol(symbol_id, node.name, "class", str(self.path), node.lineno, parent))
|
|
33
|
+
self.scope.append(node.name)
|
|
34
|
+
self.generic_visit(node)
|
|
35
|
+
self.scope.pop()
|
|
36
|
+
|
|
37
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
38
|
+
if isinstance(node.func, ast.Name):
|
|
39
|
+
callee = node.func.id
|
|
40
|
+
elif isinstance(node.func, ast.Attribute):
|
|
41
|
+
callee = ast.unparse(node.func)
|
|
42
|
+
else:
|
|
43
|
+
callee = ast.unparse(node.func)
|
|
44
|
+
self.result.calls.append(Call(self.current, callee, str(self.path), node.lineno))
|
|
45
|
+
self.generic_visit(node)
|
|
46
|
+
|
|
47
|
+
def visit_Import(self, node: ast.Import) -> None:
|
|
48
|
+
for alias in node.names:
|
|
49
|
+
self.result.imports.append(Import(alias.name, str(self.path), node.lineno))
|
|
50
|
+
|
|
51
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
|
52
|
+
module = "." * node.level + (node.module or "")
|
|
53
|
+
self.result.imports.append(Import(module, str(self.path), node.lineno))
|
|
54
|
+
|
|
55
|
+
def analyze(path: Path) -> CodeMap:
|
|
56
|
+
from .cmap_ast import parse
|
|
57
|
+
analyzer = Analyzer(path)
|
|
58
|
+
analyzer.visit(parse(path))
|
|
59
|
+
return analyzer.result
|
|
60
|
+
|
|
61
|
+
def analyze_paths(paths: list[Path]) -> CodeMap:
|
|
62
|
+
result = CodeMap()
|
|
63
|
+
for path in paths:
|
|
64
|
+
current = analyze(path)
|
|
65
|
+
result.symbols.extend(current.symbols)
|
|
66
|
+
result.calls.extend(current.calls)
|
|
67
|
+
result.imports.extend(current.imports)
|
|
68
|
+
return result
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .cmap_models import CodeMap
|
|
4
|
+
|
|
5
|
+
def callers(graph: CodeMap, target: str) -> list[str]:
|
|
6
|
+
return sorted({call.caller for call in graph.calls if call.callee == target})
|
|
7
|
+
|
|
8
|
+
def callees(graph: CodeMap, target: str) -> list[str]:
|
|
9
|
+
return sorted({call.callee for call in graph.calls if call.caller == target})
|
|
10
|
+
|
|
11
|
+
def dependencies(graph: CodeMap) -> dict[str, set[str]]:
|
|
12
|
+
result: dict[str, set[str]] = defaultdict(set)
|
|
13
|
+
for item in graph.imports:
|
|
14
|
+
result[item.file].add(item.module)
|
|
15
|
+
return dict(result)
|
|
16
|
+
|
|
17
|
+
def _module_name(path: str, root: Path) -> str:
|
|
18
|
+
relative = Path(path).relative_to(root)
|
|
19
|
+
parts = list(relative.with_suffix("").parts)
|
|
20
|
+
if parts[-1] == "__init__":
|
|
21
|
+
parts.pop()
|
|
22
|
+
return ".".join(parts)
|
|
23
|
+
|
|
24
|
+
def circular_dependencies(graph: CodeMap, root: Path) -> list[list[str]]:
|
|
25
|
+
files = {Path(symbol.file) for symbol in graph.symbols}
|
|
26
|
+
modules = {_module_name(str(path), root): path for path in files}
|
|
27
|
+
edges: dict[Path, set[Path]] = defaultdict(set)
|
|
28
|
+
for item in graph.imports:
|
|
29
|
+
source = Path(item.file)
|
|
30
|
+
for module, target in modules.items():
|
|
31
|
+
if item.module == module or item.module.startswith(f"{module}."):
|
|
32
|
+
edges[source].add(target)
|
|
33
|
+
visited: set[Path] = set()
|
|
34
|
+
stack: list[Path] = []
|
|
35
|
+
found: list[list[str]] = []
|
|
36
|
+
|
|
37
|
+
def visit(node: Path) -> None:
|
|
38
|
+
if node in stack:
|
|
39
|
+
cycle = stack[stack.index(node):] + [node]
|
|
40
|
+
names = [str(item) for item in cycle]
|
|
41
|
+
if names not in found:
|
|
42
|
+
found.append(names)
|
|
43
|
+
return
|
|
44
|
+
if node in visited:
|
|
45
|
+
return
|
|
46
|
+
visited.add(node)
|
|
47
|
+
stack.append(node)
|
|
48
|
+
for target in edges.get(node, ()):
|
|
49
|
+
visit(target)
|
|
50
|
+
stack.pop()
|
|
51
|
+
|
|
52
|
+
for node in files:
|
|
53
|
+
visit(node)
|
|
54
|
+
return found
|
|
55
|
+
|
|
56
|
+
def trace(graph: CodeMap, target: str) -> list[tuple[str, str]]:
|
|
57
|
+
result: list[tuple[str, str]] = []
|
|
58
|
+
queue = [target]
|
|
59
|
+
seen = {target}
|
|
60
|
+
while queue:
|
|
61
|
+
current = queue.pop(0)
|
|
62
|
+
for callee in callees(graph, current):
|
|
63
|
+
result.append((current, callee))
|
|
64
|
+
if callee not in seen:
|
|
65
|
+
seen.add(callee)
|
|
66
|
+
queue.append(callee)
|
|
67
|
+
return result
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
@dataclass(frozen=True, slots=True)
|
|
4
|
+
class Symbol:
|
|
5
|
+
id: str
|
|
6
|
+
name: str
|
|
7
|
+
kind: str
|
|
8
|
+
file: str
|
|
9
|
+
line: int
|
|
10
|
+
parent: str | None = None
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class Call:
|
|
14
|
+
caller: str
|
|
15
|
+
callee: str
|
|
16
|
+
file: str
|
|
17
|
+
line: int
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class Import:
|
|
21
|
+
module: str
|
|
22
|
+
file: str
|
|
23
|
+
line: int
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class CodeMap:
|
|
27
|
+
symbols: list[Symbol] = field(default_factory=list)
|
|
28
|
+
calls: list[Call] = field(default_factory=list)
|
|
29
|
+
imports: list[Import] = field(default_factory=list)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import asdict
|
|
3
|
+
from .cmap_models import CodeMap
|
|
4
|
+
|
|
5
|
+
def text(graph: CodeMap) -> str:
|
|
6
|
+
lines = [f"{symbol.kind} {symbol.name} {symbol.file}:{symbol.line}" for symbol in graph.symbols]
|
|
7
|
+
if graph.calls:
|
|
8
|
+
lines += ["", "Calls", *[f" {call.caller} -> {call.callee}" for call in graph.calls]]
|
|
9
|
+
if graph.imports:
|
|
10
|
+
lines += ["", "Imports", *[f" {item.file} -> {item.module}" for item in graph.imports]]
|
|
11
|
+
return "\n".join(lines)
|
|
12
|
+
|
|
13
|
+
def json_output(graph: CodeMap) -> str:
|
|
14
|
+
return json.dumps({"symbols": [asdict(item) for item in graph.symbols], "calls": [asdict(item) for item in graph.calls], "imports": [asdict(item) for item in graph.imports]}, indent=2)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
import json
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from . import __version__
|
|
7
|
+
from .cmap_analyzer import analyze_paths
|
|
8
|
+
from .cmap_graph import callers, callees, circular_dependencies, dependencies, trace
|
|
9
|
+
from .cmap_render import json_output, text
|
|
10
|
+
from .cmap_scan import python_files
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="CMap — Code Map for Python.", no_args_is_help=True)
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def version() -> None:
|
|
17
|
+
"""Show the CMap version."""
|
|
18
|
+
typer.echo(__version__)
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def scan(path: Annotated[Path, typer.Argument(exists=True, readable=True)], json_mode: Annotated[bool, typer.Option("--json")] = False) -> None:
|
|
22
|
+
"""Scan Python files and show their structure."""
|
|
23
|
+
graph = analyze_paths(python_files(path))
|
|
24
|
+
typer.echo(json_output(graph) if json_mode else text(graph))
|
|
25
|
+
|
|
26
|
+
@app.command("trace")
|
|
27
|
+
def trace_code(path: Annotated[Path, typer.Argument(exists=True, readable=True)], target: Annotated[str, typer.Argument()], json_mode: Annotated[bool, typer.Option("--json")] = False) -> None:
|
|
28
|
+
"""Show the reachable calls from a function."""
|
|
29
|
+
graph = analyze_paths(python_files(path))
|
|
30
|
+
result = trace(graph, target)
|
|
31
|
+
typer.echo(json.dumps(result) if json_mode else "\n".join(f"{a} -> {b}" for a, b in result))
|
|
32
|
+
|
|
33
|
+
@app.command("callers")
|
|
34
|
+
def callers_code(path: Annotated[Path, typer.Argument(exists=True, readable=True)], target: Annotated[str, typer.Argument()]) -> None:
|
|
35
|
+
"""Show callers of a function."""
|
|
36
|
+
graph = analyze_paths(python_files(path))
|
|
37
|
+
typer.echo("\n".join(callers(graph, target)))
|
|
38
|
+
|
|
39
|
+
@app.command("callees")
|
|
40
|
+
def callees_code(path: Annotated[Path, typer.Argument(exists=True, readable=True)], target: Annotated[str, typer.Argument()]) -> None:
|
|
41
|
+
"""Show functions called by a function."""
|
|
42
|
+
graph = analyze_paths(python_files(path))
|
|
43
|
+
typer.echo("\n".join(callees(graph, target)))
|
|
44
|
+
|
|
45
|
+
@app.command()
|
|
46
|
+
def deps(path: Annotated[Path, typer.Argument(exists=True, readable=True)]) -> None:
|
|
47
|
+
"""Show Python import dependencies."""
|
|
48
|
+
graph = analyze_paths(python_files(path))
|
|
49
|
+
for source, modules in dependencies(graph).items():
|
|
50
|
+
typer.echo(source)
|
|
51
|
+
for module in sorted(modules):
|
|
52
|
+
typer.echo(f" -> {module}")
|
|
53
|
+
|
|
54
|
+
@app.command()
|
|
55
|
+
def circular(path: Annotated[Path, typer.Argument(exists=True, readable=True)]) -> None:
|
|
56
|
+
"""Find circular local import dependencies."""
|
|
57
|
+
graph = analyze_paths(python_files(path))
|
|
58
|
+
cycles = circular_dependencies(graph)
|
|
59
|
+
if not cycles:
|
|
60
|
+
typer.echo("No circular dependencies found.")
|
|
61
|
+
return
|
|
62
|
+
for cycle in cycles:
|
|
63
|
+
typer.echo(" -> ".join(cycle))
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
app()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from cmap_core.cmap_analyzer import analyze
|
|
3
|
+
|
|
4
|
+
def test_analyze_collects_symbols_calls_and_imports(tmp_path: Path) -> None:
|
|
5
|
+
path = tmp_path / "example.py"
|
|
6
|
+
path.write_text("import os\n\ndef main():\n print(os.getcwd())\n", encoding="utf-8")
|
|
7
|
+
graph = analyze(path)
|
|
8
|
+
assert any(symbol.name == "main" for symbol in graph.symbols)
|
|
9
|
+
assert any(call.callee == "print" for call in graph.calls)
|
|
10
|
+
assert graph.imports[0].module == "os"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from cmap_core.cmap_graph import CodeMap, Call, callers, callees, trace
|
|
2
|
+
|
|
3
|
+
def test_graph_queries() -> None:
|
|
4
|
+
graph = CodeMap(calls=[Call("main", "load", "x.py", 1), Call("load", "parse", "x.py", 2)])
|
|
5
|
+
assert callers(graph, "load") == ["main"]
|
|
6
|
+
assert callees(graph, "main") == ["load"]
|
|
7
|
+
assert trace(graph, "main") == [("main", "load"), ("load", "parse")]
|
cmap_core-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.12"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "annotated-doc"
|
|
7
|
+
version = "0.0.5"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "cmap-core"
|
|
16
|
+
version = "0.1.0"
|
|
17
|
+
source = { editable = "." }
|
|
18
|
+
dependencies = [
|
|
19
|
+
{ name = "rich" },
|
|
20
|
+
{ name = "typer" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[package.dev-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
{ name = "pytest" },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[package.metadata]
|
|
29
|
+
requires-dist = [
|
|
30
|
+
{ name = "rich", specifier = ">=13.9,<15" },
|
|
31
|
+
{ name = "typer", specifier = ">=0.16,<1" },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[package.metadata.requires-dev]
|
|
35
|
+
dev = [{ name = "pytest", specifier = ">=8,<9" }]
|
|
36
|
+
|
|
37
|
+
[[package]]
|
|
38
|
+
name = "colorama"
|
|
39
|
+
version = "0.4.6"
|
|
40
|
+
source = { registry = "https://pypi.org/simple" }
|
|
41
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
42
|
+
wheels = [
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[[package]]
|
|
47
|
+
name = "iniconfig"
|
|
48
|
+
version = "2.3.0"
|
|
49
|
+
source = { registry = "https://pypi.org/simple" }
|
|
50
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
51
|
+
wheels = [
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[[package]]
|
|
56
|
+
name = "markdown-it-py"
|
|
57
|
+
version = "4.2.0"
|
|
58
|
+
source = { registry = "https://pypi.org/simple" }
|
|
59
|
+
dependencies = [
|
|
60
|
+
{ name = "mdurl" },
|
|
61
|
+
]
|
|
62
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
|
|
63
|
+
wheels = [
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[[package]]
|
|
68
|
+
name = "mdurl"
|
|
69
|
+
version = "0.1.2"
|
|
70
|
+
source = { registry = "https://pypi.org/simple" }
|
|
71
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
|
72
|
+
wheels = [
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "packaging"
|
|
78
|
+
version = "26.3"
|
|
79
|
+
source = { registry = "https://pypi.org/simple" }
|
|
80
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
|
81
|
+
wheels = [
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "pluggy"
|
|
87
|
+
version = "1.6.0"
|
|
88
|
+
source = { registry = "https://pypi.org/simple" }
|
|
89
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
90
|
+
wheels = [
|
|
91
|
+
{ 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" },
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[[package]]
|
|
95
|
+
name = "pygments"
|
|
96
|
+
version = "2.21.0"
|
|
97
|
+
source = { registry = "https://pypi.org/simple" }
|
|
98
|
+
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
|
99
|
+
wheels = [
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[[package]]
|
|
104
|
+
name = "pytest"
|
|
105
|
+
version = "8.4.2"
|
|
106
|
+
source = { registry = "https://pypi.org/simple" }
|
|
107
|
+
dependencies = [
|
|
108
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
109
|
+
{ name = "iniconfig" },
|
|
110
|
+
{ name = "packaging" },
|
|
111
|
+
{ name = "pluggy" },
|
|
112
|
+
{ name = "pygments" },
|
|
113
|
+
]
|
|
114
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
|
115
|
+
wheels = [
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
[[package]]
|
|
120
|
+
name = "rich"
|
|
121
|
+
version = "14.3.4"
|
|
122
|
+
source = { registry = "https://pypi.org/simple" }
|
|
123
|
+
dependencies = [
|
|
124
|
+
{ name = "markdown-it-py" },
|
|
125
|
+
{ name = "pygments" },
|
|
126
|
+
]
|
|
127
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" }
|
|
128
|
+
wheels = [
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" },
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
[[package]]
|
|
133
|
+
name = "shellingham"
|
|
134
|
+
version = "1.5.4"
|
|
135
|
+
source = { registry = "https://pypi.org/simple" }
|
|
136
|
+
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
|
137
|
+
wheels = [
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
[[package]]
|
|
142
|
+
name = "typer"
|
|
143
|
+
version = "0.27.2"
|
|
144
|
+
source = { registry = "https://pypi.org/simple" }
|
|
145
|
+
dependencies = [
|
|
146
|
+
{ name = "annotated-doc" },
|
|
147
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
148
|
+
{ name = "rich" },
|
|
149
|
+
{ name = "shellingham" },
|
|
150
|
+
]
|
|
151
|
+
sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" }
|
|
152
|
+
wheels = [
|
|
153
|
+
{ url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" },
|
|
154
|
+
]
|