hassl 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {hassl-0.3.0/hassl.egg-info → hassl-0.3.2}/PKG-INFO +76 -6
- {hassl-0.3.0 → hassl-0.3.2}/README.md +75 -5
- hassl-0.3.2/hassl/__init__.py +1 -0
- hassl-0.3.2/hassl/ast/nodes.py +86 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/codegen/package.py +368 -13
- {hassl-0.3.0 → hassl-0.3.2}/hassl/codegen/rules_min.py +50 -13
- {hassl-0.3.0 → hassl-0.3.2}/hassl/parser/hassl.lark +52 -2
- hassl-0.3.2/hassl/parser/transform.py +521 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/semantics/analyzer.py +175 -13
- {hassl-0.3.0 → hassl-0.3.2/hassl.egg-info}/PKG-INFO +76 -6
- {hassl-0.3.0 → hassl-0.3.2}/hassl.egg-info/SOURCES.txt +2 -1
- {hassl-0.3.0 → hassl-0.3.2}/pyproject.toml +1 -1
- {hassl-0.3.0 → hassl-0.3.2}/setup.cfg +1 -1
- hassl-0.3.2/tests/test_schedule_windows_codegen.py +123 -0
- hassl-0.3.0/hassl/__init__.py +0 -1
- hassl-0.3.0/hassl/ast/nodes.py +0 -53
- hassl-0.3.0/hassl/parser/transform.py +0 -354
- {hassl-0.3.0 → hassl-0.3.2}/LICENSE +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/MANIFEST.in +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/ast/__init__.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/cli.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/codegen/__init__.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/codegen/generate.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/codegen/init.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/codegen/yaml_emit.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/parser/__init__.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/parser/loader.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/semantics/__init__.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl/semantics/domains.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl.egg-info/dependency_links.txt +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl.egg-info/entry_points.txt +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl.egg-info/requires.txt +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/hassl.egg-info/top_level.txt +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/tests/test_codegen_sync_basic.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/tests/test_golden_ir_sync_shared.py +0 -0
- {hassl-0.3.0 → hassl-0.3.2}/tests/test_imports_and_schedules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hassl
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: HASSL: Home Assistant Simple Scripting Language
|
|
5
5
|
Home-page: https://github.com/adanowitz/hassl
|
|
6
6
|
Author: adanowitz
|
|
@@ -17,7 +17,7 @@ Dynamic: license-file
|
|
|
17
17
|
|
|
18
18
|
> **Home Assistant Simple Scripting Language**
|
|
19
19
|
|
|
20
|
-

|
|
21
21
|
|
|
22
22
|
HASSL is a human-friendly domain-specific language (DSL) for building **loop-safe**, **deterministic**, and **composable** automations for [Home Assistant](https://www.home-assistant.io/).
|
|
23
23
|
|
|
@@ -30,11 +30,12 @@ It compiles lightweight `.hassl` scripts into fully functional YAML packages tha
|
|
|
30
30
|
- **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
|
|
31
31
|
- **Sync devices** → keep switches, dimmers, and fans perfectly in sync
|
|
32
32
|
- **Schedules** → declare time-based gates (`enable from 08:00 until 19:00`)
|
|
33
|
+
- **Weekday/weekend/holiday schedules** → full support for Home Assistant’s **Workday integration** (v0.3.1)
|
|
33
34
|
- **Loop-safe** → context ID tracking prevents feedback loops
|
|
34
35
|
- **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
|
|
35
36
|
- **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
|
|
36
37
|
- **Color temperature in Kelvin** → `light.kelvin = 2700`
|
|
37
|
-
- **Modular packages/imports** → split automations across files with public/private exports
|
|
38
|
+
- **Modular packages/imports** → split automations across files with public/private exports
|
|
38
39
|
- **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
|
|
39
40
|
|
|
40
41
|
---
|
|
@@ -147,7 +148,7 @@ Each `.hassl` file compiles into an isolated package — no naming collisions, n
|
|
|
147
148
|
| `scripts_<pkg>.yaml` | Writer scripts with context stamping |
|
|
148
149
|
| `sync_<pkg>_*.yaml` | Sync automations for each property |
|
|
149
150
|
| `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
|
|
150
|
-
| `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.
|
|
151
|
+
| `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.1) |
|
|
151
152
|
|
|
152
153
|
---
|
|
153
154
|
|
|
@@ -180,10 +181,79 @@ All schedules are restart-safe:
|
|
|
180
181
|
|
|
181
182
|
---
|
|
182
183
|
|
|
184
|
+
## 🗓️ Holiday & Workday Integration (v0.3.1)
|
|
185
|
+
|
|
186
|
+
HASSL now supports `holidays <id>:` schedules tied to Home Assistant’s **Workday** integration.
|
|
187
|
+
|
|
188
|
+
To enable holiday and weekday/weekend-aware schedules:
|
|
189
|
+
|
|
190
|
+
### 1️⃣ Create two Workday sensors in Home Assistant
|
|
191
|
+
|
|
192
|
+
You must create **two Workday integrations** through the Home Assistant UI.
|
|
193
|
+
|
|
194
|
+
#### Sensor 1 — `binary_sensor.hassl_<id>_workday`
|
|
195
|
+
- **Workdays:** Mon–Fri
|
|
196
|
+
- **Excludes:** `holiday`
|
|
197
|
+
- **Meaning:** ON only on real workdays (Mon–Fri that are not holidays).
|
|
198
|
+
|
|
199
|
+
#### Sensor 2 — `binary_sensor.hassl_<id>_not_holiday`
|
|
200
|
+
- **Workdays:** Mon–Sun
|
|
201
|
+
- **Excludes:** `holiday`
|
|
202
|
+
- **Meaning:** ON every day except official holidays (including weekends).
|
|
203
|
+
|
|
204
|
+
> In both, set your **Country** and optional **Province/Region** as needed for your locale (e.g., `US`, `CA`, `GB`, etc.).
|
|
205
|
+
> After setup, rename the entity IDs to exactly match:
|
|
206
|
+
> - `binary_sensor.hassl_<id>_workday`
|
|
207
|
+
> - `binary_sensor.hassl_<id>_not_holiday`
|
|
208
|
+
> where `<id>` matches the identifier used in your `.hassl` file (e.g., `us_ca`).
|
|
209
|
+
|
|
210
|
+
HASSL derives:
|
|
211
|
+
- `binary_sensor.hassl_holiday_<id>` → ON on holidays (even when they fall on weekends).
|
|
212
|
+
|
|
213
|
+
### Truth table
|
|
214
|
+
|
|
215
|
+
| Day type | `hassl_<id>_workday` | `hassl_<id>_not_holiday` | `hassl_holiday_<id>` (derived) |
|
|
216
|
+
|------------------------------|-----------------------|---------------------------|---------------------------------|
|
|
217
|
+
| Tue (normal) | on | on | off |
|
|
218
|
+
| Sat (normal weekend) | off | on | off |
|
|
219
|
+
| Mon that’s an official holiday | off | off | on |
|
|
220
|
+
| Sat that’s an official holiday | off | off | on |
|
|
221
|
+
|
|
222
|
+
This distinction lets you build precise schedules like:
|
|
223
|
+
```hassl
|
|
224
|
+
holidays us_ca:
|
|
225
|
+
country="US", province="CA"
|
|
226
|
+
|
|
227
|
+
schedule master_wake:
|
|
228
|
+
on weekdays 06:00–22:00 except holidays us_ca;
|
|
229
|
+
on weekends 08:00–22:00;
|
|
230
|
+
on holidays us_ca 09:00–22:00;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
🧩 **Note:**
|
|
234
|
+
Both sensors must be created manually in the Home Assistant UI — integrations can’t be defined in YAML.
|
|
235
|
+
Once created, HASSL automatically references them in generated automations.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## ⚗️ Experimental: Date & Month Range Schedules
|
|
240
|
+
|
|
241
|
+
HASSL v0.3.1 includes early support for:
|
|
242
|
+
|
|
243
|
+
```hassl
|
|
244
|
+
on months Jun–Aug 07:00–22:00;
|
|
245
|
+
on dates 12-24..01-02 06:00–20:00;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
These may compile successfully but are **not yet validated in production**.
|
|
249
|
+
They’re marked **experimental** and will be verified after template automation support (v0.4 milestone).
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
183
253
|
## 📚 Documentation
|
|
184
254
|
|
|
185
|
-
- [Quickstart Guide](./
|
|
186
|
-
- [Language Specification](./
|
|
255
|
+
- [Quickstart Guide](./quickstart.md)
|
|
256
|
+
- [Language Specification](./Hassl_spec.md)
|
|
187
257
|
|
|
188
258
|
---
|
|
189
259
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Home Assistant Simple Scripting Language**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
HASSL is a human-friendly domain-specific language (DSL) for building **loop-safe**, **deterministic**, and **composable** automations for [Home Assistant](https://www.home-assistant.io/).
|
|
8
8
|
|
|
@@ -15,11 +15,12 @@ It compiles lightweight `.hassl` scripts into fully functional YAML packages tha
|
|
|
15
15
|
- **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
|
|
16
16
|
- **Sync devices** → keep switches, dimmers, and fans perfectly in sync
|
|
17
17
|
- **Schedules** → declare time-based gates (`enable from 08:00 until 19:00`)
|
|
18
|
+
- **Weekday/weekend/holiday schedules** → full support for Home Assistant’s **Workday integration** (v0.3.1)
|
|
18
19
|
- **Loop-safe** → context ID tracking prevents feedback loops
|
|
19
20
|
- **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
|
|
20
21
|
- **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
|
|
21
22
|
- **Color temperature in Kelvin** → `light.kelvin = 2700`
|
|
22
|
-
- **Modular packages/imports** → split automations across files with public/private exports
|
|
23
|
+
- **Modular packages/imports** → split automations across files with public/private exports
|
|
23
24
|
- **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
|
|
24
25
|
|
|
25
26
|
---
|
|
@@ -132,7 +133,7 @@ Each `.hassl` file compiles into an isolated package — no naming collisions, n
|
|
|
132
133
|
| `scripts_<pkg>.yaml` | Writer scripts with context stamping |
|
|
133
134
|
| `sync_<pkg>_*.yaml` | Sync automations for each property |
|
|
134
135
|
| `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
|
|
135
|
-
| `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.
|
|
136
|
+
| `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.1) |
|
|
136
137
|
|
|
137
138
|
---
|
|
138
139
|
|
|
@@ -165,10 +166,79 @@ All schedules are restart-safe:
|
|
|
165
166
|
|
|
166
167
|
---
|
|
167
168
|
|
|
169
|
+
## 🗓️ Holiday & Workday Integration (v0.3.1)
|
|
170
|
+
|
|
171
|
+
HASSL now supports `holidays <id>:` schedules tied to Home Assistant’s **Workday** integration.
|
|
172
|
+
|
|
173
|
+
To enable holiday and weekday/weekend-aware schedules:
|
|
174
|
+
|
|
175
|
+
### 1️⃣ Create two Workday sensors in Home Assistant
|
|
176
|
+
|
|
177
|
+
You must create **two Workday integrations** through the Home Assistant UI.
|
|
178
|
+
|
|
179
|
+
#### Sensor 1 — `binary_sensor.hassl_<id>_workday`
|
|
180
|
+
- **Workdays:** Mon–Fri
|
|
181
|
+
- **Excludes:** `holiday`
|
|
182
|
+
- **Meaning:** ON only on real workdays (Mon–Fri that are not holidays).
|
|
183
|
+
|
|
184
|
+
#### Sensor 2 — `binary_sensor.hassl_<id>_not_holiday`
|
|
185
|
+
- **Workdays:** Mon–Sun
|
|
186
|
+
- **Excludes:** `holiday`
|
|
187
|
+
- **Meaning:** ON every day except official holidays (including weekends).
|
|
188
|
+
|
|
189
|
+
> In both, set your **Country** and optional **Province/Region** as needed for your locale (e.g., `US`, `CA`, `GB`, etc.).
|
|
190
|
+
> After setup, rename the entity IDs to exactly match:
|
|
191
|
+
> - `binary_sensor.hassl_<id>_workday`
|
|
192
|
+
> - `binary_sensor.hassl_<id>_not_holiday`
|
|
193
|
+
> where `<id>` matches the identifier used in your `.hassl` file (e.g., `us_ca`).
|
|
194
|
+
|
|
195
|
+
HASSL derives:
|
|
196
|
+
- `binary_sensor.hassl_holiday_<id>` → ON on holidays (even when they fall on weekends).
|
|
197
|
+
|
|
198
|
+
### Truth table
|
|
199
|
+
|
|
200
|
+
| Day type | `hassl_<id>_workday` | `hassl_<id>_not_holiday` | `hassl_holiday_<id>` (derived) |
|
|
201
|
+
|------------------------------|-----------------------|---------------------------|---------------------------------|
|
|
202
|
+
| Tue (normal) | on | on | off |
|
|
203
|
+
| Sat (normal weekend) | off | on | off |
|
|
204
|
+
| Mon that’s an official holiday | off | off | on |
|
|
205
|
+
| Sat that’s an official holiday | off | off | on |
|
|
206
|
+
|
|
207
|
+
This distinction lets you build precise schedules like:
|
|
208
|
+
```hassl
|
|
209
|
+
holidays us_ca:
|
|
210
|
+
country="US", province="CA"
|
|
211
|
+
|
|
212
|
+
schedule master_wake:
|
|
213
|
+
on weekdays 06:00–22:00 except holidays us_ca;
|
|
214
|
+
on weekends 08:00–22:00;
|
|
215
|
+
on holidays us_ca 09:00–22:00;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
🧩 **Note:**
|
|
219
|
+
Both sensors must be created manually in the Home Assistant UI — integrations can’t be defined in YAML.
|
|
220
|
+
Once created, HASSL automatically references them in generated automations.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## ⚗️ Experimental: Date & Month Range Schedules
|
|
225
|
+
|
|
226
|
+
HASSL v0.3.1 includes early support for:
|
|
227
|
+
|
|
228
|
+
```hassl
|
|
229
|
+
on months Jun–Aug 07:00–22:00;
|
|
230
|
+
on dates 12-24..01-02 06:00–20:00;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
These may compile successfully but are **not yet validated in production**.
|
|
234
|
+
They’re marked **experimental** and will be verified after template automation support (v0.4 milestone).
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
168
238
|
## 📚 Documentation
|
|
169
239
|
|
|
170
|
-
- [Quickstart Guide](./
|
|
171
|
-
- [Language Specification](./
|
|
240
|
+
- [Quickstart Guide](./quickstart.md)
|
|
241
|
+
- [Language Specification](./Hassl_spec.md)
|
|
172
242
|
|
|
173
243
|
---
|
|
174
244
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.2"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from dataclasses import dataclass, asdict, field
|
|
2
|
+
from typing import List, Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Alias:
|
|
6
|
+
name: str
|
|
7
|
+
entity: str
|
|
8
|
+
private: bool = False
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Sync:
|
|
12
|
+
kind: str
|
|
13
|
+
members: List[str]
|
|
14
|
+
name: str
|
|
15
|
+
invert: List[str] = field(default_factory=list)
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class IfClause:
|
|
19
|
+
condition: Dict[str, Any]
|
|
20
|
+
actions: List[Dict[str, Any]]
|
|
21
|
+
|
|
22
|
+
# ---- NEW: Holiday sets & structured schedule windows ----
|
|
23
|
+
@dataclass
|
|
24
|
+
class HolidaySet:
|
|
25
|
+
id: str
|
|
26
|
+
country: str
|
|
27
|
+
province: Optional[str] = None
|
|
28
|
+
add: List[str] = field(default_factory=list) # YYYY-MM-DD
|
|
29
|
+
remove: List[str] = field(default_factory=list)
|
|
30
|
+
workdays: List[str] = field(default_factory=lambda: ["mon","tue","wed","thu","fri"])
|
|
31
|
+
excludes: List[str] = field(default_factory=lambda: ["sat","sun","holiday"])
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class PeriodSelector:
|
|
35
|
+
# kind = 'months' | 'dates' | 'range'
|
|
36
|
+
kind: str
|
|
37
|
+
# data:
|
|
38
|
+
# - months: {"list":[Mon,...]} or {"range":[Mon,Mon]}
|
|
39
|
+
# - dates: {"start":"MM-DD","end":"MM-DD"}
|
|
40
|
+
# - range: {"start":"YYYY-MM-DD","end":"YYYY-MM-DD"}
|
|
41
|
+
data: Dict[str, Any]
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ScheduleWindow:
|
|
45
|
+
start: str # "HH:MM"
|
|
46
|
+
end: str # "HH:MM"
|
|
47
|
+
day_selector: str # "weekdays" | "weekends" | "daily"
|
|
48
|
+
period: Optional[PeriodSelector] = None
|
|
49
|
+
holiday_ref: Optional[str] = None # id from HolidaySet (for 'except'/'only')
|
|
50
|
+
holiday_mode: Optional[str] = None # "except" | "only" | None
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Schedule:
|
|
54
|
+
name: str
|
|
55
|
+
# raw clauses as produced by the transformer (legacy form)
|
|
56
|
+
clauses: List[Dict[str, Any]]
|
|
57
|
+
# structured windows for the new 'on ...' syntax (optional)
|
|
58
|
+
windows: List[ScheduleWindow] = field(default_factory=list)
|
|
59
|
+
private: bool = False
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Rule:
|
|
63
|
+
name: str
|
|
64
|
+
# allow schedule dicts
|
|
65
|
+
clauses: List[Any]
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Program:
|
|
69
|
+
statements: List[object]
|
|
70
|
+
package: Optional[str] = None
|
|
71
|
+
# normalized import entries (dicts) from the transformer:
|
|
72
|
+
# {"type":"import","module": "...", "kind": "glob|list|alias", "items":
|
|
73
|
+
#[...], "as": "name"|None}
|
|
74
|
+
imports: List[Dict[str, Any]] = field(default_factory=list)
|
|
75
|
+
def to_dict(self):
|
|
76
|
+
def enc(x):
|
|
77
|
+
if isinstance(x, (Alias, Sync, Rule, IfClause, Schedule,
|
|
78
|
+
HolidaySet, ScheduleWindow, PeriodSelector)):
|
|
79
|
+
d = asdict(x); d["type"] = x.__class__.__name__; return d
|
|
80
|
+
return x
|
|
81
|
+
return {
|
|
82
|
+
"type": "Program",
|
|
83
|
+
"package": self.package,
|
|
84
|
+
"imports": self.imports,
|
|
85
|
+
"statements": [enc(s) for s in self.statements],
|
|
86
|
+
}
|