Compare commits
11 commits
0d2e68a03d
...
9b2dc37279
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b2dc37279 | |||
| 46135f9325 | |||
| 620a7bc401 | |||
| cb5e38b503 | |||
| 97478d62d1 | |||
| ee8fef930a | |||
| bd05dadf56 | |||
| db11128beb | |||
| ce8ffa3ae8 | |||
| d103a632d0 | |||
| f8f0a2077d |
5 changed files with 281 additions and 80 deletions
|
|
@ -25,4 +25,5 @@ typeCheckingMode = "standard"
|
|||
[dependency-groups]
|
||||
dev = [
|
||||
"pdoc>=15.0.1",
|
||||
"pytest>=9.0.1",
|
||||
]
|
||||
|
|
|
|||
0
test/__init__.py
Normal file
0
test/__init__.py
Normal file
122
test/test_configuration.py
Normal file
122
test/test_configuration.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from topen import OPTIONS, TConf, parse_cli, parse_env, parse_rc
|
||||
|
||||
|
||||
class TestCli:
|
||||
def test_cli_minimum_id(self, monkeypatch):
|
||||
monkeypatch.setattr("sys.argv", ["topen", "42"])
|
||||
assert parse_cli() == {"task_id": "42"}
|
||||
|
||||
def test_cli_options(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"sys.argv",
|
||||
[
|
||||
"topen",
|
||||
"123",
|
||||
"--extension",
|
||||
"txt",
|
||||
"--editor",
|
||||
"vim",
|
||||
"--quiet",
|
||||
"True",
|
||||
],
|
||||
)
|
||||
assert parse_cli() == {
|
||||
"task_id": "123",
|
||||
"notes_ext": "txt",
|
||||
"notes_editor": "vim",
|
||||
"notes_quiet": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isolate_env(monkeypatch):
|
||||
# delete all existing env vars that could interfere
|
||||
monkeypatch.delenv("EDITOR", raising=False)
|
||||
monkeypatch.delenv("VISUAL", raising=False)
|
||||
for opt in OPTIONS.values():
|
||||
if opt.env:
|
||||
monkeypatch.delenv(opt.env, raising=False)
|
||||
|
||||
|
||||
class TestEnv:
|
||||
def test_env_notes_ext(self, isolate_env, monkeypatch):
|
||||
monkeypatch.setenv("TOPEN_NOTES_DIR", "/blablubb")
|
||||
monkeypatch.setenv("TOPEN_NOTES_EXT", "rst")
|
||||
monkeypatch.setenv("TOPEN_NOTES_ANNOT", "qmd")
|
||||
monkeypatch.setenv("TOPEN_NOTES_EDITOR", "vim")
|
||||
monkeypatch.setenv("TOPEN_NOTES_QUIET", "true")
|
||||
env = parse_env()
|
||||
assert env["notes_dir"] == Path("/blablubb")
|
||||
assert env["notes_ext"] == "rst"
|
||||
assert env["notes_annot"] == "qmd"
|
||||
assert env["notes_editor"] == "vim"
|
||||
assert env["notes_quiet"] is True
|
||||
|
||||
def test_env_task_rc(self, isolate_env, monkeypatch):
|
||||
monkeypatch.setenv("TASKRC", "/a/dir/that/dont/exist/file")
|
||||
monkeypatch.setenv("TASKDATA", "~/somewhere/tasks")
|
||||
env = parse_env()
|
||||
assert env["task_rc"] == Path("/a/dir/that/dont/exist/file")
|
||||
assert env["task_data"] == Path("~/somewhere/tasks")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_rc(tmp_path: Path, monkeypatch):
|
||||
rc = tmp_path / "test.taskrc"
|
||||
monkeypatch.setattr(OPTIONS["task_rc"], "default", rc)
|
||||
return rc
|
||||
|
||||
|
||||
class TestRcFile:
|
||||
def test_taskrc_parsing(self, fake_rc):
|
||||
fake_rc.write_text("""
|
||||
data.location=~/.taskies
|
||||
notes.dir=/there
|
||||
notes.ext=yaml
|
||||
notes.annot=Boo!
|
||||
notes.editor=micro
|
||||
notes.quiet=true
|
||||
""")
|
||||
rc_cfg = parse_rc(fake_rc)
|
||||
assert rc_cfg["task_data"] == Path("~/.taskies")
|
||||
assert rc_cfg["notes_dir"] == Path("/there")
|
||||
assert rc_cfg["notes_ext"] == "yaml"
|
||||
assert rc_cfg["notes_annot"] == "Boo!"
|
||||
assert rc_cfg["notes_editor"] == "micro"
|
||||
assert rc_cfg["notes_quiet"] is True
|
||||
|
||||
|
||||
class TestTConf:
|
||||
def test_paths_are_expanded(self):
|
||||
cfg = TConf.from_dict(
|
||||
{
|
||||
"task_data": "~/somewhere/tasks",
|
||||
"task_id": 0,
|
||||
"task_rc": "$HOME/taskrc",
|
||||
"notes_dir": "$HOME/notes",
|
||||
}
|
||||
)
|
||||
assert cfg.task_data == Path("~/somewhere/tasks").expanduser()
|
||||
assert cfg.task_rc == Path("~/taskrc").expanduser()
|
||||
assert cfg.notes_dir == Path("~/notes").expanduser()
|
||||
|
||||
def test_default_notes_sub_dir(self):
|
||||
cfg = TConf.from_dict({"task_data": "~/my/tasks", "task_id": 0})
|
||||
assert cfg.notes_dir == Path("~/my/tasks/notes").expanduser()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env,expected",
|
||||
[
|
||||
({"EDITOR": "vim"}, "vim"),
|
||||
({"VISUAL": "emacs", "EDITOR": ""}, "emacs"),
|
||||
({"VISUAL": "nvim", "EDITOR": "notepad"}, "notepad"),
|
||||
],
|
||||
)
|
||||
def test_editor_env_resolution(isolate_env, monkeypatch, env, expected):
|
||||
for k, v in env.items():
|
||||
monkeypatch.setenv(k, v)
|
||||
assert TConf(0).notes_editor == expected
|
||||
118
topen.py
118
topen.py
|
|
@ -23,14 +23,14 @@ import subprocess
|
|||
import sys
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Self
|
||||
from typing import Any, Self, cast
|
||||
|
||||
from tasklib import Task, TaskWarrior
|
||||
|
||||
NON_EXISTENT_PATH = Path("%%%%I_DONT_EXIST_%%%%")
|
||||
|
||||
|
||||
def main(cfg: "TConf | None" = None):
|
||||
def main(cfg: "TConf | None" = None, io: "_IO | None" = None) -> int:
|
||||
"""Runs the cli interface.
|
||||
|
||||
First sets up the correct options, with overrides in the following order:
|
||||
|
|
@ -42,32 +42,40 @@ def main(cfg: "TConf | None" = None):
|
|||
pointing to the file.
|
||||
|
||||
If the task does not yet have a note annotation it also adds it automatically.
|
||||
|
||||
Returns the status code as int, 0 for success, 1 for error.
|
||||
"""
|
||||
if not cfg:
|
||||
cfg = build_config()
|
||||
if not io:
|
||||
io = _IO(quiet=cfg.notes_quiet)
|
||||
|
||||
if not cfg.task_id:
|
||||
_ = sys.stderr.write("Please provide task ID as argument.\n")
|
||||
if cfg.notes_quiet:
|
||||
global IS_QUIET
|
||||
IS_QUIET = True
|
||||
_ = io.err("Please provide task ID as argument.\n")
|
||||
return 1
|
||||
|
||||
try:
|
||||
task = get_task(id=cfg.task_id, data_location=cfg.task_data)
|
||||
uuid = task["uuid"]
|
||||
if not uuid:
|
||||
_ = sys.stderr.write(f"Could not find task for ID: {cfg.task_id}.")
|
||||
sys.exit(1)
|
||||
uuid = cast(str, task["uuid"])
|
||||
except Task.DoesNotExist:
|
||||
_ = io.err(f"Could not find task for ID: {cfg.task_id}.\n")
|
||||
return 1
|
||||
|
||||
fpath = get_notes_file(uuid, notes_dir=cfg.notes_dir, notes_ext=cfg.notes_ext)
|
||||
|
||||
if not fpath.parent.exists():
|
||||
fpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
io.out(f"Editing note: {fpath}")
|
||||
open_editor(fpath, editor=cfg.notes_editor)
|
||||
|
||||
if fpath.exists():
|
||||
add_annotation_if_missing(task, annotation_content=cfg.notes_annot)
|
||||
return
|
||||
whisper("No note file, doing nothing.")
|
||||
if is_annotation_missing(task, annotation_content=cfg.notes_annot):
|
||||
add_annotation(task, annotation_content=cfg.notes_annot)
|
||||
io.out(f"Added annotation: {cfg.notes_annot}")
|
||||
return 0
|
||||
io.out("No note file, doing nothing.")
|
||||
return 0
|
||||
|
||||
|
||||
def get_task(id: str | int, data_location: Path) -> Task:
|
||||
|
|
@ -91,22 +99,26 @@ def get_notes_file(uuid: str, notes_dir: Path, notes_ext: str) -> Path:
|
|||
|
||||
def open_editor(file: Path, editor: str) -> None:
|
||||
"""Opens a file with the chosen editor."""
|
||||
whisper(f"Editing note: {file}")
|
||||
_ = subprocess.run(f"{editor} {file}", shell=True)
|
||||
|
||||
|
||||
def add_annotation_if_missing(task: Task, annotation_content: str) -> None:
|
||||
"""Conditionally adds an annotation to a task.
|
||||
def is_annotation_missing(task: Task, annotation_content: str) -> bool:
|
||||
"""Checks if the task is missing the annotation.
|
||||
|
||||
Only adds the annotation if the task does not yet have an
|
||||
annotation with exactly that content (i.e. avoids
|
||||
duplication).
|
||||
Only succeeds if the _complete_ annatotation is found,
|
||||
and not just as a substring.
|
||||
|
||||
Returns True if annotation was added, otherwise False.
|
||||
"""
|
||||
for annot in task["annotations"] or []:
|
||||
if annot["description"] == annotation_content:
|
||||
return
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def add_annotation(task: Task, annotation_content: str) -> None:
|
||||
"""Adds an annotation to a task."""
|
||||
task.add_annotation(annotation_content)
|
||||
_ = whisper(f"Added annotation: {annotation_content}")
|
||||
|
||||
|
||||
@dataclass()
|
||||
|
|
@ -122,13 +134,25 @@ class Opt:
|
|||
help_text: str = ""
|
||||
|
||||
|
||||
def _real_path(p: Path | str) -> Path:
|
||||
return Path(os.path.expandvars(p)).expanduser()
|
||||
|
||||
|
||||
def _determine_default_task_rc() -> Path:
|
||||
if _real_path("~/.taskrc").exists():
|
||||
return _real_path("~/.taskrc")
|
||||
if _real_path("$XDG_CONFIG_HOME/task/taskrc").exists():
|
||||
return _real_path("$XDG_CONFIG_HOME/task/taskrc")
|
||||
return _real_path("~/.config/task/taskrc")
|
||||
|
||||
|
||||
OPTIONS: dict[str, Opt] = {
|
||||
"task_id": Opt(None, None, None, default=None),
|
||||
"task_rc": Opt(
|
||||
("--task-rc",),
|
||||
"TASKRC",
|
||||
None, # taskrc has no key for this
|
||||
default=Path("~/.taskrc"),
|
||||
default=_determine_default_task_rc(),
|
||||
metavar="FILE",
|
||||
cast=Path,
|
||||
help_text="Location of taskwarrior config file",
|
||||
|
|
@ -171,7 +195,7 @@ OPTIONS: dict[str, Opt] = {
|
|||
("--editor",),
|
||||
"TOPEN_NOTES_EDITOR",
|
||||
"notes.editor",
|
||||
default=os.getenv("EDITOR") or os.getenv("VISUAL") or "nano",
|
||||
default="nano",
|
||||
metavar="CMD",
|
||||
help_text="Program to open note files with",
|
||||
),
|
||||
|
|
@ -195,44 +219,40 @@ class TConf:
|
|||
|
||||
task_id: int
|
||||
"""The id (or uuid) of the task to edit a note for."""
|
||||
task_rc: Path = NON_EXISTENT_PATH
|
||||
task_rc: Path = OPTIONS["task_rc"].default
|
||||
"""The path to the taskwarrior taskrc file. Can be absolute or relative to cwd."""
|
||||
|
||||
task_data: Path = Path("~/.task")
|
||||
task_data: Path = OPTIONS["task_data"].default
|
||||
"""The path to the taskwarrior data directory. Can be absolute or relative to cwd."""
|
||||
|
||||
notes_dir: Path = NON_EXISTENT_PATH
|
||||
"""The path to the notes directory."""
|
||||
|
||||
notes_ext: str = "md"
|
||||
notes_ext: str = OPTIONS["notes_ext"].default
|
||||
"""The extension of note files."""
|
||||
notes_annot: str = "Note"
|
||||
notes_annot: str = OPTIONS["notes_annot"].default
|
||||
"""The annotation to add to taskwarrior tasks with notes."""
|
||||
notes_editor: str = os.getenv("EDITOR") or os.getenv("VISUAL") or "nano"
|
||||
notes_editor: str = "" # added in post-init
|
||||
"""The editor to open note files with."""
|
||||
notes_quiet: bool = False
|
||||
notes_quiet: bool = OPTIONS["notes_quiet"].default
|
||||
"""If set topen will give no feedback on note editing."""
|
||||
|
||||
def __post_init__(self):
|
||||
if self.task_rc == NON_EXISTENT_PATH:
|
||||
self.task_rc = self._default_task_rc()
|
||||
self.task_rc = _real_path(self.task_rc)
|
||||
self.task_data = _real_path(self.task_data)
|
||||
if self.notes_dir == NON_EXISTENT_PATH:
|
||||
self.notes_dir = self._default_notes_dir()
|
||||
self.notes_dir = _real_path(self.notes_dir)
|
||||
if not self.notes_editor:
|
||||
self.notes_editor = (
|
||||
os.getenv("EDITOR")
|
||||
or os.getenv("VISUAL")
|
||||
or OPTIONS["notes_editor"].default
|
||||
)
|
||||
|
||||
def __or__(self, other: Any, /) -> Self:
|
||||
return self.__class__(**asdict(self) | asdict(other))
|
||||
|
||||
def _default_task_rc(self) -> Path:
|
||||
if Path("~/.taskrc").exists():
|
||||
return Path("~/.taskrc")
|
||||
elif Path("$XDG_CONFIG_HOME/task/taskrc").exists():
|
||||
return Path("$XDG_CONFIG_HOME/task/taskrc")
|
||||
else:
|
||||
return Path("~/.config/task/taskrc")
|
||||
|
||||
def _default_notes_dir(self) -> Path:
|
||||
return self.task_data.joinpath("notes")
|
||||
|
||||
|
|
@ -250,7 +270,7 @@ def build_config() -> TConf:
|
|||
cli = parse_cli()
|
||||
env = parse_env()
|
||||
rc_path = Path(
|
||||
cli.get("task_rc") or env.get("task_rc") or TConf(0).task_rc
|
||||
cli.get("task_rc") or env.get("task_rc") or OPTIONS["task_rc"].default
|
||||
).expanduser()
|
||||
rc = parse_rc(rc_path) if rc_path.exists() else {}
|
||||
|
||||
|
|
@ -288,6 +308,7 @@ you view the task.
|
|||
dest=key,
|
||||
metavar=opt.metavar,
|
||||
help=opt.help_text,
|
||||
type=opt.cast or str,
|
||||
default=None,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
|
@ -326,17 +347,18 @@ def parse_rc(rc_path: Path) -> dict:
|
|||
return out
|
||||
|
||||
|
||||
IS_QUIET = False
|
||||
class _IO:
|
||||
def __init__(self, quiet: bool = False) -> None:
|
||||
self.quiet = quiet
|
||||
|
||||
|
||||
def whisper(text: str) -> None:
|
||||
if not IS_QUIET:
|
||||
def out(self, text: str) -> None:
|
||||
if not self.quiet:
|
||||
print(text)
|
||||
|
||||
|
||||
def _real_path(p: Path | str) -> Path:
|
||||
return Path(os.path.expandvars(p)).expanduser()
|
||||
def err(self, text: str) -> None:
|
||||
sys.stderr.write(text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
exit = main()
|
||||
sys.exit(exit)
|
||||
|
|
|
|||
116
uv.lock
generated
116
uv.lock
generated
|
|
@ -1,7 +1,25 @@
|
|||
version = 1
|
||||
revision = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
|
|
@ -9,37 +27,46 @@ source = { registry = "https://pypi.org/simple" }
|
|||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
|
||||
{ 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 = "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]]
|
||||
|
|
@ -51,25 +78,50 @@ dependencies = [
|
|||
{ name = "markupsafe" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/16/1b542af6f18a27de059f722c487a596681127897b6d31f78e46d6e5bf2fe/pdoc-15.0.1.tar.gz", hash = "sha256:3b08382c9d312243ee6c2a1813d0ff517a6ab84d596fa2c6c6b5255b17c3d666", size = 154174 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/16/1b542af6f18a27de059f722c487a596681127897b6d31f78e46d6e5bf2fe/pdoc-15.0.1.tar.gz", hash = "sha256:3b08382c9d312243ee6c2a1813d0ff517a6ab84d596fa2c6c6b5255b17c3d666", size = 154174, upload-time = "2024-12-12T15:39:18.498Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/4d/60d856a1b12fbf6ac1539efccfa138e57c6b88675c9867d84bbb46455cc1/pdoc-15.0.1-py3-none-any.whl", hash = "sha256:fd437ab8eb55f9b942226af7865a3801e2fb731665199b74fd9a44737dbe20f9", size = 144186 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/4d/60d856a1b12fbf6ac1539efccfa138e57c6b88675c9867d84bbb46455cc1/pdoc-15.0.1-py3-none-any.whl", hash = "sha256:fd437ab8eb55f9b942226af7865a3801e2fb731665199b74fd9a44737dbe20f9", size = 144186, upload-time = "2024-12-12T15:39:17.107Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
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 = "pygments"
|
||||
version = "2.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tasklib"
|
||||
version = "2.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/50/3e876f39e31bad8783fd3fe117577cbf1dde836e161f8446631bde71aeb4/tasklib-2.5.1.tar.gz", hash = "sha256:5ccd731b52636dd10457a8b8d858cb0d026ffaab1e3e751baf791bf803e37d7b", size = 23805 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/50/3e876f39e31bad8783fd3fe117577cbf1dde836e161f8446631bde71aeb4/tasklib-2.5.1.tar.gz", hash = "sha256:5ccd731b52636dd10457a8b8d858cb0d026ffaab1e3e751baf791bf803e37d7b", size = 23805, upload-time = "2022-11-17T02:48:09.446Z" }
|
||||
|
||||
[[package]]
|
||||
name = "topen"
|
||||
|
|
@ -82,10 +134,14 @@ dependencies = [
|
|||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pdoc" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "tasklib", specifier = ">=2.5.1" }]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "pdoc", specifier = ">=15.0.1" }]
|
||||
dev = [
|
||||
{ name = "pdoc", specifier = ">=15.0.1" },
|
||||
{ name = "pytest", specifier = ">=9.0.1" },
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue