hassl 0.2.1__tar.gz → 0.3.1__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.
Files changed (40) hide show
  1. {hassl-0.2.1/hassl.egg-info → hassl-0.3.1}/PKG-INFO +123 -10
  2. hassl-0.3.1/README.md +265 -0
  3. hassl-0.3.1/hassl/__init__.py +1 -0
  4. hassl-0.3.1/hassl/ast/nodes.py +86 -0
  5. hassl-0.3.1/hassl/cli.py +372 -0
  6. hassl-0.3.1/hassl/codegen/generate.py +6 -0
  7. hassl-0.3.1/hassl/codegen/init.py +3 -0
  8. hassl-0.3.1/hassl/codegen/package.py +910 -0
  9. {hassl-0.2.1 → hassl-0.3.1}/hassl/codegen/rules_min.py +204 -196
  10. {hassl-0.2.1 → hassl-0.3.1}/hassl/parser/hassl.lark +75 -8
  11. hassl-0.3.1/hassl/parser/transform.py +696 -0
  12. hassl-0.3.1/hassl/semantics/analyzer.py +421 -0
  13. {hassl-0.2.1 → hassl-0.3.1/hassl.egg-info}/PKG-INFO +123 -10
  14. {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/SOURCES.txt +7 -1
  15. {hassl-0.2.1 → hassl-0.3.1}/pyproject.toml +1 -1
  16. {hassl-0.2.1 → hassl-0.3.1}/setup.cfg +1 -1
  17. hassl-0.3.1/tests/test_imports_and_schedules.py +257 -0
  18. hassl-0.3.1/tests/test_schedule_windows_codegen.py +123 -0
  19. hassl-0.2.1/README.md +0 -152
  20. hassl-0.2.1/hassl/ast/nodes.py +0 -34
  21. hassl-0.2.1/hassl/cli.py +0 -42
  22. hassl-0.2.1/hassl/codegen/package.py +0 -335
  23. hassl-0.2.1/hassl/parser/transform.py +0 -272
  24. hassl-0.2.1/hassl/semantics/__init__.py +0 -0
  25. hassl-0.2.1/hassl/semantics/analyzer.py +0 -145
  26. {hassl-0.2.1 → hassl-0.3.1}/LICENSE +0 -0
  27. {hassl-0.2.1 → hassl-0.3.1}/MANIFEST.in +0 -0
  28. {hassl-0.2.1/hassl → hassl-0.3.1/hassl/ast}/__init__.py +0 -0
  29. {hassl-0.2.1 → hassl-0.3.1}/hassl/codegen/__init__.py +0 -0
  30. {hassl-0.2.1 → hassl-0.3.1}/hassl/codegen/yaml_emit.py +0 -0
  31. {hassl-0.2.1/hassl/ast → hassl-0.3.1/hassl/parser}/__init__.py +0 -0
  32. {hassl-0.2.1 → hassl-0.3.1}/hassl/parser/loader.py +0 -0
  33. {hassl-0.2.1/hassl/parser → hassl-0.3.1/hassl/semantics}/__init__.py +0 -0
  34. {hassl-0.2.1 → hassl-0.3.1}/hassl/semantics/domains.py +0 -0
  35. {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/dependency_links.txt +0 -0
  36. {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/entry_points.txt +0 -0
  37. {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/requires.txt +0 -0
  38. {hassl-0.2.1 → hassl-0.3.1}/hassl.egg-info/top_level.txt +0 -0
  39. {hassl-0.2.1 → hassl-0.3.1}/tests/test_codegen_sync_basic.py +0 -0
  40. {hassl-0.2.1 → hassl-0.3.1}/tests/test_golden_ir_sync_shared.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hassl
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: HASSL: Home Assistant Simple Scripting Language
5
5
  Home-page: https://github.com/adanowitz/hassl
6
6
  Author: adanowitz
@@ -17,6 +17,8 @@ Dynamic: license-file
17
17
 
18
18
  > **Home Assistant Simple Scripting Language**
19
19
 
20
+ ![Version](https://img.shields.io/badge/version-v0.3.1-blue)
21
+
20
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/).
21
23
 
22
24
  It compiles lightweight `.hassl` scripts into fully functional YAML packages that plug directly into Home Assistant, replacing complex automations with a clean, readable syntax.
@@ -28,16 +30,20 @@ It compiles lightweight `.hassl` scripts into fully functional YAML packages tha
28
30
  - **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
29
31
  - **Sync devices** → keep switches, dimmers, and fans perfectly in sync
30
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)
31
34
  - **Loop-safe** → context ID tracking prevents feedback loops
32
35
  - **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
33
36
  - **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
34
37
  - **Color temperature in Kelvin** → `light.kelvin = 2700`
38
+ - **Modular packages/imports** → split automations across files with public/private exports
35
39
  - **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
36
40
 
37
41
  ---
38
42
 
39
43
  ## 🧰 Example
40
44
 
45
+ ### Basic standalone script
46
+
41
47
  ```hassl
42
48
  alias light = light.wesley_lamp
43
49
  alias motion = binary_sensor.wesley_motion_motion
@@ -64,6 +70,45 @@ Produces a complete Home Assistant package with:
64
70
  - Sync automations for linked devices
65
71
  - Rule-based automations with schedules and `not_by` guards
66
72
 
73
+ ### Using imports across packages
74
+
75
+ ```hassl
76
+ # packages/std/shared.hassl
77
+ package std.shared
78
+
79
+ alias light = light.wesley_lamp
80
+ alias motion = binary_sensor.wesley_motion_motion
81
+ alias lux = sensor.wesley_motion_illuminance
82
+
83
+ schedule wake_hours:
84
+ enable from 08:00 until 19:00;
85
+ ```
86
+
87
+ ```hassl
88
+ # packages/home/landing.hassl
89
+ package home.landing
90
+ import std.shared.*
91
+
92
+ rule wesley_motion_light:
93
+ schedule use wake_hours;
94
+ if (motion && lux < 50)
95
+ then light = on;
96
+ wait (!motion for 10m) light = off
97
+
98
+ rule landing_manual_off:
99
+ if (light == off) not_by any_hassl
100
+ then disable rule wesley_motion_light for 3m
101
+ ```
102
+
103
+ This setup produces:
104
+ - One **shared package** defining reusable aliases and schedules
105
+ - A **landing package** importing and reusing those exports
106
+
107
+ Together, they generate:
108
+ - ✅ Shared schedule sensor (`binary_sensor.hassl_schedule_std_shared_wake_hours_active`)
109
+ - ✅ Cross-package rule automations gated by that schedule
110
+ - ✅ Context-safe helpers and syncs for both packages
111
+
67
112
  ---
68
113
 
69
114
  ## 🏗 Installation
@@ -103,6 +148,7 @@ Each `.hassl` file compiles into an isolated package — no naming collisions, n
103
148
  | `scripts_<pkg>.yaml` | Writer scripts with context stamping |
104
149
  | `sync_<pkg>_*.yaml` | Sync automations for each property |
105
150
  | `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
151
+ | `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.1) |
106
152
 
107
153
  ---
108
154
 
@@ -120,7 +166,7 @@ Each `.hassl` file compiles into an isolated package — no naming collisions, n
120
166
 
121
167
  ## 🔒 Loop Safety & Context Tracking
122
168
 
123
- HASSL automatically writes the **parent context ID** into helper entities before performing actions.\
169
+ HASSL automatically writes the **parent context ID** into helper entities before performing actions.
124
170
  This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly, preventing infinite feedback.
125
171
 
126
172
  ---
@@ -129,23 +175,91 @@ This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly
129
175
 
130
176
  All schedules are restart-safe:
131
177
 
132
- - `input_boolean.hassl_schedule_<name>` automatically re-evaluates on startup.
133
- - Triggers are set for both start and end times.
178
+ - `binary_sensor.hassl_schedule_<package>_<name>_active` automatically re-evaluates on startup.
179
+ - Clock and sun-based windows update continuously through HA’s template engine.
134
180
  - Missed events (like mid-day restarts) are recovered automatically.
135
181
 
136
182
  ---
137
183
 
138
- ## 📚 Documentation
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
+ ```
139
247
 
140
- For full grammar and detailed semantics, see the [HASSL Language Specification](./HASSL_Specification.md).
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).
141
250
 
142
- For a hands-on guide, check out the [Quickstart](./quickstart.md).
251
+ ---
252
+
253
+ ## 📚 Documentation
254
+
255
+ - [Quickstart Guide](./quickstart.md)
256
+ - [Language Specification](./Hassl_spec.md)
143
257
 
144
258
  ---
145
259
 
146
260
  ## 🧩 Contributing
147
261
 
148
- Contributions, tests, and ideas welcome!\
262
+ Contributions, tests, and ideas welcome!
149
263
  To run tests locally:
150
264
 
151
265
  ```bash
@@ -158,10 +272,9 @@ Please open pull requests for grammar improvements, new device domains, or sched
158
272
 
159
273
  ## 📄 License
160
274
 
161
- MIT License © 2025\
275
+ MIT License © 2025
162
276
  Created and maintained by [@adanowitz](https://github.com/adanowitz)
163
277
 
164
278
  ---
165
279
 
166
280
  **HASSL** — simple, reliable, human-readable automations for Home Assistant.
167
-
hassl-0.3.1/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # HASSL
2
+
3
+ > **Home Assistant Simple Scripting Language**
4
+
5
+ ![Version](https://img.shields.io/badge/version-v0.3.1-blue)
6
+
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
+
9
+ It compiles lightweight `.hassl` scripts into fully functional YAML packages that plug directly into Home Assistant, replacing complex automations with a clean, readable syntax.
10
+
11
+ ---
12
+
13
+ ## 🚀 Features
14
+
15
+ - **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
16
+ - **Sync devices** → keep switches, dimmers, and fans perfectly in sync
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)
19
+ - **Loop-safe** → context ID tracking prevents feedback loops
20
+ - **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
21
+ - **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
22
+ - **Color temperature in Kelvin** → `light.kelvin = 2700`
23
+ - **Modular packages/imports** → split automations across files with public/private exports
24
+ - **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
25
+
26
+ ---
27
+
28
+ ## 🧰 Example
29
+
30
+ ### Basic standalone script
31
+
32
+ ```hassl
33
+ alias light = light.wesley_lamp
34
+ alias motion = binary_sensor.wesley_motion_motion
35
+ alias lux = sensor.wesley_motion_illuminance
36
+
37
+ schedule wake_hours:
38
+ enable from 08:00 until 19:00;
39
+
40
+ rule wesley_motion_light:
41
+ schedule use wake_hours;
42
+ if (motion && lux < 50)
43
+ then light = on;
44
+ wait (!motion for 10m) light = off
45
+
46
+ rule landing_manual_off:
47
+ if (light == off) not_by any_hassl
48
+ then disable rule wesley_motion_light for 3m
49
+ ```
50
+
51
+ Produces a complete Home Assistant package with:
52
+
53
+ - Helpers (`input_boolean`, `input_text`, `input_number`)
54
+ - Context-aware writer scripts
55
+ - Sync automations for linked devices
56
+ - Rule-based automations with schedules and `not_by` guards
57
+
58
+ ### Using imports across packages
59
+
60
+ ```hassl
61
+ # packages/std/shared.hassl
62
+ package std.shared
63
+
64
+ alias light = light.wesley_lamp
65
+ alias motion = binary_sensor.wesley_motion_motion
66
+ alias lux = sensor.wesley_motion_illuminance
67
+
68
+ schedule wake_hours:
69
+ enable from 08:00 until 19:00;
70
+ ```
71
+
72
+ ```hassl
73
+ # packages/home/landing.hassl
74
+ package home.landing
75
+ import std.shared.*
76
+
77
+ rule wesley_motion_light:
78
+ schedule use wake_hours;
79
+ if (motion && lux < 50)
80
+ then light = on;
81
+ wait (!motion for 10m) light = off
82
+
83
+ rule landing_manual_off:
84
+ if (light == off) not_by any_hassl
85
+ then disable rule wesley_motion_light for 3m
86
+ ```
87
+
88
+ This setup produces:
89
+ - One **shared package** defining reusable aliases and schedules
90
+ - A **landing package** importing and reusing those exports
91
+
92
+ Together, they generate:
93
+ - ✅ Shared schedule sensor (`binary_sensor.hassl_schedule_std_shared_wake_hours_active`)
94
+ - ✅ Cross-package rule automations gated by that schedule
95
+ - ✅ Context-safe helpers and syncs for both packages
96
+
97
+ ---
98
+
99
+ ## 🏗 Installation
100
+
101
+ ```bash
102
+ git clone https://github.com/adanowitz/hassl.git
103
+ cd hassl
104
+ pip install -e .
105
+ ```
106
+
107
+ Verify:
108
+
109
+ ```bash
110
+ hasslc --help
111
+ ```
112
+
113
+ ---
114
+
115
+ ## ⚙️ Usage
116
+
117
+ 1. Create a `.hassl` script (e.g., `living_room.hassl`).
118
+ 2. Compile it into a Home Assistant package:
119
+ ```bash
120
+ hasslc living_room.hassl -o ./packages/living_room/
121
+ ```
122
+ 3. Copy the package into `/config/packages/` and reload automations.
123
+
124
+ Each `.hassl` file compiles into an isolated package — no naming collisions, no shared helpers.
125
+
126
+ ---
127
+
128
+ ## 📦 Output Files
129
+
130
+ | File | Description |
131
+ | -------------------------- | --------------------------------------------- |
132
+ | `helpers_<pkg>.yaml` | Defines all helpers (booleans, text, numbers) |
133
+ | `scripts_<pkg>.yaml` | Writer scripts with context stamping |
134
+ | `sync_<pkg>_*.yaml` | Sync automations for each property |
135
+ | `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
136
+ | `schedules_<pkg>.yaml` | Time/sun-based schedule sensors (v0.3.1) |
137
+
138
+ ---
139
+
140
+ ## 🧠 Concepts
141
+
142
+ | Concept | Description |
143
+ | ------------ | --------------------------------------------------------------- |
144
+ | **Alias** | Maps short names to HA entities (`alias light = light.kitchen`) |
145
+ | **Sync** | Keeps multiple devices aligned across on/off, brightness, etc. |
146
+ | **Rule** | Defines reactive logic with guards, waits, and control flow. |
147
+ | **Schedule** | Defines active time windows, reusable across rules. |
148
+ | **Tag** | Lightweight metadata stored in `input_text` helpers. |
149
+
150
+ ---
151
+
152
+ ## 🔒 Loop Safety & Context Tracking
153
+
154
+ HASSL automatically writes the **parent context ID** into helper entities before performing actions.
155
+ This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly, preventing infinite feedback.
156
+
157
+ ---
158
+
159
+ ## 🕒 Schedules That Survive Restarts
160
+
161
+ All schedules are restart-safe:
162
+
163
+ - `binary_sensor.hassl_schedule_<package>_<name>_active` automatically re-evaluates on startup.
164
+ - Clock and sun-based windows update continuously through HA’s template engine.
165
+ - Missed events (like mid-day restarts) are recovered automatically.
166
+
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
+
238
+ ## 📚 Documentation
239
+
240
+ - [Quickstart Guide](./quickstart.md)
241
+ - [Language Specification](./Hassl_spec.md)
242
+
243
+ ---
244
+
245
+ ## 🧩 Contributing
246
+
247
+ Contributions, tests, and ideas welcome!
248
+ To run tests locally:
249
+
250
+ ```bash
251
+ pytest tests/
252
+ ```
253
+
254
+ Please open pull requests for grammar improvements, new device domains, or scheduling logic.
255
+
256
+ ---
257
+
258
+ ## 📄 License
259
+
260
+ MIT License © 2025
261
+ Created and maintained by [@adanowitz](https://github.com/adanowitz)
262
+
263
+ ---
264
+
265
+ **HASSL** — simple, reliable, human-readable automations for Home Assistant.
@@ -0,0 +1 @@
1
+ __version__ = "0.3.1"
@@ -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
+ }