diff --git a/custom_components/hacs/__init__.py b/custom_components/hacs/__init__.py
index 33a3cc0ba1b..f3777a449de 100644
--- a/custom_components/hacs/__init__.py
+++ b/custom_components/hacs/__init__.py
@@ -57,6 +57,7 @@
{
vol.Required("token"): cv.string,
vol.Optional("appdaemon", default=False): cv.boolean,
+ vol.Optional("python_script", default=False): cv.boolean,
}
)
},
@@ -72,6 +73,8 @@ async def async_setup(hass, config): # pylint: disable=unused-argument
if config[DOMAIN]["appdaemon"]:
ELEMENT_TYPES.append("appdaemon")
+ if config[DOMAIN]["python_script"]:
+ ELEMENT_TYPES.append("python_script")
# Print DEV warning
if VERSION == "DEV":
diff --git a/custom_components/hacs/blueprints.py b/custom_components/hacs/blueprints.py
index 774750a6d5e..d663d999564 100644
--- a/custom_components/hacs/blueprints.py
+++ b/custom_components/hacs/blueprints.py
@@ -4,4 +4,5 @@
from .hacsrepositoryappdaemon import HacsRepositoryAppDaemon
from .hacsrepositoryintegration import HacsRepositoryIntegration
from .hacsrepositorybaseplugin import HacsRepositoryPlugin
+from .hacsrepositorypythonscript import HacsRepositoryPythonScripts
from .hacsviewbase import HacsViewBase
diff --git a/custom_components/hacs/const.py b/custom_components/hacs/const.py
index 1f953ab2ffc..672364e1a2b 100644
--- a/custom_components/hacs/const.py
+++ b/custom_components/hacs/const.py
@@ -104,4 +104,5 @@
"bramkragten/swipe-card",
"CyrisXD/love-lock-card",
],
+ "python_script": [],
}
diff --git a/custom_components/hacs/frontend/views/overview.py b/custom_components/hacs/frontend/views/overview.py
index 018d251e492..e3d438d51aa 100644
--- a/custom_components/hacs/frontend/views/overview.py
+++ b/custom_components/hacs/frontend/views/overview.py
@@ -116,6 +116,8 @@ async def get(self, request): # pylint: disable=unused-argument
typedisplay = "{}S".format(element_type.upper())
if element_type == "appdaemon":
typedisplay = "APPDAEMON APPS"
+ elif element_type == "python_script":
+ typedisplay = "PYTHON SCRIPTS"
if self.data.get("hacs", {}).get("view") == "Table":
content += """
diff --git a/custom_components/hacs/frontend/views/store.py b/custom_components/hacs/frontend/views/store.py
index bf8b6210837..94595551f28 100644
--- a/custom_components/hacs/frontend/views/store.py
+++ b/custom_components/hacs/frontend/views/store.py
@@ -124,6 +124,8 @@ async def get(self, request): # pylint: disable=unused-argument
typedisplay = "{}S".format(element_type.upper())
if element_type == "appdaemon":
typedisplay = "APPDAEMON APPS"
+ elif element_type == "python_script":
+ typedisplay = "PYTHON SCRIPTS"
if self.data.get("hacs", {}).get("view") == "Table":
content += """
diff --git a/custom_components/hacs/hacsbase.py b/custom_components/hacs/hacsbase.py
index bb95c917788..4fc5e905556 100644
--- a/custom_components/hacs/hacsbase.py
+++ b/custom_components/hacs/hacsbase.py
@@ -100,6 +100,7 @@ async def register_new_repository(self, element_type, repo, repositoryobject=Non
HacsRepositoryAppDaemon,
HacsRepositoryIntegration,
HacsRepositoryPlugin,
+ HacsRepositoryPythonScripts,
)
_LOGGER.info("Starting repository registration for %s", repo)
@@ -117,6 +118,9 @@ async def register_new_repository(self, element_type, repo, repositoryobject=Non
elif element_type == "plugin":
repository = HacsRepositoryPlugin(repo, repositoryobject)
+ elif element_type == "python_script":
+ repository = HacsRepositoryPythonScripts(repo, repositoryobject)
+
else:
return None, False
diff --git a/custom_components/hacs/hacsrepositorybase.py b/custom_components/hacs/hacsrepositorybase.py
index 6f0a5840f1f..5f98e550186 100644
--- a/custom_components/hacs/hacsrepositorybase.py
+++ b/custom_components/hacs/hacsrepositorybase.py
@@ -70,6 +70,8 @@ def custom(self):
return False
elif self.repository_name in DEFAULT_REPOSITORIES["plugin"]:
return False
+ elif self.repository_name in DEFAULT_REPOSITORIES["python_script"]:
+ return False
return True
@property
@@ -89,6 +91,10 @@ def local_path(self):
elif self.repository_type == "plugin":
local_path = "{}/www/community/{}".format(self.config_dir, self.name)
+
+ elif self.repository_type == "python_script":
+ local_path = "{}/python_scripts".format(self.config_dir)
+
return local_path
@property
@@ -176,7 +182,10 @@ async def download_repository_directory_content(
"""Download the content of a directory."""
try:
# Get content
- if self.content_path == "release":
+ if (
+ self.content_path == "release"
+ or self.repository_type == "python_script"
+ ):
contents = self.content_objects
else:
contents = await self.repository.get_contents(
@@ -216,10 +225,13 @@ async def download_repository_directory_content(
"{}/".format(self.content_path), ""
)
- local_directory = "{}/{}".format(self.local_path, _content_path)
- local_directory = local_directory.split(
- "/{}".format(content_object.name)
- )[0]
+ if self.repository_type == "python_script":
+ local_directory = self.local_path
+ else:
+ local_directory = "{}/{}".format(self.local_path, _content_path)
+ local_directory = local_directory.split(
+ "/{}".format(content_object.name)
+ )[0]
# Check local directory
pathlib.Path(local_directory).mkdir(parents=True, exist_ok=True)
@@ -320,20 +332,27 @@ async def check_local_directory(self, path=None):
async def remove_local_directory(self):
"""Check the local directory."""
try:
- if os.path.exists(self.local_path):
- _LOGGER.debug(
- "(%s) - Removing %s", self.repository_name, self.local_path
- )
- shutil.rmtree(self.local_path)
+ if self.repository_type == "python_script":
+ local_path = "{}/{}.py".format(self.local_path, self.name)
+ else:
+ local_path = self.local_path
+
+ if os.path.exists(local_path):
+ _LOGGER.debug("(%s) - Removing %s", self.repository_name, local_path)
- while os.path.exists(self.local_path):
+ if self.repository_type == "python_script":
+ os.remove(local_path)
+ else:
+ shutil.rmtree(local_path)
+
+ while os.path.exists(local_path):
await sleep(1)
except Exception as exception:
_LOGGER.debug(
- "(%s) - Removing directory %s failed with %s",
+ "(%s) - Removing %s failed with %s",
self.repository_name,
- self.local_path,
+ local_path,
exception,
)
return
diff --git a/custom_components/hacs/hacsrepositorypythonscript.py b/custom_components/hacs/hacsrepositorypythonscript.py
new file mode 100644
index 00000000000..ee0bc222b86
--- /dev/null
+++ b/custom_components/hacs/hacsrepositorypythonscript.py
@@ -0,0 +1,53 @@
+"""Blueprint for HacsRepositoryPythonScripts."""
+# pylint: disable=too-many-instance-attributes,invalid-name,broad-except
+import logging
+
+from .blueprints import HacsRepositoryBase
+from .exceptions import HacsRequirement
+
+_LOGGER = logging.getLogger("custom_components.hacs.repository")
+
+
+class HacsRepositoryPythonScripts(HacsRepositoryBase):
+ """
+ Set up a HacsRepositoryPythonScripts object.
+
+ repository_name(str): The full name of a repository
+ (example: awesome-dev/awesome-repo)
+ """
+
+ def __init__(self, repository_name: str, repositoryobject=None):
+ """Initialize a HacsRepositoryAppDaemon object."""
+ super().__init__()
+ self.repository = repositoryobject
+ self.repository_name = repository_name
+ self.repository_type = "python_script"
+ self.manifest_content = None
+
+ async def update(self):
+ """Run update tasks."""
+ if await self.common_update():
+ return
+ await self.set_repository_content()
+
+ async def set_repository_content(self):
+ """Set repository content attributes."""
+ contentfiles = []
+
+ if self.content_path is None:
+ self.content_objects = await self.repository.get_contents(
+ "python_scripts", self.ref
+ )
+
+ self.content_path = self.content_objects[0].path
+
+ self.name = self.content_objects[0].name.replace(".py", "")
+
+ if not isinstance(self.content_objects, list):
+ raise HacsRequirement("Repository structure does not meet the requirements")
+
+ for filename in self.content_objects:
+ contentfiles.append(filename.name)
+
+ if contentfiles:
+ self.content_files = contentfiles
diff --git a/docs/developer/appdaemon.md b/docs/developer/appdaemon.md
index 29b215053cc..2c2e57d45bf 100644
--- a/docs/developer/appdaemon.md
+++ b/docs/developer/appdaemon.md
@@ -8,10 +8,9 @@ For a AppDaemon app repository to be valid these are the requirements:
### Repository structure
-- There is only one integration (one directory under `ROOT_OF_THE_REPO/apps/`) pr. repository (if you have more, only the first one will be managed.)
-- The integration (all the python files for it) are located under `ROOT_OF_THE_REPO/apps/APP_NAME/`
-- There is only one integration (one directory under `ROOT_OF_THE_REPO/apps/`) per repository (if you have more, only the first one will be managed.)
-- The integration and all the python files for it are located under `ROOT_OF_THE_REPO/apps/APP_NAME/`
+- There is only one app (one directory under `ROOT_OF_THE_REPO/apps/`) pr. repository (if you have more, only the first one will be managed.)
+- The app (all the python files for it) are located under `ROOT_OF_THE_REPO/apps/APP_NAME/`
+- The app and all the python files for it are located under `ROOT_OF_THE_REPO/apps/APP_NAME/`
#### OK example:
diff --git a/docs/developer/python_script.md b/docs/developer/python_script.md
new file mode 100644
index 00000000000..61a373179da
--- /dev/null
+++ b/docs/developer/python_script.md
@@ -0,0 +1,39 @@
+# `python_script` developers
+
+A template to use as a reference is [ps-hacs](https://github.com/ludeeus/ps-hacs)
+
+This is for the [`python_script` integration in Home Assistant](https://www.home-assistant.io/components/python_script/)
+
+## Requirements
+
+For a python_script repository to be valid these are the requirements:
+
+### Repository structure
+
+- The python script are located here `ROOT_OF_THE_REPO/python_scripts/SCRIPT_NAME.py`
+- There is only one python file (one directory under `ROOT_OF_THE_REPO/python_scripts/`) per repository (if you have more, only the first one will be managed.)
+
+#### OK example:
+
+```text
+python_scripts/awesome.py
+info.md
+README.md
+```
+#### Not OK example:
+
+```text
+awesome.py
+info.md
+README.md
+```
+
+### GitHub releases (optional)
+
+#### If there are releases
+
+When installing/upgrading it will scan the content in the latest release.
+
+#### If there are no releases
+
+It will scan files in the branch marked as default.
diff --git a/docs/faq.md b/docs/faq.md
index 8a689ae014e..94dd8a17bd9 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -8,9 +8,9 @@ Although "Store" is not "technically" correct, since nothing is sold, it's more
### Highlights of what HACS can do
-- Help you discover new integrations and plugins.
-- Help you install (download) new integrations and plugins.
-- Help you keep track of your integrations and plugins.
+- Help you discover new custom elements.
+- Help you install (download) new custom elements.
+- Help you keep track of your custom elements.
***
@@ -58,6 +58,12 @@ If you make local changes to a plugin in the `.js` file, delete the `.gz` varian
The files are downloaded to `/www/community/*`
+### Files downloaded for `python_script`
+
+The first file under the `python_scripts` directory._
+
+The files are downloaded to `/python_scripts/*`
+
***
## How does it work: Upgrade
diff --git a/docs/index.md b/docs/index.md
index 72fa5a33194..ec3bae3c3b1 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -6,7 +6,7 @@
## What can HACS do?
-This is a manager for your custom integration (components) plugin (lovelace elements) and AppDaemon needs.
+This is a manager for your custom Home Assistant needs.
It can help you download and update elements.
diff --git a/docs/installation/configuration.md b/docs/installation/configuration.md
index e6429a6ae79..6d73c6fda07 100644
--- a/docs/installation/configuration.md
+++ b/docs/installation/configuration.md
@@ -13,6 +13,7 @@ key | optional | default | description
-- | -- | -- | --
`token` | False | | A Github Personal Access Token
`appdaemon` | True | `False` | Enable tracking of AppDaemon apps.
+`python_script` | True | `False` | Enable tracking of python scripts.
***
diff --git a/docs/usage/settings.md b/docs/usage/settings.md
index 88b855b7432..23a93cc57c9 100644
--- a/docs/usage/settings.md
+++ b/docs/usage/settings.md
@@ -40,6 +40,7 @@ Type | Description
`Appdaemon` | Apps for AppDaemon
`Integration` | Integrations for Home Assistant (custom_components)
`Plugin` | Pugins for Lovelace (cards, mods, elements, rows and so on.)
+`Python_Script` | Python scripts for the [`python_script` integration](https://www.home-assistant.io/components/python_script/)
After adding a repository the repository will be scanned, if it can be tracked the element will show up under "STORE", and you will be redirected to that element.