Tooling V1
This commit is contained in:
@@ -5,7 +5,9 @@ description = "NeoECU build and flash tooling"
|
|||||||
authors = [{ name = "Hector van der Aa", email = "hector@h3cx.dev" }]
|
authors = [{ name = "Hector van der Aa", email = "hector@h3cx.dev" }]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
"rich>=14.3.3",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
neoecu = "neoecu.cli:main"
|
neoecu = "neoecu.cli:main"
|
||||||
|
|||||||
@@ -3,22 +3,53 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import sys
|
||||||
|
import neoecu.common
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(prog="neoecu")
|
parser = argparse.ArgumentParser(prog="neoecu")
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(dest="command")
|
subparsers = parser.add_subparsers(dest="command")
|
||||||
|
|
||||||
subparsers.add_parser("build")
|
build_parser = subparsers.add_parser("build")
|
||||||
subparsers.add_parser("flash")
|
subparsers.add_parser("flash")
|
||||||
|
subparsers.add_parser("envcheck")
|
||||||
|
subparsers.add_parser("init")
|
||||||
|
subparsers.add_parser("clean")
|
||||||
|
|
||||||
|
build_parser.add_argument("--debug", action="store_true")
|
||||||
|
build_parser.add_argument("--release", action="store_true")
|
||||||
|
build_parser.add_argument("--clean", action="store_true")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
project_root = neoecu.common.find_project_root()
|
||||||
|
|
||||||
|
if project_root is None:
|
||||||
|
print("neoecu could not find a neoecu.toml file, perhaps you are not running the command from inside the project path")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
config = neoecu.common.load_config(project_root)
|
||||||
|
|
||||||
|
pair = neoecu.common.check_pair(config)
|
||||||
|
if not pair:
|
||||||
|
print("neoecu currently only support building ST for M7 and Zephyr for M4, update your neoecu.toml")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if args.command == "build":
|
if args.command == "build":
|
||||||
from .commands.build import run
|
from .commands.build import run
|
||||||
run()
|
run(project_root, args)
|
||||||
elif args.command == "flash":
|
elif args.command == "flash":
|
||||||
from .commands.flash import run
|
from .commands.flash import run
|
||||||
run()
|
run()
|
||||||
|
elif args.command == "envcheck":
|
||||||
|
from .commands.envcheck import run
|
||||||
|
run()
|
||||||
|
elif args.command == "init":
|
||||||
|
from .commands.init import run
|
||||||
|
run(project_root)
|
||||||
|
elif args.command == "clean":
|
||||||
|
from .commands.clean import run
|
||||||
|
run(project_root)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|||||||
@@ -1,5 +1,94 @@
|
|||||||
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
def run():
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
import neoecu.common
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd: list[str], cwd: Path) -> bool:
|
||||||
|
result = subprocess.run(cmd, cwd=cwd)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def init_m7(path: Path) -> bool:
|
||||||
|
debug_ok = run_cmd(["cmake", "--preset", "Debug"], path)
|
||||||
|
release_ok = run_cmd(["cmake", "--preset", "Release"], path)
|
||||||
|
return debug_ok and release_ok
|
||||||
|
|
||||||
|
|
||||||
|
def build_m7(path: Path, mode: str, clean: bool) -> bool:
|
||||||
|
cmd = ["cmake", "--build", "--preset", mode]
|
||||||
|
|
||||||
|
if clean:
|
||||||
|
cmd.append("--clean-first")
|
||||||
|
|
||||||
|
return run_cmd(cmd, path)
|
||||||
|
|
||||||
|
|
||||||
|
def build_m4(path: Path, clean: bool) -> bool:
|
||||||
|
cmd = ["west", "build", "-b", "arduino_giga_r1/stm32h747xx/m4"]
|
||||||
|
|
||||||
|
if clean:
|
||||||
|
cmd.insert(2, "-p")
|
||||||
|
cmd.insert(3, "always")
|
||||||
|
|
||||||
|
return run_cmd(cmd, path)
|
||||||
|
|
||||||
|
|
||||||
|
def run(root_path: Path, args):
|
||||||
|
console = Console()
|
||||||
|
|
||||||
print("Building NeoECU...")
|
print("Building NeoECU...")
|
||||||
|
|
||||||
|
config = neoecu.common.load_config(root_path)
|
||||||
|
|
||||||
|
m7_root = root_path / config["build"]["m7_dir"]
|
||||||
|
m4_root = root_path / config["build"]["m4_dir"]
|
||||||
|
|
||||||
|
# Validate mode
|
||||||
|
if args.debug and args.release:
|
||||||
|
print("Cannot build Debug and Release at once")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
mode = "Debug" if args.debug else "Release"
|
||||||
|
|
||||||
|
# Ensure M7 initialized
|
||||||
|
m7_build_dir = m7_root / "build"
|
||||||
|
if not m7_build_dir.exists():
|
||||||
|
if not init_m7(m7_root):
|
||||||
|
print("Failed to init STM32 project")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
table = Table()
|
||||||
|
table.add_column("Step")
|
||||||
|
table.add_column("Status")
|
||||||
|
|
||||||
|
ok_str = "[green]OK[/green]"
|
||||||
|
fail_str = "[red]FAILED[/red]"
|
||||||
|
|
||||||
|
results: list[bool] = []
|
||||||
|
|
||||||
|
# M7 build
|
||||||
|
m7_ok = build_m7(m7_root, mode, args.clean)
|
||||||
|
table.add_row(f"M7 {mode}", ok_str if m7_ok else fail_str)
|
||||||
|
results.append(m7_ok)
|
||||||
|
|
||||||
|
# M4 build
|
||||||
|
m4_ok = build_m4(m4_root, args.clean)
|
||||||
|
table.add_row("M4 build", ok_str if m4_ok else fail_str)
|
||||||
|
results.append(m4_ok)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
if all(results):
|
||||||
|
print("Build success")
|
||||||
|
else:
|
||||||
|
print("Build failed")
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
98
src/neoecu/commands/clean.py
Normal file
98
src/neoecu/commands/clean.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
import neoecu.common
|
||||||
|
|
||||||
|
|
||||||
|
def safe_remove_dir(path: Path, root: Path, protected: list[Path]) -> bool:
|
||||||
|
"""
|
||||||
|
Safely remove a directory with strict protections:
|
||||||
|
- Must be inside project root
|
||||||
|
- Must not match protected paths
|
||||||
|
- Must contain "build" in its path
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
resolved = path.resolve()
|
||||||
|
root_resolved = root.resolve()
|
||||||
|
|
||||||
|
# Ensure inside project root
|
||||||
|
if not resolved.is_relative_to(root_resolved):
|
||||||
|
raise ValueError("outside project root")
|
||||||
|
|
||||||
|
# Ensure not protected
|
||||||
|
for p in protected:
|
||||||
|
if resolved == p.resolve():
|
||||||
|
raise ValueError("protected path")
|
||||||
|
|
||||||
|
# Ensure this is actually a build directory
|
||||||
|
if "build" not in resolved.parts:
|
||||||
|
raise ValueError("not a build directory")
|
||||||
|
|
||||||
|
# Remove if exists
|
||||||
|
if resolved.exists():
|
||||||
|
shutil.rmtree(resolved)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return True # treat non-existent as success
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def run(root_path: Path):
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
config = neoecu.common.load_config(root_path)
|
||||||
|
|
||||||
|
m7_root = root_path / config["build"]["m7_dir"]
|
||||||
|
m4_root = root_path / config["build"]["m4_dir"]
|
||||||
|
|
||||||
|
# Build directories
|
||||||
|
m7_root_build_dir = m7_root / "build"
|
||||||
|
m7_cm7_build_dir = m7_root / "CM7" / "build"
|
||||||
|
m7_cm4_build_dir = m7_root / "CM4" / "build"
|
||||||
|
m4_root_build_dir = m4_root / "build"
|
||||||
|
|
||||||
|
build_dirs = [
|
||||||
|
("M7 root build", m7_root_build_dir),
|
||||||
|
("M7 CM7 build", m7_cm7_build_dir),
|
||||||
|
("M7 CM4 build", m7_cm4_build_dir),
|
||||||
|
("M4 root build", m4_root_build_dir),
|
||||||
|
]
|
||||||
|
|
||||||
|
protected = [
|
||||||
|
root_path,
|
||||||
|
m7_root,
|
||||||
|
m4_root,
|
||||||
|
]
|
||||||
|
|
||||||
|
table = Table()
|
||||||
|
table.add_column("Directory")
|
||||||
|
table.add_column("Status")
|
||||||
|
|
||||||
|
ok_str = "[green]OK[/green]"
|
||||||
|
fail_str = "[red]FAILED[/red]"
|
||||||
|
|
||||||
|
results: list[bool] = []
|
||||||
|
|
||||||
|
for name, path in build_dirs:
|
||||||
|
result = safe_remove_dir(path, root_path, protected)
|
||||||
|
table.add_row(name, ok_str if result else fail_str)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
if all(results):
|
||||||
|
print("Clean success")
|
||||||
|
else:
|
||||||
|
print("Clean completed with errors")
|
||||||
|
sys.exit(1)
|
||||||
103
src/neoecu/commands/envcheck.py
Normal file
103
src/neoecu/commands/envcheck.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.console import Console
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
|
||||||
|
def which_check(tool: str) -> bool:
|
||||||
|
return shutil.which(tool) is not None
|
||||||
|
|
||||||
|
def has_newlib() -> bool:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["arm-none-eabi-gcc", "-print-file-name=libc.a"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
path = result.stdout.strip()
|
||||||
|
|
||||||
|
# If gcc returns just "libc.a", it means NOT found
|
||||||
|
return path != "libc.a"
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_env(var: str) -> str | None:
|
||||||
|
return os.environ.get(var)
|
||||||
|
|
||||||
|
def run():
|
||||||
|
missing_str = "[red]MISSING[/red]"
|
||||||
|
ok_str = "[green]OK[/green]"
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
print("Checking build tools")
|
||||||
|
|
||||||
|
|
||||||
|
build_tools_table = Table()
|
||||||
|
|
||||||
|
build_tools_table.add_column("Tool")
|
||||||
|
build_tools_table.add_column("Status")
|
||||||
|
|
||||||
|
tools = ["ninja", "cmake", "west", "STM32CubeMX", "JLinkExe"]
|
||||||
|
for tool in tools:
|
||||||
|
result = which_check(tool)
|
||||||
|
if result:
|
||||||
|
build_tools_table.add_row(tool, ok_str)
|
||||||
|
else:
|
||||||
|
build_tools_table.add_row(tool, missing_str)
|
||||||
|
|
||||||
|
zephyr_base = get_env("ZEPHYR_BASE")
|
||||||
|
if zephyr_base is None:
|
||||||
|
build_tools_table.add_row("Zephyr Base", missing_str)
|
||||||
|
else:
|
||||||
|
build_tools_table.add_row("Zephyr Base", ok_str)
|
||||||
|
|
||||||
|
zephyr_sdk = get_env("ZEPHYR_SDK_INSTALL_DIR")
|
||||||
|
if zephyr_sdk is None:
|
||||||
|
build_tools_table.add_row("Zephyr SDK", missing_str)
|
||||||
|
else:
|
||||||
|
build_tools_table.add_row("Zephyr SDK", ok_str)
|
||||||
|
|
||||||
|
console.print(build_tools_table)
|
||||||
|
|
||||||
|
|
||||||
|
build_deps_table = Table()
|
||||||
|
|
||||||
|
build_deps_table.add_column("Dependency")
|
||||||
|
build_deps_table.add_column("Status")
|
||||||
|
|
||||||
|
deps = [
|
||||||
|
# Compiler
|
||||||
|
"arm-none-eabi-gcc",
|
||||||
|
|
||||||
|
# Binutils (core)
|
||||||
|
"arm-none-eabi-ld", # linker
|
||||||
|
"arm-none-eabi-as", # assembler
|
||||||
|
"arm-none-eabi-ar", # archive tool
|
||||||
|
"arm-none-eabi-nm", # symbol table
|
||||||
|
"arm-none-eabi-objcopy", # binary conversion
|
||||||
|
"arm-none-eabi-objdump", # disassembler
|
||||||
|
"arm-none-eabi-strip", # strip symbols
|
||||||
|
"arm-none-eabi-size", # size info
|
||||||
|
"arm-none-eabi-strings", # extract strings
|
||||||
|
"arm-none-eabi-readelf", # ELF inspection
|
||||||
|
]
|
||||||
|
for dep in deps:
|
||||||
|
result = which_check(dep)
|
||||||
|
if result:
|
||||||
|
build_deps_table.add_row(dep, "[green]OK[/green]")
|
||||||
|
else:
|
||||||
|
build_deps_table.add_row(dep, "[red]MISSING[/red]")
|
||||||
|
|
||||||
|
if has_newlib():
|
||||||
|
build_deps_table.add_row("arm-none-eabi-newlib", "[green]OK[/green]")
|
||||||
|
else:
|
||||||
|
build_deps_table.add_row("arm-none-eabi-newlib", "[red]MISSING[/red]")
|
||||||
|
|
||||||
|
console.print(build_deps_table)
|
||||||
|
|
||||||
91
src/neoecu/commands/init.py
Normal file
91
src/neoecu/commands/init.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
from neoecu.commands import build
|
||||||
|
from neoecu.common import load_config
|
||||||
|
|
||||||
|
|
||||||
|
def get_env(var: str) -> str | None:
|
||||||
|
return os.environ.get(var)
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd: list[str], cwd: Path | None = None) -> bool:
|
||||||
|
result = subprocess.run(cmd, cwd=cwd)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def run(root_path: Path):
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
ok_str = "[green]OK[/green]"
|
||||||
|
fail_str = "[red]FAILED[/red]"
|
||||||
|
|
||||||
|
print("Initializing project")
|
||||||
|
|
||||||
|
table = Table()
|
||||||
|
table.add_column("Step")
|
||||||
|
table.add_column("Status")
|
||||||
|
|
||||||
|
results: list[bool] = []
|
||||||
|
|
||||||
|
# Step 1 — Install west
|
||||||
|
west_ok = run_cmd(["uv", "pip", "install", "west"])
|
||||||
|
table.add_row("Install west", ok_str if west_ok else fail_str)
|
||||||
|
results.append(west_ok)
|
||||||
|
|
||||||
|
# Step 2 — Zephyr requirements
|
||||||
|
requirements = get_env("ZEPHYR_BASE")
|
||||||
|
if requirements is None:
|
||||||
|
table.add_row("Zephyr requirements", "[red]ZEPHYR_BASE not set[/red]")
|
||||||
|
console.print(table)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
requirements_path = Path(requirements) / "scripts" / "requirements.txt"
|
||||||
|
|
||||||
|
deps_ok = run_cmd(["uv", "pip", "install", "-r", str(requirements_path)])
|
||||||
|
table.add_row("Install Zephyr deps", ok_str if deps_ok else fail_str)
|
||||||
|
results.append(deps_ok)
|
||||||
|
|
||||||
|
# Step 3 — Load config
|
||||||
|
config = load_config(root_path)
|
||||||
|
m7_root = root_path / config["build"]["m7_dir"]
|
||||||
|
m4_root = root_path / config["build"]["m4_dir"]
|
||||||
|
|
||||||
|
# Step 4 — CMake Debug Init
|
||||||
|
debug_ok = run_cmd(["cmake", "--preset", "Debug"], cwd=m7_root)
|
||||||
|
table.add_row("CMake Debug Init", ok_str if debug_ok else fail_str)
|
||||||
|
results.append(debug_ok)
|
||||||
|
|
||||||
|
# Step 5 — CMake Release Init
|
||||||
|
release_ok = run_cmd(["cmake", "--preset", "Release"], cwd=m7_root)
|
||||||
|
table.add_row("CMake Release Init", ok_str if release_ok else fail_str)
|
||||||
|
results.append(release_ok)
|
||||||
|
|
||||||
|
# Step 6 - CMake Debug Build
|
||||||
|
build_ok = run_cmd(["cmake", "--build", "--preset", "Debug", "--clean-first", "--verbose"], cwd=m7_root)
|
||||||
|
table.add_row("CMake Debug Build", ok_str if build_ok else fail_str)
|
||||||
|
results.append(build_ok)
|
||||||
|
|
||||||
|
# Step 7 - Zephyr Debug
|
||||||
|
zephyr_ok = run_cmd(["west", "build","-p", "always", "-b", "arduino_giga_r1/stm32h747xx/m4"], cwd=m4_root)
|
||||||
|
table.add_row("Zephyr Build", ok_str if zephyr_ok else fail_str)
|
||||||
|
results.append(zephyr_ok)
|
||||||
|
|
||||||
|
# Print results table
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
# Final result
|
||||||
|
if all(results):
|
||||||
|
print("Init success")
|
||||||
|
else:
|
||||||
|
print("Init failed")
|
||||||
|
sys.exit(1)
|
||||||
33
src/neoecu/common.py
Normal file
33
src/neoecu/common.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
||||||
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from pathlib import Path
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
def find_project_root(
|
||||||
|
start: Path | None = None,
|
||||||
|
filename: str = "neoecu.toml",
|
||||||
|
max_depth: int = 10
|
||||||
|
) -> Path | None:
|
||||||
|
current = start or Path.cwd()
|
||||||
|
|
||||||
|
for _ in range(max_depth + 1):
|
||||||
|
if (current / filename).exists():
|
||||||
|
return current # return the directory (project root)
|
||||||
|
|
||||||
|
if current.parent == current:
|
||||||
|
break
|
||||||
|
|
||||||
|
current = current.parent
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_config(root: Path) -> dict:
|
||||||
|
config_path = root / "neoecu.toml"
|
||||||
|
with open(config_path, "rb") as f:
|
||||||
|
return tomllib.load(f)
|
||||||
|
|
||||||
|
def check_pair(config: dict) -> bool:
|
||||||
|
if (config["build"]["m7_type"] == "ST") & (config["build"]["m4_type"] == "Zephyr"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
57
uv.lock
generated
Normal file
57
uv.lock
generated
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mdurl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "neoecu"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "rich" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "rich", specifier = ">=14.3.3" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "14.3.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markdown-it-py" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user