Home Assistant Git Exporter
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
"""https://github.com/dummylabs/thewatchman§"""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import voluptuous as vol
|
||||
from homeassistant.loader import async_get_integration
|
||||
from anyio import Path
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.components import persistent_notification
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.config_entries import ConfigEntry, SOURCE_IMPORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
@@ -21,7 +20,6 @@ from homeassistant.const import (
|
||||
EVENT_SERVICE_REMOVED,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_CALL_SERVICE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
|
||||
from .coordinator import WatchmanCoordinator
|
||||
@@ -33,7 +31,7 @@ from .utils import (
|
||||
table_renderer,
|
||||
text_renderer,
|
||||
get_config,
|
||||
get_report_path,
|
||||
async_get_report_path,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
@@ -97,7 +95,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: dict):
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up is called when Home Assistant is loading our component."""
|
||||
if config.get(DOMAIN) is None:
|
||||
# We get here if the integration is set up using config flow
|
||||
@@ -113,7 +111,7 @@ async def async_setup(hass: HomeAssistantType, config: dict):
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up this integration using UI"""
|
||||
_LOGGER.debug(entry.options)
|
||||
_LOGGER.debug("Home assistant path: %s", hass.config.path(""))
|
||||
@@ -134,19 +132,12 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
await add_event_handlers(hass)
|
||||
if hass.is_running:
|
||||
# integration reloaded or options changed via UI
|
||||
parse_config(hass, reason="changes in watchman configuration")
|
||||
await parse_config(hass, reason="changes in watchman configuration")
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
else:
|
||||
# first run, home assistant is loading
|
||||
# parse_config will be scheduled once HA is fully loaded
|
||||
_LOGGER.info("Watchman started [%s]", VERSION)
|
||||
|
||||
|
||||
# resources = hass.data["lovelace"]["resources"]
|
||||
# await resources.async_get_info()
|
||||
# for itm in resources.async_items():
|
||||
# _LOGGER.debug(itm)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -155,9 +146,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, config_entry
|
||||
): # pylint: disable=unused-argument
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry): # pylint: disable=unused-argument
|
||||
"""Handle integration unload"""
|
||||
for cancel_handle in hass.data[DOMAIN].get("cancel_handlers", []):
|
||||
if cancel_handle:
|
||||
@@ -189,7 +178,7 @@ async def add_services(hass: HomeAssistant):
|
||||
async def async_handle_report(call):
|
||||
"""Handle the service call"""
|
||||
config = hass.data.get(DOMAIN_DATA, {})
|
||||
path = get_report_path(hass, config.get(CONF_REPORT_PATH, None))
|
||||
path = await async_get_report_path(hass, config.get(CONF_REPORT_PATH, None))
|
||||
send_notification = call.data.get(CONF_SEND_NOTIFICATION, False)
|
||||
create_file = call.data.get(CONF_CREATE_FILE, True)
|
||||
test_mode = call.data.get(CONF_TEST_MODE, False)
|
||||
@@ -211,7 +200,7 @@ async def add_services(hass: HomeAssistant):
|
||||
await async_notification(hass, "Watchman error", message, error=True)
|
||||
|
||||
if call.data.get(CONF_PARSE_CONFIG, False):
|
||||
parse_config(hass, reason="service call")
|
||||
await parse_config(hass, reason="service call")
|
||||
|
||||
if send_notification:
|
||||
chunk_size = call.data.get(CONF_CHUNK_SIZE, config.get(CONF_CHUNK_SIZE))
|
||||
@@ -227,7 +216,7 @@ async def add_services(hass: HomeAssistant):
|
||||
error=True,
|
||||
)
|
||||
|
||||
if onboarding(hass, service, path):
|
||||
if await async_onboarding(hass, service, path):
|
||||
await async_notification(
|
||||
hass,
|
||||
"🖖 Achievement unlocked: first report!",
|
||||
@@ -271,7 +260,7 @@ async def add_event_handlers(hass: HomeAssistant):
|
||||
await coordinator.async_refresh()
|
||||
|
||||
async def async_on_home_assistant_started(event): # pylint: disable=unused-argument
|
||||
parse_config(hass, reason="HA restart")
|
||||
await parse_config(hass, reason="HA restart")
|
||||
startup_delay = get_config(hass, CONF_STARTUP_DELAY, 0)
|
||||
await async_schedule_refresh_states(hass, startup_delay)
|
||||
|
||||
@@ -284,12 +273,12 @@ async def add_event_handlers(hass: HomeAssistant):
|
||||
"reload_core_config",
|
||||
"reload",
|
||||
]:
|
||||
parse_config(hass, reason="configuration changes")
|
||||
await parse_config(hass, reason="configuration changes")
|
||||
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||
await coordinator.async_refresh()
|
||||
|
||||
elif typ in [EVENT_AUTOMATION_RELOADED, EVENT_SCENE_RELOADED]:
|
||||
parse_config(hass, reason="configuration changes")
|
||||
await parse_config(hass, reason="configuration changes")
|
||||
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||
await coordinator.async_refresh()
|
||||
|
||||
@@ -341,14 +330,14 @@ async def add_event_handlers(hass: HomeAssistant):
|
||||
hass.data[DOMAIN]["cancel_handlers"] = hdlr
|
||||
|
||||
|
||||
def parse_config(hass: HomeAssistant, reason=None):
|
||||
async def parse_config(hass: HomeAssistant, reason=None):
|
||||
"""parse home assistant configuration files"""
|
||||
assert hass.data.get(DOMAIN_DATA)
|
||||
start_time = time.time()
|
||||
included_folders = get_included_folders(hass)
|
||||
ignored_files = hass.data[DOMAIN_DATA].get(CONF_IGNORED_FILES, None)
|
||||
|
||||
entity_list, service_list, files_parsed, files_ignored = parse(
|
||||
entity_list, service_list, files_parsed, files_ignored = await parse(
|
||||
hass, included_folders, ignored_files, hass.config.config_dir
|
||||
)
|
||||
hass.data[DOMAIN]["entity_list"] = entity_list
|
||||
@@ -376,10 +365,10 @@ def get_included_folders(hass):
|
||||
config_folders = [hass.config.config_dir]
|
||||
|
||||
for fld in config_folders:
|
||||
folders.append(os.path.join(fld, "**/*.yaml"))
|
||||
folders.append((fld, "**/*.yaml"))
|
||||
|
||||
if DOMAIN_DATA in hass.data and hass.data[DOMAIN_DATA].get(CONF_CHECK_LOVELACE):
|
||||
folders.append(os.path.join(hass.config.config_dir, ".storage/**/lovelace*"))
|
||||
folders.append((hass.config.config_dir, ".storage/**/lovelace*"))
|
||||
|
||||
return folders
|
||||
|
||||
@@ -388,11 +377,16 @@ async def async_report_to_file(hass, path, test_mode):
|
||||
"""save report to a file"""
|
||||
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||
await coordinator.async_refresh()
|
||||
report_chunks = report(hass, table_renderer, chunk_size=0, test_mode=test_mode)
|
||||
# OSError exception is handled in async_handle_report
|
||||
with open(path, "w", encoding="utf-8") as report_file:
|
||||
for chunk in report_chunks:
|
||||
report_file.write(chunk)
|
||||
report_chunks = await report(
|
||||
hass, table_renderer, chunk_size=0, test_mode=test_mode
|
||||
)
|
||||
|
||||
def write(path):
|
||||
with open(path, "w", encoding="utf-8") as report_file:
|
||||
for chunk in report_chunks:
|
||||
report_file.write(chunk)
|
||||
|
||||
await hass.async_add_executor_job(write, path)
|
||||
|
||||
|
||||
async def async_report_to_notification(hass, service_str, service_data, chunk_size):
|
||||
@@ -423,7 +417,7 @@ async def async_report_to_notification(hass, service_str, service_data, chunk_si
|
||||
|
||||
coordinator = hass.data[DOMAIN]["coordinator"]
|
||||
await coordinator.async_refresh()
|
||||
report_chunks = report(hass, text_renderer, chunk_size)
|
||||
report_chunks = await report(hass, text_renderer, chunk_size)
|
||||
for chunk in report_chunks:
|
||||
data["message"] = chunk
|
||||
# blocking=True ensures execution order
|
||||
@@ -446,7 +440,7 @@ async def async_notification(hass, title, message, error=False, n_id="watchman")
|
||||
raise HomeAssistantError(message.replace("`", ""))
|
||||
|
||||
|
||||
def onboarding(hass, service, path):
|
||||
async def async_onboarding(hass, service, path):
|
||||
"""check if the user runs report for the first time"""
|
||||
service = service or get_config(hass, CONF_SERVICE_NAME, None)
|
||||
return not (service or os.path.exists(path))
|
||||
return not (service or await Path(path).exists())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"ConfigFlow definition for watchman"
|
||||
|
||||
from typing import Dict
|
||||
import json
|
||||
from json.decoder import JSONDecodeError
|
||||
@@ -7,7 +8,7 @@ from homeassistant.config_entries import ConfigFlow, OptionsFlow, ConfigEntry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import config_validation as cv, selector
|
||||
import voluptuous as vol
|
||||
from .utils import is_service, get_columns_width, get_report_path
|
||||
from .utils import is_service, get_columns_width, async_get_report_path
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
@@ -91,7 +92,7 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
self.config_entry = config_entry
|
||||
|
||||
def default(self, key, uinput=None):
|
||||
async def async_default(self, key, uinput=None):
|
||||
"""provide default value for an OptionsFlow field"""
|
||||
if uinput and key in uinput:
|
||||
# supply last entered value to display an error during form validation
|
||||
@@ -105,7 +106,7 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
if DEFAULT_DATA[key]:
|
||||
result = DEFAULT_DATA[key]
|
||||
elif key == CONF_REPORT_PATH:
|
||||
result = get_report_path(self.hass, None)
|
||||
result = await async_get_report_path(self.hass, None)
|
||||
|
||||
if isinstance(result, list):
|
||||
return ", ".join([str(i) for i in result])
|
||||
@@ -130,9 +131,7 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
errors[key] = f"invalid_{key}"
|
||||
return val, errors
|
||||
|
||||
async def _show_options_form(
|
||||
self, uinput=None, errors=None, placehoders=None
|
||||
): # pylint: disable=unused-argument
|
||||
async def _show_options_form(self, uinput=None, errors=None, placehoders=None): # pylint: disable=unused-argument
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
@@ -140,19 +139,23 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
vol.Optional(
|
||||
CONF_SERVICE_NAME,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_SERVICE_NAME, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_SERVICE_NAME, uinput
|
||||
)
|
||||
},
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_SERVICE_DATA2,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_SERVICE_DATA2, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_SERVICE_DATA2, uinput
|
||||
)
|
||||
},
|
||||
): selector.TemplateSelector(),
|
||||
vol.Optional(
|
||||
CONF_INCLUDED_FOLDERS,
|
||||
description={
|
||||
"suggested_value": self.default(
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_INCLUDED_FOLDERS, uinput
|
||||
)
|
||||
},
|
||||
@@ -162,19 +165,25 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
vol.Optional(
|
||||
CONF_HEADER,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_HEADER, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_HEADER, uinput
|
||||
)
|
||||
},
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_REPORT_PATH,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_REPORT_PATH, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_REPORT_PATH, uinput
|
||||
)
|
||||
},
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_IGNORED_ITEMS,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_IGNORED_ITEMS, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_IGNORED_ITEMS, uinput
|
||||
)
|
||||
},
|
||||
): selector.TextSelector(
|
||||
selector.TextSelectorConfig(multiline=True)
|
||||
@@ -182,7 +191,9 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
vol.Optional(
|
||||
CONF_IGNORED_STATES,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_IGNORED_STATES, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_IGNORED_STATES, uinput
|
||||
)
|
||||
},
|
||||
): selector.TextSelector(
|
||||
selector.TextSelectorConfig(multiline=True)
|
||||
@@ -190,13 +201,17 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
vol.Optional(
|
||||
CONF_CHUNK_SIZE,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_CHUNK_SIZE, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_CHUNK_SIZE, uinput
|
||||
)
|
||||
},
|
||||
): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_IGNORED_FILES,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_IGNORED_FILES, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_IGNORED_FILES, uinput
|
||||
)
|
||||
},
|
||||
): selector.TextSelector(
|
||||
selector.TextSelectorConfig(multiline=True)
|
||||
@@ -204,25 +219,33 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
vol.Optional(
|
||||
CONF_COLUMNS_WIDTH,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_COLUMNS_WIDTH, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_COLUMNS_WIDTH, uinput
|
||||
)
|
||||
},
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_STARTUP_DELAY,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_STARTUP_DELAY, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_STARTUP_DELAY, uinput
|
||||
)
|
||||
},
|
||||
): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_FRIENDLY_NAMES,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_FRIENDLY_NAMES, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_FRIENDLY_NAMES, uinput
|
||||
)
|
||||
},
|
||||
): cv.boolean,
|
||||
vol.Optional(
|
||||
CONF_CHECK_LOVELACE,
|
||||
description={
|
||||
"suggested_value": self.default(CONF_CHECK_LOVELACE, uinput)
|
||||
"suggested_value": await self.async_default(
|
||||
CONF_CHECK_LOVELACE, uinput
|
||||
)
|
||||
},
|
||||
): cv.boolean,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"definition of constants"
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "watchman"
|
||||
DOMAIN_DATA = "watchman_data"
|
||||
VERSION = "0.6.1"
|
||||
VERSION = "0.6.3"
|
||||
|
||||
DEFAULT_REPORT_FILENAME = "watchman_report.txt"
|
||||
DEFAULT_HEADER = "-== WATCHMAN REPORT ==- "
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Represents Watchman service in the device registry of Home Assistant"""
|
||||
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"domain": "watchman",
|
||||
"name": "Watchman",
|
||||
"documentation": "https://github.com/dummylabs/thewatchman",
|
||||
"issue_tracker": "https://github.com/dummylabs/thewatchman/issues",
|
||||
"iot_class": "local_push",
|
||||
"version": "0.5.1",
|
||||
"requirements": [
|
||||
"prettytable==3.0.0"
|
||||
],
|
||||
"codeowners": [
|
||||
"@dummylabs"
|
||||
],
|
||||
"config_flow": true
|
||||
"config_flow": true,
|
||||
"documentation": "https://github.com/dummylabs/thewatchman",
|
||||
"iot_class": "local_push",
|
||||
"issue_tracker": "https://github.com/dummylabs/thewatchman/issues",
|
||||
"requirements": [
|
||||
"prettytable==3.10.0"
|
||||
],
|
||||
"version": "0.6.3"
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
"""Watchman sensors definition"""
|
||||
|
||||
import logging
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.components.sensor.const import (
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
|
||||
@@ -2,45 +2,33 @@ report:
|
||||
description: Run watchman report
|
||||
fields:
|
||||
create_file:
|
||||
description: Whether report file should be created (optional, true by default)
|
||||
example: true
|
||||
name: Create file report
|
||||
default: true
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
send_notification:
|
||||
description: Whether report should be sent via notification service (optional, false by default)
|
||||
example: true
|
||||
name: Send notification
|
||||
default: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
service:
|
||||
description: Notification service to send report via (optional). Overrides "service" setting from watchman configuration
|
||||
example: "notify.telegram"
|
||||
name: Notification service
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
data:
|
||||
description: Additional data in form of key:value pairs for notification service (optional)
|
||||
example: "parse_mode: html"
|
||||
name: Notification service data parameters
|
||||
|
||||
parse_config:
|
||||
description: Parse configuration files before report is created. Usually this is done by watchman automatically, so this flag is not required. (optional, false by default)
|
||||
example: true
|
||||
name: Parse configuration
|
||||
default: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
chunk_size:
|
||||
description: Maximum message size in bytes. If report size exceeds chunk_size, the report will be sent in several subsequent notifications. (optional, default is 3500 or whatever specified in integration settings)
|
||||
example: true
|
||||
name: Chunk size
|
||||
default: false
|
||||
required: false
|
||||
selector:
|
||||
|
||||
@@ -41,5 +41,37 @@
|
||||
"description": "[Help on settings](https://github.com/dummylabs/thewatchman#configuration)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"report": {
|
||||
"name": "Report",
|
||||
"description": "Run the Watchman report",
|
||||
"fields": {
|
||||
"create_file": {
|
||||
"name": "Create file report",
|
||||
"description": "Whether report file should be created (optional, true by default)"
|
||||
},
|
||||
"send_notification": {
|
||||
"name": "Send notification",
|
||||
"description": "Whether report should be sent via notification service (optional, false by default)"
|
||||
},
|
||||
"service": {
|
||||
"name": "Notification service",
|
||||
"description": "Notification service to send report via (optional). Overrides 'service' setting from watchman configuration"
|
||||
},
|
||||
"data": {
|
||||
"name": "Notification service data parameters",
|
||||
"description": "Additional data in form of key:value pairs for notification service (optional)"
|
||||
},
|
||||
"parse_config": {
|
||||
"name": "Force configuration parsing",
|
||||
"description": "Parse configuration files before report is created. Usually this is done by watchman automatically, so this flag is not required. (optional, false by default)"
|
||||
},
|
||||
"chunk_size": {
|
||||
"name": "Report chunk size",
|
||||
"description": "Maximum message size in bytes. If report size exceeds chunk_size, the report will be sent in several subsequent notifications. (optional, default is 3500 or whatever specified in integration settings)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Miscellaneous support functions for watchman"""
|
||||
import glob
|
||||
|
||||
import anyio
|
||||
import re
|
||||
import fnmatch
|
||||
import time
|
||||
@@ -58,12 +59,12 @@ def get_config(hass: HomeAssistant, key, default):
|
||||
return hass.data[DOMAIN_DATA].get(key, default)
|
||||
|
||||
|
||||
def get_report_path(hass, path):
|
||||
async def async_get_report_path(hass, path):
|
||||
"""if path not specified, create report in config directory with default filename"""
|
||||
if not path:
|
||||
path = hass.config.path(DEFAULT_REPORT_FILENAME)
|
||||
folder, _ = os.path.split(path)
|
||||
if not os.path.exists(folder):
|
||||
if not await anyio.Path(folder).exists():
|
||||
raise HomeAssistantError(f"Incorrect report_path: {path}.")
|
||||
return path
|
||||
|
||||
@@ -147,16 +148,25 @@ def text_renderer(hass, entry_type):
|
||||
return f"Text render error: unknown entry type: {entry_type}"
|
||||
|
||||
|
||||
def get_next_file(folder_list, ignored_files):
|
||||
async def async_get_next_file(folder_tuples, ignored_files):
|
||||
"""Returns next file for scan"""
|
||||
if not ignored_files:
|
||||
ignored_files = ""
|
||||
else:
|
||||
ignored_files = "|".join([f"({fnmatch.translate(f)})" for f in ignored_files])
|
||||
ignored_files_re = re.compile(ignored_files)
|
||||
for folder in folder_list:
|
||||
for filename in glob.iglob(folder, recursive=True):
|
||||
yield (filename, (ignored_files and ignored_files_re.match(filename)))
|
||||
for folder_name, glob_pattern in folder_tuples:
|
||||
_LOGGER.debug(
|
||||
"Scan folder %s with pattern %s for configuration files",
|
||||
folder_name,
|
||||
glob_pattern,
|
||||
)
|
||||
async for filename in anyio.Path(folder_name).glob(glob_pattern):
|
||||
_LOGGER.debug("Found file %s.", filename)
|
||||
yield (
|
||||
str(filename),
|
||||
(ignored_files and ignored_files_re.match(str(filename))),
|
||||
)
|
||||
|
||||
|
||||
def add_entry(_list, entry, yaml_file, lineno):
|
||||
@@ -231,7 +241,7 @@ def check_entitites(hass):
|
||||
return entities_missing
|
||||
|
||||
|
||||
def parse(hass, folders, ignored_files, root=None):
|
||||
async def parse(hass, folders, ignored_files, root=None):
|
||||
"""Parse a yaml or json file for entities/services"""
|
||||
files_parsed = 0
|
||||
entity_pattern = re.compile(
|
||||
@@ -247,7 +257,7 @@ def parse(hass, folders, ignored_files, root=None):
|
||||
service_list = {}
|
||||
effectively_ignored = []
|
||||
_LOGGER.debug("::parse started")
|
||||
for yaml_file, ignored in get_next_file(folders, ignored_files):
|
||||
async for yaml_file, ignored in async_get_next_file(folders, ignored_files):
|
||||
short_path = os.path.relpath(yaml_file, root)
|
||||
if ignored:
|
||||
effectively_ignored.append(short_path)
|
||||
@@ -255,19 +265,24 @@ def parse(hass, folders, ignored_files, root=None):
|
||||
continue
|
||||
|
||||
try:
|
||||
for i, line in enumerate(open(yaml_file, encoding="utf-8")):
|
||||
line = re.sub(comment_pattern, "", line)
|
||||
for match in re.finditer(entity_pattern, line):
|
||||
typ, val = match.group(1), match.group(2)
|
||||
if (
|
||||
typ != "service:"
|
||||
and "*" not in val
|
||||
and not val.endswith(".yaml")
|
||||
):
|
||||
add_entry(entity_list, val, short_path, i + 1)
|
||||
for match in re.finditer(service_pattern, line):
|
||||
val = match.group(1)
|
||||
add_entry(service_list, val, short_path, i + 1)
|
||||
lineno = 1
|
||||
async with await anyio.open_file(
|
||||
yaml_file, mode="r", encoding="utf-8"
|
||||
) as f:
|
||||
async for line in f:
|
||||
line = re.sub(comment_pattern, "", line)
|
||||
for match in re.finditer(entity_pattern, line):
|
||||
typ, val = match.group(1), match.group(2)
|
||||
if (
|
||||
typ != "service:"
|
||||
and "*" not in val
|
||||
and not val.endswith(".yaml")
|
||||
):
|
||||
add_entry(entity_list, val, short_path, lineno)
|
||||
for match in re.finditer(service_pattern, line):
|
||||
val = match.group(1)
|
||||
add_entry(service_list, val, short_path, lineno)
|
||||
lineno += 1
|
||||
files_parsed += 1
|
||||
_LOGGER.debug("%s parsed", yaml_file)
|
||||
except OSError as exception:
|
||||
@@ -312,9 +327,9 @@ def fill(data, width, extra=None):
|
||||
)
|
||||
|
||||
|
||||
def report(hass, render, chunk_size, test_mode=False):
|
||||
async def report(hass, render, chunk_size, test_mode=False):
|
||||
"""generates watchman report either as a table or as a list"""
|
||||
if not DOMAIN in hass.data:
|
||||
if DOMAIN not in hass.data:
|
||||
raise HomeAssistantError("No data for report, refresh required.")
|
||||
|
||||
start_time = time.time()
|
||||
@@ -354,7 +369,11 @@ def report(hass, render, chunk_size, test_mode=False):
|
||||
rep += "your config are available!\n"
|
||||
else:
|
||||
rep += "\n-== No entities found in configuration files!\n"
|
||||
timezone = pytz.timezone(hass.config.time_zone)
|
||||
|
||||
def get_timezone(hass):
|
||||
return pytz.timezone(hass.config.time_zone)
|
||||
|
||||
timezone = await hass.async_add_executor_job(get_timezone, hass)
|
||||
|
||||
if not test_mode:
|
||||
report_datetime = datetime.now(timezone).strftime("%d %b %Y %H:%M:%S")
|
||||
|
||||
Reference in New Issue
Block a user