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.