Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: refactor theme-handling internals #393

Merged
merged 1 commit into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- DEP: drop `[isolated]` extra (external tooling should handle dependency pinning)
- BLD: drop package level `__version__` attribute
- BLD: migrate build backend from `setuptools` to `hatchling`
- RFC: refactor theme-handling internals

## [5.2.1] - 2024-09-20

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ file = "README.md"
content-type = "text/markdown"

[project.scripts]
idfx = "idefix_cli.__main__:main"
baballe = "idefix_cli.__main__:alt_main"
idfx = "idefix_cli.__main__:idfx_entry_point"
baballe = "idefix_cli.__main__:baballe_entry_point"

[project.urls]
Homepage = "https://github.com/neutrinoceros/idefix_cli"
Expand Down
45 changes: 17 additions & 28 deletions src/idefix_cli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import inspect
import os
import sys
import unicodedata
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
from importlib.metadata import version
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
from types import FunctionType, ModuleType
from typing import Any, Final
from typing import Any, Final, Literal

from idefix_cli._theme import set_theme
from idefix_cli._theme import theme_ctx
from idefix_cli.lib import get_config_file, get_option, print_error, print_warning

CommandMap = dict[str, tuple[FunctionType, bool]]
Expand Down Expand Up @@ -113,14 +112,10 @@ def _setup_commands(parser: ArgumentParser) -> CommandMap:
return cmddict


def main(
argv: list[str] | None = None,
parser: ArgumentParser | None = None,
) -> Any:
def cli(caller: Literal["idfx", "baballe"], argv: list[str] | None = None) -> Any:
# the return value is deleguated to sub commands so its type is arbitrary
# In practice it should be either 'int' or 'typing.NoReturn'
if parser is None:
parser = ArgumentParser(prog="idfx", allow_abbrev=False)
parser = ArgumentParser(prog=caller, allow_abbrev=False)
parser.add_argument(
"-v", "--version", action="version", version=version("idefix-cli")
)
Expand All @@ -145,26 +140,20 @@ def main(
return cmd(*unknown_args, **vars(known_args))


def alt_main(argv: list[str] | None = None) -> Any:
print(
unicodedata.lookup("BASEBALL")
+ unicodedata.lookup("BLACK RIGHT-POINTING TRIANGLE"),
file=sys.stderr,
)
def main(caller: Literal["idfx", "baballe"], argv: list[str] | None = None) -> Any:
theme_name = {"idfx": "default", "baballe": "baballe"}[caller]

with theme_ctx(theme_name):
return cli(caller, argv)


def idfx_entry_point(argv: list[str] | None = None) -> Any:
return main(caller="idfx", argv=argv)

set_theme("baballe")
try:
retv = main(argv, parser=ArgumentParser(prog="baballe", allow_abbrev=False))
finally:
set_theme("default")
print(
unicodedata.lookup("BLACK LEFT-POINTING TRIANGLE")
+ unicodedata.lookup("BASEBALL"),
file=sys.stderr,
)

return retv
def baballe_entry_point(argv: list[str] | None = None) -> Any:
return main(caller="baballe", argv=argv)


if __name__ == "__main__":
sys.exit(main())
if __name__ == "__main__": # pragma: no cover
sys.exit(main(caller="idfx"))
99 changes: 72 additions & 27 deletions src/idefix_cli/_theme.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,95 @@
import sys
import unicodedata
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Literal, TypedDict

if sys.version_info >= (3, 11):
from typing import assert_never
else:
from typing_extensions import assert_never
__all__ = ["get_symbol", "theme_ctx"]


class Theme(TypedDict):
class SymbolSet(TypedDict):
LAUNCH: str
SUCCESS: str
WARNING: str
ERROR: str
HINT: str


Default = Theme(
LAUNCH=unicodedata.lookup("ROCKET"), # 🚀
SUCCESS=unicodedata.lookup("PARTY POPPER"), # 🎉
WARNING=unicodedata.lookup("HEAVY EXCLAMATION MARK SYMBOL"), # ❗
ERROR=unicodedata.lookup("COLLISION SYMBOL"), # 💥
HINT=unicodedata.lookup("LEFT-POINTING MAGNIFYING GLASS"), # 🔍
@dataclass(frozen=True, slots=True, kw_only=True)
class Theme:
name: str
symbols: SymbolSet
enter_msg: str | None
exit_msg: str | None


class ThemeRegistry:
def __init__(self):
self._registry: dict[str, Theme] = {}

def register(
self,
name: str,
*,
symbols: SymbolSet,
enter: str | None = None,
exit: str | None = None,
):
self._registry[name] = Theme(
name=name, symbols=symbols, enter_msg=enter, exit_msg=exit
)

def __getitem__(self, item: str) -> Theme:
return self._registry[item]


themes = ThemeRegistry()
themes.register(
name="default",
symbols={
"LAUNCH": unicodedata.lookup("ROCKET"), # 🚀
"SUCCESS": unicodedata.lookup("PARTY POPPER"), # 🎉
"WARNING": unicodedata.lookup("HEAVY EXCLAMATION MARK SYMBOL"), # ❗
"ERROR": unicodedata.lookup("COLLISION SYMBOL"), # 💥
"HINT": unicodedata.lookup("LEFT-POINTING MAGNIFYING GLASS"), # 🔍
},
)

Baballe = Theme(
LAUNCH=unicodedata.lookup("GUIDE DOG"), # 🦮
SUCCESS=unicodedata.lookup("POODLE"), # 🐩
WARNING=unicodedata.lookup("PAW PRINTS"), # 🐾
ERROR=unicodedata.lookup("HOT DOG"), # 🌭
HINT=unicodedata.lookup("CRYSTAL BALL"), # 🔮
themes.register(
name="baballe",
symbols={
"LAUNCH": unicodedata.lookup("GUIDE DOG"), # 🦮
"SUCCESS": unicodedata.lookup("POODLE"), # 🐩
"WARNING": unicodedata.lookup("PAW PRINTS"), # 🐾
"ERROR": unicodedata.lookup("HOT DOG"), # 🌭
"HINT": unicodedata.lookup("CRYSTAL BALL"), # 🔮
},
enter=unicodedata.lookup("BASEBALL")
+ unicodedata.lookup("BLACK RIGHT-POINTING TRIANGLE"),
exit=unicodedata.lookup("BLACK LEFT-POINTING TRIANGLE")
+ unicodedata.lookup("BASEBALL"),
)


THEME = Default
THEME = themes["default"]


def get_symbol(key: Literal["LAUNCH", "SUCCESS", "WARNING", "ERROR", "HINT"]) -> str:
return THEME.symbols[key]

def set_theme(theme: Literal["default", "baballe"]) -> None:

@contextmanager
def theme_ctx(name: str):
global THEME
if theme == "default":
THEME = Default
elif theme == "baballe":
THEME = Baballe
else:
assert_never(theme)
old_name = THEME.name
THEME = themes[name]

if THEME.enter_msg is not None:
print(THEME.enter_msg, file=sys.stderr)
try:
yield
finally:
if THEME.exit_msg is not None:
print(THEME.exit_msg, file=sys.stderr)

def get_symbol(key: Literal["LAUNCH", "SUCCESS", "WARNING", "ERROR", "HINT"]) -> str:
return THEME[key]
THEME = themes[old_name]
2 changes: 1 addition & 1 deletion tests/test_app_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pytest

from idefix_cli.__main__ import _setup_commands, main
from idefix_cli.__main__ import _setup_commands, idfx_entry_point as main
from idefix_cli.lib import print_error


Expand Down
2 changes: 1 addition & 1 deletion tests/test_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main
from idefix_cli._commands.clean import gpatterns, kokkos_files


Expand Down
2 changes: 1 addition & 1 deletion tests/test_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main

DATADIR = Path(__file__).parent / "data"
BASE_SETUP = DATADIR / "OrszagTang3D"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from packaging.version import Version
from pytest_check import check

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main
from idefix_cli._commands.conf import substitute_cmake_args


Expand Down
2 changes: 1 addition & 1 deletion tests/test_digest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main
from idefix_cli._commands.digest import command as digest

if sys.version_info >= (3, 11):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pytest_check import check

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main


def test_read_not_a_file(tmp_path, capsys):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_run.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main
from idefix_cli._commands.run import get_highest_power_of_two


Expand Down
2 changes: 1 addition & 1 deletion tests/test_ux.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from pytest_check import check

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main

HELP_MESSAGE = (
"usage: idfx [-h] [-v] {clean,clone,conf,digest,read,run,switch,write} ...\n"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest
from pytest_check import check

from idefix_cli.__main__ import main
from idefix_cli.__main__ import idfx_entry_point as main

jq_available = subprocess.run(["which", "jq"]).returncode == 0

Expand Down
Loading