Skip to content

Commit

Permalink
Add support for custom_templates (#3101)
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeeus authored Apr 5, 2023
1 parent 643361b commit 3908940
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/generate-hacs-data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
- integration
- plugin
- python_script
- template
- theme
steps:
- name: Checkout the repository
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ settings.json
custom_components/hacs/hacs_frontend
custom_components/hacs/hacs_frontend_experimental

# Custom templates
custom_templates


# Home Assistant configuration
.cloud
Expand Down
1 change: 1 addition & 0 deletions action/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"netdaemon",
"plugin",
"python_script",
"template",
"theme",
]

Expand Down
3 changes: 3 additions & 0 deletions custom_components/hacs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,9 @@ def set_active_categories(self) -> None:
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
self.enable_hacs_category(HacsCategory(category))

if self.configuration.experimental and self.core.ha_version >= "2021.4.0b0":
self.enable_hacs_category(HacsCategory.TEMPLATE)

if HacsCategory.PYTHON_SCRIPT in self.hass.config.components:
self.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)

Expand Down
1 change: 1 addition & 0 deletions custom_components/hacs/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class HacsCategory(StrEnum):
PLUGIN = "plugin" # Kept for legacy purposes
NETDAEMON = "netdaemon"
PYTHON_SCRIPT = "python_script"
TEMPLATE = "template"
THEME = "theme"
REMOVED = "removed"

Expand Down
2 changes: 2 additions & 0 deletions custom_components/hacs/repositories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .netdaemon import HacsNetdaemonRepository
from .plugin import HacsPluginRepository
from .python_script import HacsPythonScriptRepository
from .template import HacsTemplateRepository
from .theme import HacsThemeRepository

RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
Expand All @@ -17,4 +18,5 @@
HacsCategory.APPDAEMON: HacsAppdaemonRepository,
HacsCategory.NETDAEMON: HacsNetdaemonRepository,
HacsCategory.PLUGIN: HacsPluginRepository,
HacsCategory.TEMPLATE: HacsTemplateRepository,
}
8 changes: 7 additions & 1 deletion custom_components/hacs/repositories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
"sensor",
"smart-home",
"smarthome",
"template",
"templates",
"theme",
"themes",
)
Expand Down Expand Up @@ -772,6 +774,8 @@ async def uninstall(self) -> None:
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
pass
elif self.data.category == "template":
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})

await async_remove_store(self.hacs.hass, f"hacs/{self.data.id}.hacs")

Expand All @@ -796,6 +800,8 @@ async def remove_local_directory(self) -> None:
try:
if self.data.category == "python_script":
local_path = f"{self.content.path.local}/{self.data.name}.py"
elif self.data.category == "template":
local_path = f"{self.content.path.local}/{self.data.file_name}"
elif self.data.category == "theme":
path = (
f"{self.hacs.core.config_path}/"
Expand Down Expand Up @@ -823,7 +829,7 @@ async def remove_local_directory(self) -> None:
return False
self.logger.debug("%s Removing %s", self.string, local_path)

if self.data.category in ["python_script"]:
if self.data.category in ["python_script", "template"]:
os.remove(local_path)
else:
shutil.rmtree(local_path)
Expand Down
91 changes: 91 additions & 0 deletions custom_components/hacs/repositories/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Class for themes in HACS."""
from __future__ import annotations

from typing import TYPE_CHECKING

from ..enums import HacsCategory, HacsDispatchEvent
from ..exceptions import HacsException
from ..utils.decorator import concurrent
from .base import HacsRepository

if TYPE_CHECKING:
from ..base import HacsBase


class HacsTemplateRepository(HacsRepository):
"""Custom templates in HACS."""

def __init__(self, hacs: HacsBase, full_name: str):
"""Initialize."""
super().__init__(hacs=hacs)
self.data.full_name = full_name
self.data.full_name_lower = full_name.lower()
self.data.category = HacsCategory.TEMPLATE
self.content.path.remote = ""
self.content.path.local = self.localpath
self.content.single = True

@property
def localpath(self):
"""Return localpath."""
return f"{self.hacs.core.config_path}/custom_templates"

async def async_post_installation(self):
"""Run post installation steps."""
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})

async def validate_repository(self):
"""Validate."""
# Run common validation steps.
await self.common_validate()

# Custom step 1: Validate content.
self.data.file_name = self.repository_manifest.filename

if (
not self.data.file_name
or "/" in self.data.file_name
or not self.data.file_name.endswith(".jinja")
or self.data.file_name not in self.treefiles
):
raise HacsException(
f"{self.string} Repository structure for {self.ref.replace('tags/','')} is not compliant"
)

# Handle potential errors
if self.validate.errors:
for error in self.validate.errors:
if not self.hacs.status.startup:
self.logger.error("%s %s", self.string, error)
return self.validate.success

async def async_post_registration(self):
"""Registration."""
# Set filenames
self.data.file_name = self.repository_manifest.filename
self.content.path.local = self.localpath

if self.hacs.system.action:
await self.hacs.validation.async_run_repository_checks(self)

@concurrent(concurrenttasks=10, backoff_time=5)
async def update_repository(self, ignore_issues=False, force=False):
"""Update."""
if not await self.common_update(ignore_issues, force) and not force:
return

# Update filenames
self.data.file_name = self.repository_manifest.filename
self.content.path.local = self.localpath

# Signal entities to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
{
"id": 1337,
"action": "update",
"repository": self.data.full_name,
"repository_id": self.data.id,
},
)
1 change: 1 addition & 0 deletions custom_components/hacs/utils/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ def is_safe(hacs: HacsBase, path: str | Path) -> bool:
Path(f"{hacs.core.config_path}/{hacs.configuration.python_script_path}").as_posix(),
Path(f"{hacs.core.config_path}/{hacs.configuration.theme_path}").as_posix(),
Path(f"{hacs.core.config_path}/custom_components/").as_posix(),
Path(f"{hacs.core.config_path}/custom_templates/").as_posix(),
)
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
HacsNetdaemonRepository,
HacsPluginRepository,
HacsPythonScriptRepository,
HacsTemplateRepository,
HacsThemeRepository,
)
from custom_components.hacs.utils.configuration_schema import TOKEN as CONF_TOKEN
Expand Down Expand Up @@ -188,6 +189,13 @@ def repository_python_script(hacs):
yield dummy_repository_base(hacs, repository_obj)


@pytest.fixture
def repository_template(hacs):
"""Fixtrue for HACS template repository object"""
repository_obj = HacsTemplateRepository(hacs, "test/test")
yield dummy_repository_base(hacs, repository_obj)


@pytest.fixture
def repository_appdaemon(hacs):
"""Fixtrue for HACS appdaemon repository object"""
Expand Down

0 comments on commit 3908940

Please sign in to comment.