Skip to content
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
2 changes: 1 addition & 1 deletion .github/actions/setup-python/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ runs:
using: "composite"
steps:
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: "pyproject.toml"
- name: Install uv
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ name: checks
on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: ./.github/actions/setup-python
- run: |
uv run ruff check --fix
Expand All @@ -15,13 +21,17 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: ./.github/actions/setup-python
- run: uv run ty check

vulture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: ./.github/actions/setup-python
- run: uv run vulture --min-confidence 100 --exclude ".venv" .
17 changes: 15 additions & 2 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,24 @@ jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: ./.github/actions/setup-python
- run: |
uv run pytest

# Browser/Playwright tests are excluded by default (pytest.ini) and run
# only on pull requests, where we install Chromium for them.
- name: Install Playwright browser
if: github.event_name == 'pull_request'
run: |
uv run playwright install --with-deps chromium
- name: Run browser tests
if: github.event_name == 'pull_request'
run: |
uv run pytest -m browser

- name: Run tests (with coverage)
run: |
uv run pytest \
Expand All @@ -24,7 +37,7 @@ jobs:
| tee pytest-coverage.txt

- name: Pytest coverage comment
uses: MishaKav/pytest-coverage-comment@main
uses: MishaKav/pytest-coverage-comment@dda025d84b1dd831193ed9cd5b1d7e33c79efa9b # v1.1.59
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml
14 changes: 14 additions & 0 deletions docs/schema/cheatsheet.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@
"default": false,
"description": "When true, free-form shortcut text is allowed; requires RenderKeys=false."
},
"theme": {
"type": "string",
"minLength": 1,
"default": "catppuccin",
"description": "Theme name: a built-in (e.g. 'catppuccin') or a user theme in the project's themes/ directory. Defaults to 'catppuccin'."
},
"custom_css": {
"type": "string",
"description": "Filename of a custom CSS file (resolved from the project's styles/ directory), layered after the theme."
},
"custom_css_inline": {
"type": "string",
"description": "Inline custom CSS, emitted last in the cascade so it overrides the theme and the custom CSS file."
},
"layout": {
"type": "object",
"additionalProperties": false,
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dev = [
"prek>=0.3.6",
"pytest>=8.4.2",
"pytest-cov>=7.1.0",
"pytest-playwright>=0.7.1",
"ruff>=0.14.0",
"ty>=0.0.1a22",
"vulture>=2.14",
Expand Down
5 changes: 4 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
addopts =
-v
--tb=short
--strict-markers
--disable-warnings
-m "not browser"
markers =
browser: browser-based Playwright tests

41 changes: 41 additions & 0 deletions src/koalakeys/generate_cheatsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from koalakeys.logger import get_logger
from koalakeys.template_renderer import render_template
from koalakeys.theming import ThemeError, resolve_theme, sanitize_css
from koalakeys.validate_yaml import lint_yaml, validate_yaml

yaml_safe = YAML(typ="safe")
Expand All @@ -25,6 +26,8 @@

OUTPUT_DIR = Path(os.getenv("CHEATSHEET_OUTPUT_DIR") or PROJECT_ROOT / "output")
CHEATSHEETS_DIR = PROJECT_ROOT / "cheatsheets"
THEMES_DIR = PROJECT_ROOT / "themes"
STYLES_DIR = PROJECT_ROOT / "styles"
LAYOUTS_DIR = PACKAGE_DIR / "layouts"

OUTPUT_DIR.mkdir(exist_ok=True, parents=True)
Expand Down Expand Up @@ -135,6 +138,41 @@ def get_layout_info(data):
}


def load_custom_css_file(filename):
if not filename:
return ""
path = STYLES_DIR / filename
try:
return sanitize_css(path.read_text(encoding="utf-8"))
except FileNotFoundError:
logging.error(f"Custom CSS file not found: {path}")
return ""
except OSError as e:
logging.error(f"Error reading custom CSS file '{path}': {e}")
return ""


def apply_styling(data):
"""Resolve the cheatsheet's theme and custom CSS into render-ready context.

Returns True on success; False if the theme cannot be resolved.
"""
try:
theme = resolve_theme(data.get("theme"), themes_dir=THEMES_DIR)
except ThemeError as e:
logging.error(f"Theme error: {e}")
return False

data["theme_token_css"] = theme.render_token_css()
data["theme_font_url"] = theme.font_url
data["theme_default_is_dark"] = theme.default_is_dark
data["theme_both_modes"] = theme.both_modes
data["theme_custom_css"] = theme.custom_css
data["custom_css_file"] = load_custom_css_file(data.get("custom_css"))
data["custom_css_inline"] = sanitize_css(str(data.get("custom_css_inline") or ""))
return True


def generate_html(data, keyboard_layouts, system_mappings):
template_path = "cheatsheets/cheatsheet-template.html"
layout_info = get_layout_info(data)
Expand All @@ -144,6 +182,9 @@ def generate_html(data, keyboard_layouts, system_mappings):
data["render_keys"] = data.get("RenderKeys", True)
data["allow_text"] = data.get("AllowText", False)

if not apply_styling(data):
return None

return render_template(template_path, data)


Expand Down
4 changes: 2 additions & 2 deletions src/koalakeys/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/rtuszik/KoalaKeys/cc427d1ee6fcc8a6f671954848b161c8f1bff599/assets/icons/favicon.ico" />
<link href="https://iosevka-webfonts.github.io/iosevka/Iosevka.css" rel="stylesheet" />
{% block fonts %}<link href="https://iosevka-webfonts.github.io/iosevka/Iosevka.css" rel="stylesheet" />{% endblock %}
<title>{% block title %}{% endblock %}</title>
{% block page_styles %}{% endblock %}
</head>
<body class="dark-mode">
<body class="{% block body_class %}dark-mode{% endblock %}">
{% block content %}{% endblock %}
{% block javascript %}{% endblock %}
</body>
Expand Down
Loading
Loading