Merge pull request #47 from DARC-e-V/feat/cli
feat: implement CLI controls for 50ohm downloader / builder
This commit is contained in:
-11
@@ -1,11 +0,0 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: 50ohm.de
|
||||
image: ghcr.io/astral-sh/uv:python3.12-bookworm
|
||||
environment:
|
||||
commands:
|
||||
- uv pip install --system --break-system-packages --requirements requirements.txt
|
||||
- pytest --junitxml=./test-reports/junit.xml
|
||||
+6
-19
@@ -1,22 +1,9 @@
|
||||
FROM python:3.12
|
||||
FROM python:3.12-slim
|
||||
|
||||
# RUN apt-get update -qq && apt-get install -y \
|
||||
# wget \
|
||||
# libswitch-perl \
|
||||
# gnupg \
|
||||
# && apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
#
|
||||
# COPY ./texlive.profile /tmp/
|
||||
# RUN cd /tmp && wget -qO- https://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz | tar xz \
|
||||
# && /tmp/install-tl*/install-tl -profile /tmp/texlive.profile \
|
||||
# && rm -rf /tmp/install-tl-*
|
||||
#
|
||||
# ENV PATH="/usr/local/texlive/bin/x86_64-linux:${PATH}"
|
||||
WORKDIR /app
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
COPY --parents api assets latex renderer src templates main.py LICENSE ./
|
||||
|
||||
COPY --from=texlive/texlive:latest /usr/local/texlive /usr/local/texlive
|
||||
|
||||
RUN $(find /usr/local/texlive -name tlmgr) path add && \
|
||||
# Temporary fix for ConTeXt (#30)
|
||||
(sed -i '/package.loaded\["data-ini"\]/a if os.selfpath then environment.ownbin=lfs.symlinktarget(os.selfpath..io.fileseparator..os.selfname);environment.ownpath=environment.ownbin:match("^.*"..io.fileseparator) else environment.ownpath=kpse.new("luatex"):var_value("SELFAUTOLOC");environment.ownbin=environment.ownpath..io.fileseparator..(arg[-2] or arg[-1] or arg[0] or "luatex"):match("[^"..io.fileseparator.."]*$") end' /usr/bin/mtxrun.lua || true)
|
||||
ENTRYPOINT ["python3", "main.py"]
|
||||
@@ -0,0 +1,47 @@
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
||||
import src.build as build
|
||||
import src.config as config
|
||||
from src.edition import Edition
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
|
||||
@app.command()
|
||||
def main(
|
||||
edition: Annotated[list[Edition], typer.Option(help="Edition to build, can be specified multiple times.")] = [ # noqa: B006 -- default value is required for typer
|
||||
Edition.n,
|
||||
Edition.e,
|
||||
Edition.a,
|
||||
Edition.ne,
|
||||
Edition.ea,
|
||||
Edition.nea,
|
||||
],
|
||||
input: Annotated[str | None, typer.Option("--input", "-i", help="Content source directory.")] = None,
|
||||
output: Annotated[str | None, typer.Option("--output", "-o", help="Destination directory to build to.")] = None,
|
||||
render_editions: Annotated[bool, typer.Option(help="Skip building editions.")] = True,
|
||||
render_solutions: Annotated[bool, typer.Option(help="Skip building solutions.")] = True,
|
||||
) -> None:
|
||||
conf = config.Config(content_path=input, build_path=output)
|
||||
bd = build.Build(conf)
|
||||
|
||||
# Build surrounding website
|
||||
bd.build_website()
|
||||
|
||||
if render_editions:
|
||||
# Build individual editions
|
||||
for e in edition:
|
||||
bd.build_edition(e)
|
||||
|
||||
if render_solutions:
|
||||
# Build solution pages
|
||||
bd.build_solutions()
|
||||
|
||||
# Copy assets to build folder
|
||||
bd.build_assets()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
+2
-1
@@ -9,8 +9,9 @@ dependencies = [
|
||||
"joblib==1.5.1",
|
||||
"mistletoe==1.4.0",
|
||||
"requests==2.32.4",
|
||||
"rich>=14.2.0",
|
||||
"tqdm==4.67.1",
|
||||
"gitpython==3.1.46",
|
||||
"typer>=0.21.1",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import sys
|
||||
|
||||
from mistletoe import cli
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cli.main(sys.argv[1:])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+29
-9
@@ -1,20 +1,40 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile pyproject.toml -o requirements.txt
|
||||
certifi==2025.6.15
|
||||
# via requests
|
||||
charset-normalizer==3.4.2
|
||||
coverage==7.9.2
|
||||
# via requests
|
||||
click==8.3.1
|
||||
# via typer
|
||||
idna==3.10
|
||||
iniconfig==2.1.0
|
||||
# via requests
|
||||
jinja2==3.1.6
|
||||
# via 50ohm-de (pyproject.toml)
|
||||
joblib==1.5.1
|
||||
# via 50ohm-de (pyproject.toml)
|
||||
markdown-it-py==4.0.0
|
||||
# via rich
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
mistletoe==1.4.0
|
||||
packaging==25.0
|
||||
pluggy==1.6.0
|
||||
# via 50ohm-de (pyproject.toml)
|
||||
pygments==2.19.2
|
||||
pytest==8.4.1
|
||||
pytest-cov==6.2.1
|
||||
# via rich
|
||||
requests==2.32.4
|
||||
ruff==0.12.2
|
||||
# via 50ohm-de (pyproject.toml)
|
||||
rich==14.3.2
|
||||
# via
|
||||
# 50ohm-de (pyproject.toml)
|
||||
# typer
|
||||
shellingham==1.5.4
|
||||
# via typer
|
||||
tqdm==4.67.1
|
||||
ty==0.0.1a13
|
||||
# via 50ohm-de (pyproject.toml)
|
||||
typer==0.21.1
|
||||
# via 50ohm-de (pyproject.toml)
|
||||
typing-extensions==4.15.0
|
||||
# via typer
|
||||
urllib3==2.5.0
|
||||
gitpython==3.1.46
|
||||
# via requests
|
||||
|
||||
+52
-18
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from mistletoe import Document
|
||||
from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn, TimeRemainingColumn
|
||||
from tqdm import tqdm
|
||||
|
||||
from renderer.fifty_ohm_html_renderer import FiftyOhmHtmlRenderer
|
||||
@@ -18,7 +19,7 @@ class Build:
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
|
||||
self.env = Environment(loader=FileSystemLoader("templates/"))
|
||||
self.env = Environment(loader=FileSystemLoader(self.config.p_templates))
|
||||
self.env.filters["shuffle_answers"] = self.__filter_shuffle_answers
|
||||
self.questions = self.__parse_katalog()
|
||||
|
||||
@@ -152,7 +153,7 @@ class Build:
|
||||
except FileNotFoundError:
|
||||
tqdm.write(f"\033[31mPhoto #{id} not found\033[0m")
|
||||
|
||||
def __build_chapter(self, edition, edition_name, number, chapter, next_chapter=None):
|
||||
def __build_chapter_index(self, edition, edition_name, number, chapter, next_chapter=None):
|
||||
chapter_template = self.env.get_template("html/chapter.html")
|
||||
next_chapter_template = self.env.get_template("html/next_chapter.html")
|
||||
with (self.config.p_build / f"{edition}_chapter_{chapter['ident']}.html").open("w") as file:
|
||||
@@ -241,7 +242,9 @@ class Build:
|
||||
result = self.__build_page(result, course_wrapper=True)
|
||||
file.write(result)
|
||||
|
||||
def __build_chapter_slidedeck(self, edition, chapter, sections, next_chapter, chapter_number=None):
|
||||
def __build_chapter_slidedeck(
|
||||
self, edition, chapter, sections, next_chapter, chapter_number=None, progress: Progress = None
|
||||
):
|
||||
with (self.config.p_build / f"{edition}_slide_{chapter['ident']}.html").open("w") as file:
|
||||
slide_template = self.env.get_template("slide/slide.html")
|
||||
help_template = self.env.get_template("slide/help.html")
|
||||
@@ -270,7 +273,10 @@ class Build:
|
||||
result += "</section>\n"
|
||||
|
||||
section_counter = 0
|
||||
for section in sections:
|
||||
slides_task = progress.add_task("Rendering slides ...")
|
||||
for section in progress.track(sections, task_id=slides_task):
|
||||
progress.update(slides_task, description=f"Rendering slides of {section['title']}")
|
||||
|
||||
if section["slide"] is None:
|
||||
continue
|
||||
|
||||
@@ -293,6 +299,8 @@ class Build:
|
||||
tmp += renderer.render(doc)
|
||||
result += f"<section>{tmp}</section>\n"
|
||||
|
||||
progress.remove_task(slides_task)
|
||||
|
||||
result += next_template.render(
|
||||
edition=edition,
|
||||
next_chapter=next_chapter,
|
||||
@@ -332,17 +340,45 @@ class Build:
|
||||
result = self.__build_page(result)
|
||||
file.write(result)
|
||||
|
||||
def build_edition(self, edition):
|
||||
def build_edition(self, edition: str):
|
||||
self.config.p_build.mkdir(exist_ok=True)
|
||||
|
||||
edition = edition.upper()
|
||||
|
||||
with (self.config.p_data_toc / f"{edition}.json").open() as file:
|
||||
with (
|
||||
(self.config.p_data_toc / f"{edition}.json").open() as file,
|
||||
Progress(
|
||||
TaskProgressColumn(),
|
||||
BarColumn(),
|
||||
TimeRemainingColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
transient=True,
|
||||
) as progress,
|
||||
):
|
||||
chapter_task = progress.add_task(f"Building edition {edition} ...")
|
||||
book = json.load(file)
|
||||
edition_name = book["title"]
|
||||
|
||||
# Create index pages for chapters and slides.
|
||||
self.__build_book_index(book)
|
||||
self.__build_slide_index(book)
|
||||
|
||||
chapters = book["chapters"]
|
||||
|
||||
for chapter in chapters:
|
||||
for section in chapter["sections"]:
|
||||
for chapter_number, chapter in enumerate(progress.track(chapters, task_id=chapter_task), 1):
|
||||
progress.update(chapter_task, description=f"Building edition {edition}: Chapter {chapter['title']}")
|
||||
|
||||
# Determine next chapter for navigation (None if this is the last chapter)
|
||||
next_chapter = chapters[chapter_number] if chapter_number < len(chapters) else None
|
||||
|
||||
self.__build_chapter_index(edition, edition_name, chapter_number, chapter, next_chapter)
|
||||
|
||||
# Open, parse and render each section.
|
||||
section_task = progress.add_task(description="Rendering sections ...")
|
||||
for section_number, section in enumerate(progress.track(chapter["sections"], task_id=section_task), 1):
|
||||
progress.update(section_task, description=f"Rendering section {section['title']}")
|
||||
|
||||
# Read section and slide content from the corresponding files.
|
||||
ident = section["ident"]
|
||||
section["content"] = None
|
||||
section["slide"] = None
|
||||
@@ -353,16 +389,6 @@ class Build:
|
||||
section_content = sfile.read()
|
||||
section["slide"] = section_content
|
||||
|
||||
edition_name = book["title"]
|
||||
self.__build_book_index(book)
|
||||
self.__build_slide_index(book)
|
||||
for chapter_number, chapter in enumerate(tqdm(chapters, desc=f"Build Edition: {edition}"), 1):
|
||||
next_chapter = chapters[chapter_number] if chapter_number < len(chapters) else None
|
||||
self.__build_chapter(edition, edition_name, chapter_number, chapter, next_chapter)
|
||||
self.__build_chapter_slidedeck(edition, chapter, chapter["sections"], next_chapter, chapter_number)
|
||||
|
||||
for section_number, section in enumerate(chapter["sections"], 1):
|
||||
tqdm.write(f"Rendering section {section['title']}")
|
||||
next_section = (
|
||||
chapter["sections"][section_number] if section_number < len(chapter["sections"]) else None
|
||||
)
|
||||
@@ -377,6 +403,14 @@ class Build:
|
||||
chapter_number,
|
||||
)
|
||||
|
||||
progress.remove_task(section_task)
|
||||
# Build the slidedeck after all sections have been processed, as they require the files to be read.
|
||||
self.__build_chapter_slidedeck(
|
||||
edition, chapter, chapter["sections"], next_chapter, chapter_number, progress
|
||||
)
|
||||
|
||||
progress.remove_task(chapter_task)
|
||||
|
||||
def build_assets(self):
|
||||
self.config.p_build.mkdir(exist_ok=True)
|
||||
shutil.copytree(
|
||||
|
||||
+4
-3
@@ -6,14 +6,14 @@ from pathlib import Path
|
||||
class Config:
|
||||
__env_prefix = "OHM"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, content_path: str = None, build_path: str = None):
|
||||
if os.path.isfile("config/config.json"):
|
||||
with open("config/config.json") as file:
|
||||
self.config = json.load(file)
|
||||
else:
|
||||
self.config = {}
|
||||
|
||||
self.p_data = Path(self.get_config_value("content_path", "/var/www/content"))
|
||||
self.p_data = Path(self.get_config_value("input", "content") if content_path is None else content_path)
|
||||
|
||||
self.p_data_toc = self.p_data / "toc"
|
||||
|
||||
@@ -31,12 +31,13 @@ class Config:
|
||||
self.p_data_fragenkatalog = self.p_data_questions / "fragenkatalog3b.json"
|
||||
self.p_data_metadata = self.p_data_questions / "metadata3b.json"
|
||||
|
||||
self.p_build = Path("./build")
|
||||
self.p_build = Path(self.get_config_value("output", "./build") if build_path is None else build_path)
|
||||
self.p_build_photos = self.p_build / "photos"
|
||||
self.p_build_pictures = self.p_build / "pictures"
|
||||
self.p_build_assets = self.p_build / "assets"
|
||||
|
||||
self.p_assets = Path("./assets")
|
||||
self.p_templates = Path("./templates")
|
||||
|
||||
def get_config_value(self, key: str, default=None):
|
||||
if key in self.config:
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Edition(str, Enum):
|
||||
n = "N"
|
||||
e = "E"
|
||||
a = "A"
|
||||
ne = "NE"
|
||||
ea = "EA"
|
||||
nea = "NEA"
|
||||
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
@@ -11,7 +11,9 @@ dependencies = [
|
||||
{ name = "joblib" },
|
||||
{ name = "mistletoe" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
@@ -28,7 +30,9 @@ requires-dist = [
|
||||
{ name = "joblib", specifier = "==1.5.1" },
|
||||
{ name = "mistletoe", specifier = "==1.4.0" },
|
||||
{ name = "requests", specifier = "==2.32.4" },
|
||||
{ name = "rich", specifier = ">=14.2.0" },
|
||||
{ name = "tqdm", specifier = "==4.67.1" },
|
||||
{ name = "typer", specifier = ">=0.21.1" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -83,6 +87,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@@ -173,6 +189,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" },
|
||||
]
|
||||
|
||||
[[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 = "markupsafe"
|
||||
version = "3.0.2"
|
||||
@@ -211,6 +239,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
|
||||
]
|
||||
|
||||
[[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 = "mistletoe"
|
||||
version = "1.4.0"
|
||||
@@ -292,6 +329,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.2"
|
||||
@@ -317,6 +367,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/1f/72d2946e3cc7456bb837e88000eb3437e55f80db339c840c04015a11115d/ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12", size = 10735334, upload-time = "2025-07-03T16:40:17.677Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.67.1"
|
||||
@@ -354,6 +413,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/62/fe6cc92db2aa1a344937e5313ba3bbdaed24e54332b8a12fe28a92d314ce/ty-0.0.1a13-py3-none-win_arm64.whl", hash = "sha256:5f2237bb301078d51d006170992abb1e5f3b37cec47a11e6db416a76346f60b5", size = 6727050, upload-time = "2025-07-02T17:52:12.874Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.21.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
{ name = "shellingham" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
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 = "urllib3"
|
||||
version = "2.5.0"
|
||||
|
||||
Reference in New Issue
Block a user