hassl 0.2.0__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.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Andrew Danowitz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hassl-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: hassl
3
+ Version: 0.2.0
4
+ Summary: HASSL: Home Assistant Simple Scripting Language
5
+ Home-page: https://github.com/adanowitz/hassl
6
+ Author: adanowitz
7
+ Author-email: adanowitz@gmail.com
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: lark-parser
12
+ Requires-Dist: jinja2
13
+ Requires-Dist: pyyaml
14
+ Dynamic: license-file
15
+
16
+ # HASSL
17
+
18
+ > **Home Assistant Simple Scripting Language**
19
+
20
+ 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
+
22
+ It compiles lightweight `.hassl` scripts into fully functional YAML packages that plug directly into Home Assistant, replacing complex automations with a clean, readable syntax.
23
+
24
+ ---
25
+
26
+ ## 🚀 Features
27
+
28
+ - **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
29
+ - **Sync devices** → keep switches, dimmers, and fans perfectly in sync
30
+ - **Schedules** → declare time-based gates (`enable from 08:00 until 19:00`)
31
+ - **Loop-safe** → context ID tracking prevents feedback loops
32
+ - **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
33
+ - **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
34
+ - **Color temperature in Kelvin** → `light.kelvin = 2700`
35
+ - **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
36
+
37
+ ---
38
+
39
+ ## 🧰 Example
40
+
41
+ ```hassl
42
+ alias light = light.wesley_lamp
43
+ alias motion = binary_sensor.wesley_motion_motion
44
+ alias lux = sensor.wesley_motion_illuminance
45
+
46
+ schedule wake_hours:
47
+ enable from 08:00 until 19:00;
48
+
49
+ rule wesley_motion_light:
50
+ schedule use wake_hours;
51
+ if (motion && lux < 50)
52
+ then light = on;
53
+ wait (!motion for 10m) light = off
54
+
55
+ rule landing_manual_off:
56
+ if (light == off) not_by any_hassl
57
+ then disable rule wesley_motion_light for 3m
58
+ ```
59
+
60
+ Produces a complete Home Assistant package with:
61
+
62
+ - Helpers (`input_boolean`, `input_text`, `input_number`)
63
+ - Context-aware writer scripts
64
+ - Sync automations for linked devices
65
+ - Rule-based automations with schedules and `not_by` guards
66
+
67
+ ---
68
+
69
+ ## 🏗 Installation
70
+
71
+ ```bash
72
+ git clone https://github.com/adanowitz/hassl.git
73
+ cd hassl
74
+ pip install -e .
75
+ ```
76
+
77
+ Verify:
78
+
79
+ ```bash
80
+ hasslc --help
81
+ ```
82
+
83
+ ---
84
+
85
+ ## ⚙️ Usage
86
+
87
+ 1. Create a `.hassl` script (e.g., `living_room.hassl`).
88
+ 2. Compile it into a Home Assistant package:
89
+ ```bash
90
+ hasslc living_room.hassl -o ./packages/living_room/
91
+ ```
92
+ 3. Copy the package into `/config/packages/` and reload automations.
93
+
94
+ Each `.hassl` file compiles into an isolated package — no naming collisions, no shared helpers.
95
+
96
+ ---
97
+
98
+ ## 📦 Output Files
99
+
100
+ | File | Description |
101
+ | -------------------------- | --------------------------------------------- |
102
+ | `helpers_<pkg>.yaml` | Defines all helpers (booleans, text, numbers) |
103
+ | `scripts_<pkg>.yaml` | Writer scripts with context stamping |
104
+ | `sync_<pkg>_*.yaml` | Sync automations for each property |
105
+ | `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
106
+
107
+ ---
108
+
109
+ ## 🧠 Concepts
110
+
111
+ | Concept | Description |
112
+ | ------------ | --------------------------------------------------------------- |
113
+ | **Alias** | Maps short names to HA entities (`alias light = light.kitchen`) |
114
+ | **Sync** | Keeps multiple devices aligned across on/off, brightness, etc. |
115
+ | **Rule** | Defines reactive logic with guards, waits, and control flow. |
116
+ | **Schedule** | Defines active time windows, reusable across rules. |
117
+ | **Tag** | Lightweight metadata stored in `input_text` helpers. |
118
+
119
+ ---
120
+
121
+ ## 🔒 Loop Safety & Context Tracking
122
+
123
+ HASSL automatically writes the **parent context ID** into helper entities before performing actions.\
124
+ This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly, preventing infinite feedback.
125
+
126
+ ---
127
+
128
+ ## 🕒 Schedules That Survive Restarts
129
+
130
+ All schedules are restart-safe:
131
+
132
+ - `input_boolean.hassl_schedule_<name>` automatically re-evaluates on startup.
133
+ - Triggers are set for both start and end times.
134
+ - Missed events (like mid-day restarts) are recovered automatically.
135
+
136
+ ---
137
+
138
+ ## 📚 Documentation
139
+
140
+ For full grammar and detailed semantics, see the [HASSL Language Specification](./HASSL_Specification.md).
141
+
142
+ For a hands-on guide, check out the [Quickstart](./quickstart.md).
143
+
144
+ ---
145
+
146
+ ## 🧩 Contributing
147
+
148
+ Contributions, tests, and ideas welcome!\
149
+ To run tests locally:
150
+
151
+ ```bash
152
+ pytest tests/
153
+ ```
154
+
155
+ Please open pull requests for grammar improvements, new device domains, or scheduling logic.
156
+
157
+ ---
158
+
159
+ ## 📄 License
160
+
161
+ MIT License © 2025\
162
+ Created and maintained by [@adanowitz](https://github.com/adanowitz)
163
+
164
+ ---
165
+
166
+ **HASSL** — simple, reliable, human-readable automations for Home Assistant.
167
+
hassl-0.2.0/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # HASSL
2
+
3
+ > **Home Assistant Simple Scripting Language**
4
+
5
+ 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/).
6
+
7
+ It compiles lightweight `.hassl` scripts into fully functional YAML packages that plug directly into Home Assistant, replacing complex automations with a clean, readable syntax.
8
+
9
+ ---
10
+
11
+ ## 🚀 Features
12
+
13
+ - **Readable DSL** → write logic like natural language (`if motion && lux < 50 then light = on`)
14
+ - **Sync devices** → keep switches, dimmers, and fans perfectly in sync
15
+ - **Schedules** → declare time-based gates (`enable from 08:00 until 19:00`)
16
+ - **Loop-safe** → context ID tracking prevents feedback loops
17
+ - **Per-rule enable gates** → `disable rule` or `enable rule` dynamically
18
+ - **Inline waits** → `wait (!motion for 10m)` works like native HA triggers
19
+ - **Color temperature in Kelvin** → `light.kelvin = 2700`
20
+ - **Auto-reload resilience** → schedules re-evaluate automatically on HA restart
21
+
22
+ ---
23
+
24
+ ## 🧰 Example
25
+
26
+ ```hassl
27
+ alias light = light.wesley_lamp
28
+ alias motion = binary_sensor.wesley_motion_motion
29
+ alias lux = sensor.wesley_motion_illuminance
30
+
31
+ schedule wake_hours:
32
+ enable from 08:00 until 19:00;
33
+
34
+ rule wesley_motion_light:
35
+ schedule use wake_hours;
36
+ if (motion && lux < 50)
37
+ then light = on;
38
+ wait (!motion for 10m) light = off
39
+
40
+ rule landing_manual_off:
41
+ if (light == off) not_by any_hassl
42
+ then disable rule wesley_motion_light for 3m
43
+ ```
44
+
45
+ Produces a complete Home Assistant package with:
46
+
47
+ - Helpers (`input_boolean`, `input_text`, `input_number`)
48
+ - Context-aware writer scripts
49
+ - Sync automations for linked devices
50
+ - Rule-based automations with schedules and `not_by` guards
51
+
52
+ ---
53
+
54
+ ## 🏗 Installation
55
+
56
+ ```bash
57
+ git clone https://github.com/adanowitz/hassl.git
58
+ cd hassl
59
+ pip install -e .
60
+ ```
61
+
62
+ Verify:
63
+
64
+ ```bash
65
+ hasslc --help
66
+ ```
67
+
68
+ ---
69
+
70
+ ## ⚙️ Usage
71
+
72
+ 1. Create a `.hassl` script (e.g., `living_room.hassl`).
73
+ 2. Compile it into a Home Assistant package:
74
+ ```bash
75
+ hasslc living_room.hassl -o ./packages/living_room/
76
+ ```
77
+ 3. Copy the package into `/config/packages/` and reload automations.
78
+
79
+ Each `.hassl` file compiles into an isolated package — no naming collisions, no shared helpers.
80
+
81
+ ---
82
+
83
+ ## 📦 Output Files
84
+
85
+ | File | Description |
86
+ | -------------------------- | --------------------------------------------- |
87
+ | `helpers_<pkg>.yaml` | Defines all helpers (booleans, text, numbers) |
88
+ | `scripts_<pkg>.yaml` | Writer scripts with context stamping |
89
+ | `sync_<pkg>_*.yaml` | Sync automations for each property |
90
+ | `rules_bundled_<pkg>.yaml` | Rule logic automations + schedules |
91
+
92
+ ---
93
+
94
+ ## 🧠 Concepts
95
+
96
+ | Concept | Description |
97
+ | ------------ | --------------------------------------------------------------- |
98
+ | **Alias** | Maps short names to HA entities (`alias light = light.kitchen`) |
99
+ | **Sync** | Keeps multiple devices aligned across on/off, brightness, etc. |
100
+ | **Rule** | Defines reactive logic with guards, waits, and control flow. |
101
+ | **Schedule** | Defines active time windows, reusable across rules. |
102
+ | **Tag** | Lightweight metadata stored in `input_text` helpers. |
103
+
104
+ ---
105
+
106
+ ## 🔒 Loop Safety & Context Tracking
107
+
108
+ HASSL automatically writes the **parent context ID** into helper entities before performing actions.\
109
+ This ensures `not_by any_hassl` and `not_by rule("name")` guards work flawlessly, preventing infinite feedback.
110
+
111
+ ---
112
+
113
+ ## 🕒 Schedules That Survive Restarts
114
+
115
+ All schedules are restart-safe:
116
+
117
+ - `input_boolean.hassl_schedule_<name>` automatically re-evaluates on startup.
118
+ - Triggers are set for both start and end times.
119
+ - Missed events (like mid-day restarts) are recovered automatically.
120
+
121
+ ---
122
+
123
+ ## 📚 Documentation
124
+
125
+ For full grammar and detailed semantics, see the [HASSL Language Specification](./HASSL_Specification.md).
126
+
127
+ For a hands-on guide, check out the [Quickstart](./quickstart.md).
128
+
129
+ ---
130
+
131
+ ## 🧩 Contributing
132
+
133
+ Contributions, tests, and ideas welcome!\
134
+ To run tests locally:
135
+
136
+ ```bash
137
+ pytest tests/
138
+ ```
139
+
140
+ Please open pull requests for grammar improvements, new device domains, or scheduling logic.
141
+
142
+ ---
143
+
144
+ ## 📄 License
145
+
146
+ MIT License © 2025\
147
+ Created and maintained by [@adanowitz](https://github.com/adanowitz)
148
+
149
+ ---
150
+
151
+ **HASSL** — simple, reliable, human-readable automations for Home Assistant.
152
+
File without changes
File without changes
@@ -0,0 +1,34 @@
1
+ from dataclasses import dataclass, asdict, field
2
+ from typing import List, Any, Dict
3
+
4
+ @dataclass
5
+ class Alias:
6
+ name: str
7
+ entity: str
8
+
9
+ @dataclass
10
+ class Sync:
11
+ kind: str
12
+ members: List[str]
13
+ name: str
14
+ invert: List[str] = field(default_factory=list)
15
+
16
+ @dataclass
17
+ class IfClause:
18
+ condition: Dict[str, Any]
19
+ actions: List[Dict[str, Any]]
20
+
21
+ @dataclass
22
+ class Rule:
23
+ name: str
24
+ clauses: List[IfClause]
25
+
26
+ @dataclass
27
+ class Program:
28
+ statements: List[object]
29
+ def to_dict(self):
30
+ def enc(x):
31
+ if isinstance(x, (Alias, Sync, Rule, IfClause)):
32
+ d = asdict(x); d["type"] = x.__class__.__name__; return d
33
+ return x
34
+ return {"type": "Program","statements": [enc(s) for s in self.statements]}
@@ -0,0 +1,42 @@
1
+ import argparse
2
+ import os, json
3
+ from .parser.transform import HasslTransformer
4
+ from .ast.nodes import Program
5
+ from lark import Lark
6
+ from .semantics.analyzer import analyze
7
+ from .codegen.package import emit_package
8
+ from .codegen import generate as codegen_generate
9
+
10
+ GRAMMAR_PATH = os.path.join(os.path.dirname(__file__), "parser", "hassl.lark")
11
+
12
+ def parse_hassl(text: str) -> Program:
13
+ with open(GRAMMAR_PATH) as f:
14
+ grammar = f.read()
15
+ parser = Lark(grammar, start="start", parser="lalr", maybe_placeholders=False)
16
+ tree = parser.parse(text)
17
+ program = HasslTransformer().transform(tree)
18
+ return program
19
+
20
+ def main():
21
+ ap = argparse.ArgumentParser(prog="hasslc", description="HASSL Compiler")
22
+ ap.add_argument("input", help="Input .hassl file")
23
+ ap.add_argument("-o", "--out", default="./packages/out", help="Output directory for HA package")
24
+ args = ap.parse_args()
25
+
26
+ with open(args.input) as f:
27
+ src = f.read()
28
+
29
+ program = parse_hassl(src)
30
+ print("[hasslc] AST:", program.to_dict())
31
+ ir = analyze(program)
32
+ print("[hasslc] IR:", ir.to_dict())
33
+
34
+ ir_dict = ir.to_dict() if hasattr(ir, "to_dict") else ir
35
+ codegen_generate(ir_dict, args.out)
36
+ print(f"[hasslc] Package written to {args.out}")
37
+
38
+ os.makedirs(args.out, exist_ok=True)
39
+ emit_package(ir, args.out)
40
+ with open(os.path.join(args.out, "DEBUG_ir.json"), "w") as dbg:
41
+ dbg.write(json.dumps(ir.to_dict(), indent=2))
42
+ print(f"[hasslc] Package written to {args.out}")
@@ -0,0 +1,22 @@
1
+ from pathlib import Path
2
+ from .package import emit_package
3
+ from .rules_min import generate_rules
4
+
5
+ def generate(ir_obj, outdir):
6
+ """
7
+ Orchestrate codegen in a merge-safe order:
8
+ 1) emit_package: writes/merges helpers, scripts, and sync automations
9
+ 2) generate_rules: writes rules automations & merges gate booleans into helpers.yaml
10
+ + """
11
+ Path(outdir).mkdir(parents=True, exist_ok=True)
12
+
13
+ # 1) Sync & helpers first (merge-safe via yaml_emit._dump_yaml)
14
+ try:
15
+ emit_package(ir_obj if hasattr(ir_obj, "syncs") else ir_obj, outdir)
16
+ except Exception:
17
+ # keep going to still emit rules even if sync pass fails
18
+ pass
19
+
20
+ # 2) Rules last (adds gate booleans; also merge-safe)
21
+ generate_rules(ir_obj if isinstance(ir_obj, dict) else getattr(ir_obj, "to_dict", lambda: ir_obj)(), outdir)
22
+ return True