meteocat 3.1.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +45 -45
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  3. package/.github/ISSUE_TEMPLATE/improvement.md +39 -39
  4. package/.github/ISSUE_TEMPLATE/new_function.md +41 -41
  5. package/.github/labels.yml +63 -63
  6. package/.github/workflows/autocloser.yaml +27 -27
  7. package/.github/workflows/close-on-label.yml +48 -48
  8. package/.github/workflows/force-sync-labels.yml +18 -18
  9. package/.github/workflows/hassfest.yaml +13 -13
  10. package/.github/workflows/publish-zip.yml +67 -67
  11. package/.github/workflows/release.yml +41 -41
  12. package/.github/workflows/stale.yml +63 -63
  13. package/.github/workflows/sync-gitlab.yml +107 -107
  14. package/.github/workflows/sync-labels.yml +21 -21
  15. package/.github/workflows/validate.yaml +16 -16
  16. package/.pre-commit-config.yaml +37 -37
  17. package/.releaserc +37 -37
  18. package/AUTHORS.md +13 -13
  19. package/CHANGELOG.md +954 -898
  20. package/README.md +207 -204
  21. package/conftest.py +11 -11
  22. package/custom_components/meteocat/__init__.py +298 -293
  23. package/custom_components/meteocat/condition.py +63 -59
  24. package/custom_components/meteocat/config_flow.py +613 -435
  25. package/custom_components/meteocat/const.py +132 -120
  26. package/custom_components/meteocat/coordinator.py +1040 -205
  27. package/custom_components/meteocat/helpers.py +58 -63
  28. package/custom_components/meteocat/manifest.json +25 -24
  29. package/custom_components/meteocat/options_flow.py +287 -277
  30. package/custom_components/meteocat/sensor.py +366 -4
  31. package/custom_components/meteocat/strings.json +1058 -867
  32. package/custom_components/meteocat/translations/ca.json +1058 -867
  33. package/custom_components/meteocat/translations/en.json +1058 -867
  34. package/custom_components/meteocat/translations/es.json +1058 -867
  35. package/custom_components/meteocat/version.py +1 -1
  36. package/custom_components/meteocat/weather.py +218 -218
  37. package/filetree.py +48 -48
  38. package/filetree.txt +79 -70
  39. package/hacs.json +8 -8
  40. package/images/daily_forecast_2_alerts.png +0 -0
  41. package/images/daily_forecast_no_alerts.png +0 -0
  42. package/images/diagnostic_sensors.png +0 -0
  43. package/images/dynamic_sensors.png +0 -0
  44. package/images/options.png +0 -0
  45. package/images/regenerate_assets.png +0 -0
  46. package/images/setup_options.png +0 -0
  47. package/images/system_options.png +0 -0
  48. package/info.md +11 -11
  49. package/package.json +22 -22
  50. package/poetry.lock +3222 -3222
  51. package/pyproject.toml +68 -68
  52. package/requirements.test.txt +3 -3
  53. package/setup.cfg +64 -64
  54. package/setup.py +10 -10
  55. package/tests/bandit.yaml +17 -17
  56. package/tests/conftest.py +19 -19
  57. package/tests/test_init.py +9 -9
@@ -1 +1 @@
1
- __version__ = "3.1.0"
1
+ __version__ = "4.0.0"
@@ -1,218 +1,218 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from datetime import datetime
5
- from typing import Optional
6
- from homeassistant.helpers.update_coordinator import CoordinatorEntity
7
- from homeassistant.components.weather import (
8
- WeatherEntity,
9
- WeatherEntityFeature,
10
- Forecast,
11
- )
12
- from homeassistant.core import callback
13
- from homeassistant.helpers.entity_platform import AddEntitiesCallback
14
- from homeassistant.helpers.device_registry import DeviceInfo
15
- from homeassistant.const import (
16
- DEGREE,
17
- PERCENTAGE,
18
- UnitOfPrecipitationDepth,
19
- UnitOfPressure,
20
- UnitOfSpeed,
21
- UnitOfTemperature,
22
- )
23
-
24
- from .const import (
25
- DOMAIN,
26
- ATTRIBUTION,
27
- WIND_SPEED_CODE,
28
- WIND_DIRECTION_CODE,
29
- TEMPERATURE_CODE,
30
- HUMIDITY_CODE,
31
- PRESSURE_CODE,
32
- PRECIPITATION_CODE,
33
- WIND_GUST_CODE,
34
- )
35
- from .coordinator import (
36
- HourlyForecastCoordinator,
37
- DailyForecastCoordinator,
38
- MeteocatSensorCoordinator,
39
- MeteocatUviFileCoordinator,
40
- MeteocatConditionCoordinator,
41
- )
42
-
43
- _LOGGER = logging.getLogger(__name__)
44
-
45
- @callback
46
- async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback) -> None:
47
- """Set up Meteocat weather entity from a config entry."""
48
- entry_data = hass.data[DOMAIN][entry.entry_id]
49
-
50
- hourly_forecast_coordinator = entry_data.get("hourly_forecast_coordinator")
51
- daily_forecast_coordinator = entry_data.get("daily_forecast_coordinator")
52
- sensor_coordinator = entry_data.get("sensor_coordinator")
53
- uvi_file_coordinator = entry_data.get("uvi_file_coordinator")
54
- condition_coordinator = entry_data.get("condition_coordinator")
55
-
56
- async_add_entities([
57
- MeteocatWeatherEntity(
58
- hourly_forecast_coordinator,
59
- daily_forecast_coordinator,
60
- sensor_coordinator,
61
- uvi_file_coordinator,
62
- condition_coordinator,
63
- entry_data
64
- )
65
- ])
66
-
67
- class MeteocatWeatherEntity(CoordinatorEntity, WeatherEntity):
68
- """Representation of a Meteocat Weather Entity."""
69
-
70
- _attr_attribution = ATTRIBUTION
71
- _attr_has_entity_name = True
72
- _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
73
- _attr_native_precipitation_probability_unit = PERCENTAGE
74
- _attr_native_pressure_unit = UnitOfPressure.HPA
75
- _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
76
- _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
77
- _attr_native_wind_bearing_unit = DEGREE
78
- _attr_supported_features = (
79
- WeatherEntityFeature.FORECAST_HOURLY | WeatherEntityFeature.FORECAST_DAILY
80
- )
81
-
82
- def __init__(
83
- self,
84
- hourly_forecast_coordinator: HourlyForecastCoordinator,
85
- daily_forecast_coordinator: DailyForecastCoordinator,
86
- sensor_coordinator: MeteocatSensorCoordinator,
87
- uvi_file_coordinator: MeteocatUviFileCoordinator,
88
- condition_coordinator: MeteocatConditionCoordinator,
89
- entry_data: dict,
90
- ) -> None:
91
- """Initialize the weather entity."""
92
- super().__init__(daily_forecast_coordinator)
93
- self._hourly_forecast_coordinator = hourly_forecast_coordinator
94
- self._daily_forecast_coordinator = daily_forecast_coordinator
95
- self._sensor_coordinator = sensor_coordinator
96
- self._uvi_file_coordinator = uvi_file_coordinator
97
- self._condition_coordinator = condition_coordinator
98
- self._town_name = entry_data["town_name"]
99
- self._town_id = entry_data["town_id"]
100
- self._station_id = entry_data["station_id"]
101
-
102
- @property
103
- def name(self) -> str:
104
- """Return the name of the entity."""
105
- return f"Weather {self._town_name}"
106
-
107
- @property
108
- def unique_id(self) -> str:
109
- """Return the unique ID of the entity."""
110
- return f"weather.{DOMAIN}_{self._town_id}"
111
-
112
- @property
113
- def condition(self) -> Optional[str]:
114
- """Return the current weather condition."""
115
- condition_data = self._condition_coordinator.data or {}
116
- return condition_data.get("condition")
117
-
118
- def _get_latest_sensor_value(self, code: str) -> Optional[float]:
119
- """Helper method to retrieve the latest sensor value."""
120
- sensor_code = code
121
- if not sensor_code:
122
- return None
123
-
124
- stations = self._sensor_coordinator.data or []
125
- for station in stations:
126
- variables = station.get("variables", [])
127
- variable_data = next(
128
- (var for var in variables if var.get("codi") == sensor_code),
129
- None,
130
- )
131
- if variable_data:
132
- lectures = variable_data.get("lectures", [])
133
- if lectures:
134
- return lectures[-1].get("valor")
135
- return None
136
-
137
- @property
138
- def native_temperature(self) -> Optional[float]:
139
- return self._get_latest_sensor_value(TEMPERATURE_CODE)
140
-
141
- @property
142
- def humidity(self) -> Optional[float]:
143
- return self._get_latest_sensor_value(HUMIDITY_CODE)
144
-
145
- @property
146
- def native_pressure(self) -> Optional[float]:
147
- return self._get_latest_sensor_value(PRESSURE_CODE)
148
-
149
- @property
150
- def native_wind_speed(self) -> Optional[float]:
151
- return self._get_latest_sensor_value(WIND_SPEED_CODE)
152
-
153
- @property
154
- def native_wind_gust_speed(self) -> Optional[float]:
155
- return self._get_latest_sensor_value(WIND_GUST_CODE)
156
-
157
- @property
158
- def wind_bearing(self) -> Optional[float]:
159
- return self._get_latest_sensor_value(WIND_DIRECTION_CODE)
160
-
161
- @property
162
- def uv_index(self) -> Optional[float]:
163
- """Return the UV index."""
164
- uvi_data = self._uvi_file_coordinator.data or {}
165
- return uvi_data.get("uvi")
166
-
167
- async def async_forecast_daily(self) -> list[Forecast] | None:
168
- """Return the daily forecast."""
169
- await self._daily_forecast_coordinator.async_request_refresh()
170
- daily_forecasts = self._daily_forecast_coordinator.get_all_daily_forecasts()
171
- if not daily_forecasts:
172
- return None
173
-
174
- return [
175
- Forecast(
176
- datetime=forecast["date"],
177
- temperature=forecast["temperature_max"],
178
- templow=forecast["temperature_min"],
179
- precipitation_probability=forecast["precipitation"],
180
- condition=forecast["condition"],
181
- )
182
- for forecast in daily_forecasts
183
- ]
184
-
185
- async def async_forecast_hourly(self) -> list[Forecast] | None:
186
- """Return the hourly forecast."""
187
- await self._hourly_forecast_coordinator.async_request_refresh()
188
- hourly_forecasts = self._hourly_forecast_coordinator.get_all_hourly_forecasts()
189
- if not hourly_forecasts:
190
- return None
191
-
192
- return [
193
- Forecast(
194
- datetime=forecast["datetime"],
195
- temperature=forecast["temperature"],
196
- precipitation=forecast["precipitation"],
197
- condition=forecast["condition"],
198
- wind_speed=forecast["wind_speed"],
199
- wind_bearing=forecast["wind_bearing"],
200
- humidity=forecast["humidity"],
201
- )
202
- for forecast in hourly_forecasts
203
- ]
204
-
205
- async def async_update(self) -> None:
206
- """Update the weather entity."""
207
- await self._hourly_forecast_coordinator.async_request_refresh()
208
- await self._daily_forecast_coordinator.async_request_refresh()
209
-
210
- @property
211
- def device_info(self) -> DeviceInfo:
212
- """Return the device info."""
213
- return DeviceInfo(
214
- identifiers={(DOMAIN, self._town_id)},
215
- name=f"Meteocat {self._station_id}",
216
- manufacturer="Meteocat",
217
- model="Meteocat API",
218
- )
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from datetime import datetime
5
+ from typing import Optional
6
+ from homeassistant.helpers.update_coordinator import CoordinatorEntity
7
+ from homeassistant.components.weather import (
8
+ WeatherEntity,
9
+ WeatherEntityFeature,
10
+ Forecast,
11
+ )
12
+ from homeassistant.core import callback
13
+ from homeassistant.helpers.entity_platform import AddEntitiesCallback
14
+ from homeassistant.helpers.device_registry import DeviceInfo
15
+ from homeassistant.const import (
16
+ DEGREE,
17
+ PERCENTAGE,
18
+ UnitOfPrecipitationDepth,
19
+ UnitOfPressure,
20
+ UnitOfSpeed,
21
+ UnitOfTemperature,
22
+ )
23
+
24
+ from .const import (
25
+ DOMAIN,
26
+ ATTRIBUTION,
27
+ WIND_SPEED_CODE,
28
+ WIND_DIRECTION_CODE,
29
+ TEMPERATURE_CODE,
30
+ HUMIDITY_CODE,
31
+ PRESSURE_CODE,
32
+ PRECIPITATION_CODE,
33
+ WIND_GUST_CODE,
34
+ )
35
+ from .coordinator import (
36
+ HourlyForecastCoordinator,
37
+ DailyForecastCoordinator,
38
+ MeteocatSensorCoordinator,
39
+ MeteocatUviFileCoordinator,
40
+ MeteocatConditionCoordinator,
41
+ )
42
+
43
+ _LOGGER = logging.getLogger(__name__)
44
+
45
+ @callback
46
+ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback) -> None:
47
+ """Set up Meteocat weather entity from a config entry."""
48
+ entry_data = hass.data[DOMAIN][entry.entry_id]
49
+
50
+ hourly_forecast_coordinator = entry_data.get("hourly_forecast_coordinator")
51
+ daily_forecast_coordinator = entry_data.get("daily_forecast_coordinator")
52
+ sensor_coordinator = entry_data.get("sensor_coordinator")
53
+ uvi_file_coordinator = entry_data.get("uvi_file_coordinator")
54
+ condition_coordinator = entry_data.get("condition_coordinator")
55
+
56
+ async_add_entities([
57
+ MeteocatWeatherEntity(
58
+ hourly_forecast_coordinator,
59
+ daily_forecast_coordinator,
60
+ sensor_coordinator,
61
+ uvi_file_coordinator,
62
+ condition_coordinator,
63
+ entry_data
64
+ )
65
+ ])
66
+
67
+ class MeteocatWeatherEntity(CoordinatorEntity, WeatherEntity):
68
+ """Representation of a Meteocat Weather Entity."""
69
+
70
+ _attr_attribution = ATTRIBUTION
71
+ _attr_has_entity_name = True
72
+ _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
73
+ _attr_native_precipitation_probability_unit = PERCENTAGE
74
+ _attr_native_pressure_unit = UnitOfPressure.HPA
75
+ _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
76
+ _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
77
+ _attr_native_wind_bearing_unit = DEGREE
78
+ _attr_supported_features = (
79
+ WeatherEntityFeature.FORECAST_HOURLY | WeatherEntityFeature.FORECAST_DAILY
80
+ )
81
+
82
+ def __init__(
83
+ self,
84
+ hourly_forecast_coordinator: HourlyForecastCoordinator,
85
+ daily_forecast_coordinator: DailyForecastCoordinator,
86
+ sensor_coordinator: MeteocatSensorCoordinator,
87
+ uvi_file_coordinator: MeteocatUviFileCoordinator,
88
+ condition_coordinator: MeteocatConditionCoordinator,
89
+ entry_data: dict,
90
+ ) -> None:
91
+ """Initialize the weather entity."""
92
+ super().__init__(daily_forecast_coordinator)
93
+ self._hourly_forecast_coordinator = hourly_forecast_coordinator
94
+ self._daily_forecast_coordinator = daily_forecast_coordinator
95
+ self._sensor_coordinator = sensor_coordinator
96
+ self._uvi_file_coordinator = uvi_file_coordinator
97
+ self._condition_coordinator = condition_coordinator
98
+ self._town_name = entry_data["town_name"]
99
+ self._town_id = entry_data["town_id"]
100
+ self._station_id = entry_data["station_id"]
101
+
102
+ @property
103
+ def name(self) -> str:
104
+ """Return the name of the entity."""
105
+ return f"Weather {self._town_name}"
106
+
107
+ @property
108
+ def unique_id(self) -> str:
109
+ """Return the unique ID of the entity."""
110
+ return f"weather.{DOMAIN}_{self._town_id}"
111
+
112
+ @property
113
+ def condition(self) -> Optional[str]:
114
+ """Return the current weather condition."""
115
+ condition_data = self._condition_coordinator.data or {}
116
+ return condition_data.get("condition")
117
+
118
+ def _get_latest_sensor_value(self, code: str) -> Optional[float]:
119
+ """Helper method to retrieve the latest sensor value."""
120
+ sensor_code = code
121
+ if not sensor_code:
122
+ return None
123
+
124
+ stations = self._sensor_coordinator.data or []
125
+ for station in stations:
126
+ variables = station.get("variables", [])
127
+ variable_data = next(
128
+ (var for var in variables if var.get("codi") == sensor_code),
129
+ None,
130
+ )
131
+ if variable_data:
132
+ lectures = variable_data.get("lectures", [])
133
+ if lectures:
134
+ return lectures[-1].get("valor")
135
+ return None
136
+
137
+ @property
138
+ def native_temperature(self) -> Optional[float]:
139
+ return self._get_latest_sensor_value(TEMPERATURE_CODE)
140
+
141
+ @property
142
+ def humidity(self) -> Optional[float]:
143
+ return self._get_latest_sensor_value(HUMIDITY_CODE)
144
+
145
+ @property
146
+ def native_pressure(self) -> Optional[float]:
147
+ return self._get_latest_sensor_value(PRESSURE_CODE)
148
+
149
+ @property
150
+ def native_wind_speed(self) -> Optional[float]:
151
+ return self._get_latest_sensor_value(WIND_SPEED_CODE)
152
+
153
+ @property
154
+ def native_wind_gust_speed(self) -> Optional[float]:
155
+ return self._get_latest_sensor_value(WIND_GUST_CODE)
156
+
157
+ @property
158
+ def wind_bearing(self) -> Optional[float]:
159
+ return self._get_latest_sensor_value(WIND_DIRECTION_CODE)
160
+
161
+ @property
162
+ def uv_index(self) -> Optional[float]:
163
+ """Return the UV index."""
164
+ uvi_data = self._uvi_file_coordinator.data or {}
165
+ return uvi_data.get("uvi")
166
+
167
+ async def async_forecast_daily(self) -> list[Forecast] | None:
168
+ """Return the daily forecast."""
169
+ await self._daily_forecast_coordinator.async_request_refresh()
170
+ daily_forecasts = self._daily_forecast_coordinator.get_all_daily_forecasts()
171
+ if not daily_forecasts:
172
+ return None
173
+
174
+ return [
175
+ Forecast(
176
+ datetime=forecast["date"],
177
+ temperature=forecast["temperature_max"],
178
+ templow=forecast["temperature_min"],
179
+ precipitation_probability=forecast["precipitation"],
180
+ condition=forecast["condition"],
181
+ )
182
+ for forecast in daily_forecasts
183
+ ]
184
+
185
+ async def async_forecast_hourly(self) -> list[Forecast] | None:
186
+ """Return the hourly forecast."""
187
+ await self._hourly_forecast_coordinator.async_request_refresh()
188
+ hourly_forecasts = self._hourly_forecast_coordinator.get_all_hourly_forecasts()
189
+ if not hourly_forecasts:
190
+ return None
191
+
192
+ return [
193
+ Forecast(
194
+ datetime=forecast["datetime"],
195
+ temperature=forecast["temperature"],
196
+ precipitation=forecast["precipitation"],
197
+ condition=forecast["condition"],
198
+ wind_speed=forecast["wind_speed"],
199
+ wind_bearing=forecast["wind_bearing"],
200
+ humidity=forecast["humidity"],
201
+ )
202
+ for forecast in hourly_forecasts
203
+ ]
204
+
205
+ async def async_update(self) -> None:
206
+ """Update the weather entity."""
207
+ await self._hourly_forecast_coordinator.async_request_refresh()
208
+ await self._daily_forecast_coordinator.async_request_refresh()
209
+
210
+ @property
211
+ def device_info(self) -> DeviceInfo:
212
+ """Return the device info."""
213
+ return DeviceInfo(
214
+ identifiers={(DOMAIN, self._town_id)},
215
+ name=f"Meteocat {self._station_id}",
216
+ manufacturer="Meteocat",
217
+ model="Meteocat API",
218
+ )
package/filetree.py CHANGED
@@ -1,48 +1,48 @@
1
- import subprocess
2
- import os
3
-
4
- def generate_file_tree():
5
- # Ruta donde se generará el archivo
6
- output_file = 'filetree.txt'
7
-
8
- # Ejecutar el comando git ls-files para obtener los archivos no ignorados
9
- git_command = ['git', 'ls-files']
10
- try:
11
- # Captura de la salida del comando git
12
- git_files = subprocess.check_output(git_command).decode('utf-8').splitlines()
13
-
14
- # Crear una estructura de árbol
15
- tree = {}
16
-
17
- # Construir el árbol de directorios
18
- for file in git_files:
19
- parts = file.split('/') # Separar la ruta del archivo por '/'
20
- current = tree
21
- for part in parts[:-1]: # Recorremos todos los directorios
22
- current = current.setdefault(part, {})
23
- current[parts[-1]] = None # El archivo final se marca como 'None'
24
-
25
- # Función recursiva para imprimir el árbol con la indentación correcta
26
- def print_tree(directory, indent=""):
27
- for name, subdirectory in directory.items():
28
- if subdirectory is None:
29
- # Si es un archivo, lo imprimimos
30
- f.write(f"{indent}├── {name}\n")
31
- else:
32
- # Si es un directorio, lo imprimimos y recursivamente llamamos a print_tree
33
- f.write(f"{indent}└── {name}/\n")
34
- print_tree(subdirectory, indent + " ")
35
-
36
- # Crear el archivo y escribir la estructura del árbol con codificación UTF-8
37
- with open(output_file, 'w', encoding='utf-8') as f:
38
- print_tree(tree)
39
-
40
- print(f"Árbol de directorios generado en: {os.path.abspath(output_file)}")
41
-
42
- except subprocess.CalledProcessError as e:
43
- print(f"Error al ejecutar el comando: {e}")
44
- except Exception as e:
45
- print(f"Ocurrió un error inesperado: {e}")
46
-
47
- if __name__ == "__main__":
48
- generate_file_tree()
1
+ import subprocess
2
+ import os
3
+
4
+ def generate_file_tree():
5
+ # Ruta donde se generará el archivo
6
+ output_file = 'filetree.txt'
7
+
8
+ # Ejecutar el comando git ls-files para obtener los archivos no ignorados
9
+ git_command = ['git', 'ls-files']
10
+ try:
11
+ # Captura de la salida del comando git
12
+ git_files = subprocess.check_output(git_command).decode('utf-8').splitlines()
13
+
14
+ # Crear una estructura de árbol
15
+ tree = {}
16
+
17
+ # Construir el árbol de directorios
18
+ for file in git_files:
19
+ parts = file.split('/') # Separar la ruta del archivo por '/'
20
+ current = tree
21
+ for part in parts[:-1]: # Recorremos todos los directorios
22
+ current = current.setdefault(part, {})
23
+ current[parts[-1]] = None # El archivo final se marca como 'None'
24
+
25
+ # Función recursiva para imprimir el árbol con la indentación correcta
26
+ def print_tree(directory, indent=""):
27
+ for name, subdirectory in directory.items():
28
+ if subdirectory is None:
29
+ # Si es un archivo, lo imprimimos
30
+ f.write(f"{indent}├── {name}\n")
31
+ else:
32
+ # Si es un directorio, lo imprimimos y recursivamente llamamos a print_tree
33
+ f.write(f"{indent}└── {name}/\n")
34
+ print_tree(subdirectory, indent + " ")
35
+
36
+ # Crear el archivo y escribir la estructura del árbol con codificación UTF-8
37
+ with open(output_file, 'w', encoding='utf-8') as f:
38
+ print_tree(tree)
39
+
40
+ print(f"Árbol de directorios generado en: {os.path.abspath(output_file)}")
41
+
42
+ except subprocess.CalledProcessError as e:
43
+ print(f"Error al ejecutar el comando: {e}")
44
+ except Exception as e:
45
+ print(f"Ocurrió un error inesperado: {e}")
46
+
47
+ if __name__ == "__main__":
48
+ generate_file_tree()