From e69f4e215cc263c1ed5cc76bbb814bcd2f9f3f66 Mon Sep 17 00:00:00 2001 From: Tiwas Date: Mon, 5 Oct 2026 10:15:20 +0000 Subject: [PATCH 1/3] chore: remove remaining assistant attribution from repo files Second pass over AI_RULES.md section 7. The circadian source image is renamed to source.png (content unchanged) with .homeyignore updated so it stays out of the app bundle. robots.md loses its assistant credit line, DOCUMENTATION_PROMPT_TEMPLATE.md no longer names the conventions file, the .gitignore comment is neutral, and WORKLOG.md no longer repeats the names the first pass removed. A new WORKLOG.md entry logs this pass, including two signed PR comments that were edited on GitHub. --- .gitignore | 2 +- WORKLOG.md | 29 ++++++++++++++++-- no.tiwas.booleantoolbox/.homeyignore | 2 +- .../DOCUMENTATION_PROMPT_TEMPLATE.md | 6 ++-- ..._Image_j52hlcj52hlcj52h.png => source.png} | Bin no.tiwas.booleantoolbox/robots.md | 1 - 6 files changed, 31 insertions(+), 9 deletions(-) rename no.tiwas.booleantoolbox/assets/images_circadian_light_group/{Gemini_Generated_Image_j52hlcj52hlcj52h.png => source.png} (100%) diff --git a/.gitignore b/.gitignore index 42efbd7..c4263aa 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,7 @@ codebase-manifest.* *.tar.gz *.old -# Project Documentation (AI-generated, kept local) +# Project Documentation (kept local) PROJECT_DOCUMENTATION.md # Dashboard apps diff --git a/WORKLOG.md b/WORKLOG.md index a145178..dfed316 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -1,14 +1,37 @@ # Worklog +## 2026-10-05 — Second pass: remaining attribution and references + +### Requested +- Lars asked for a new pass over `AI_RULES.md` §7: messages had still been signed, and the conventions file had been referenced by name. Work from a separate worktree and leave no old worktrees or leftovers behind. + +### Implemented +- GitHub: two `@codex review` comments on PR #57 carried a "Generated by …" footer. Both were edited down to the command itself; a read-back confirmed the footer was not added again. +- A read-only check of all 35 PRs (descriptions, comments and review threads) and all 23 issues (bodies and comments) found nothing else to fix. The owner's replies in review threads (#43, #47, #48) carry no attribution. There are no releases. The remaining assistant names on GitHub come from the review bot itself (its own comments and reviews) and from the `@codex review` commands that start it; both were left as they are. +- Repo files: + - The circadian source image whose file name named the image generator is renamed to `source.png` (content unchanged), and `.homeyignore` names `source.png`, so it stays out of the app bundle as before. This matches the two source images renamed in the first pass. + - `robots.md`: removed the closing line that credited an assistant with maintaining the file. + - `DOCUMENTATION_PROMPT_TEMPLATE.md`: removed three references to the conventions file by name. + - `.gitignore`: neutral comment above the `PROJECT_DOCUMENTATION.md` pattern. + - This worklog: the first-pass entry no longer repeats the names it removed. + +### Not changed +- Commit history on `main` (trailers and merge messages with tool branch names), for the reasons given in the first pass. +- The rule text in `AI_RULES.md` and the conventions file itself, and the ignore patterns for local tool folders. +- The circadian source image still carries the generator's embedded content-credential metadata; only the file name changed. + +### Verification +- No test run: the change touches documentation, ignore files and an image name only. `source.png` is not referenced by any manifest or code, and the Homey CLI for `npm run test:package` is not available here. + ## 2026-10-05 — Remove assistant names and attribution ### Requested - Lars asked to remove AI attribution from pull requests and the repo, following `AI_RULES.md` §7. Reviews stay; Lars decides about comments himself. ### Implemented -- Pull request descriptions #47–#57: removed "Generated with …" footers, session links and "(CLAUDE.md …)" headings. A read-only check of all 33 PR titles and descriptions found no remaining attribution. -- Repo files: neutral wording in four `WORKLOG.md` lines; "(Codex review)" removed from four test names in `LongWaitFlowCards.test.js`; the two source images in `assets/images_logic_device/` and `assets/images_logic_unit/` renamed to `source.png`, with `.homeyignore` updated so they stay out of the app bundle; neutral `.gitignore` comment. -- Kept on purpose: the `.claude/` and `.codex-logs/` ignore patterns (they keep local tool files out of git and the bundle), the rule text in `AI_RULES.md`, the repo conventions file itself, and `DOCUMENTATION_PROMPT_TEMPLATE.md` (deleting files needs Lars's confirmation under rule 5). +- Pull request descriptions #47–#57: removed "Generated with …" footers, session links and headings that named the conventions file. A read-only check of all 33 PR titles and descriptions found no remaining attribution. +- Repo files: neutral wording in four `WORKLOG.md` lines; a tool name removed from four test names in `LongWaitFlowCards.test.js`; the two source images in `assets/images_logic_device/` and `assets/images_logic_unit/` renamed to `source.png`, with `.homeyignore` updated so they stay out of the app bundle; neutral `.gitignore` comment. +- Kept on purpose: the ignore patterns for local tool folders in `.gitignore`, `.homeyignore` and `scripts/check-package.js` (they keep local tool files out of git and the bundle), the rule text in `AI_RULES.md`, the repo conventions file itself, and `DOCUMENTATION_PROMPT_TEMPLATE.md` (deleting files needs Lars's confirmation under rule 5). ### Not done - Rewriting the commit history of `main` (trailers, merge messages with tool branch names, commits authored by an assistant identity) was not possible from the working environment. It would change about 470 commit ids and move four tags, and the original commits stay visible on the pull request pages anyway. diff --git a/no.tiwas.booleantoolbox/.homeyignore b/no.tiwas.booleantoolbox/.homeyignore index 3097c32..13a418b 100644 --- a/no.tiwas.booleantoolbox/.homeyignore +++ b/no.tiwas.booleantoolbox/.homeyignore @@ -26,7 +26,7 @@ assets/images_composite_device/source.svg assets/images_logic_device.backup/ assets/images_logic_unit.backup/ assets/images_circadian_light_group/circadian light group.png -assets/images_circadian_light_group/Gemini_Generated_Image_*.png +assets/images_circadian_light_group/source.png assets/images_circadian_light_group/logo_nobg.png assets/images_logic_device/source.png assets/images_logic_unit/source.png diff --git a/no.tiwas.booleantoolbox/DOCUMENTATION_PROMPT_TEMPLATE.md b/no.tiwas.booleantoolbox/DOCUMENTATION_PROMPT_TEMPLATE.md index 982c3ac..c2f3d64 100644 --- a/no.tiwas.booleantoolbox/DOCUMENTATION_PROMPT_TEMPLATE.md +++ b/no.tiwas.booleantoolbox/DOCUMENTATION_PROMPT_TEMPLATE.md @@ -15,7 +15,7 @@ I need to get this project documented. Therefore, I want you to go through all c 3. Create a separate file that explains the project as a whole, what each file in the project does. There must also be a dependency map for the project. -4. Add instructions to robots.md (or CLAUDE.md / CONTRIBUTING.md) for keeping this updated for each session. +4. Add instructions to robots.md (or CONTRIBUTING.md) for keeping this updated for each session. 5. All comments and documentation must be in English. @@ -82,7 +82,7 @@ Create PROJECT_DOCUMENTATION.md containing: - Ensure documentation is not pushed to git ### 3. AI Instructions File -Update robots.md (or create CLAUDE.md) with: +Update robots.md with: - Core principles (complete code, no breaking changes, etc.) - Documentation maintenance instructions - Code quality guidelines @@ -92,7 +92,7 @@ Update robots.md (or create CLAUDE.md) with: When this template is executed on a project, the following files are created: -1. **robots.md** (or CLAUDE.md) +1. **robots.md** - AI assistant behavioral guidelines - Documentation maintenance procedures - Code quality requirements diff --git a/no.tiwas.booleantoolbox/assets/images_circadian_light_group/Gemini_Generated_Image_j52hlcj52hlcj52h.png b/no.tiwas.booleantoolbox/assets/images_circadian_light_group/source.png similarity index 100% rename from no.tiwas.booleantoolbox/assets/images_circadian_light_group/Gemini_Generated_Image_j52hlcj52hlcj52h.png rename to no.tiwas.booleantoolbox/assets/images_circadian_light_group/source.png diff --git a/no.tiwas.booleantoolbox/robots.md b/no.tiwas.booleantoolbox/robots.md index 9ffa5ae..2518a57 100644 --- a/no.tiwas.booleantoolbox/robots.md +++ b/no.tiwas.booleantoolbox/robots.md @@ -132,4 +132,3 @@ When creating new files: --- *Last updated: December 2024* -*Documentation maintained by AI assistant according to project guidelines* From eaa0f2fbdaa214bc0785db678e44b346ae76900b Mon Sep 17 00:00:00 2001 From: Tiwas Date: Mon, 5 Oct 2026 12:54:08 +0000 Subject: [PATCH 2/3] chore: remove old session note and committed coverage reports SESSION_SUMMARY.md and the generated reports in no.tiwas.booleantoolbox/coverage/ are not referenced anywhere. coverage/ is added to .gitignore so test:coverage output is not committed again. WORKLOG.md records the removal and the deleted branches. --- .gitignore | 1 + SESSION_SUMMARY.md | 38 - WORKLOG.md | 5 +- .../coverage/BaseLogicDriver.js.html | 1516 -------- .../coverage/BaseLogicUnit.js.html | 3226 ----------------- .../coverage/FormulaEvaluator.js.html | 1174 ------ .../coverage/Logger.js.html | 1303 ------- no.tiwas.booleantoolbox/coverage/base.css | 224 -- .../coverage/block-navigation.js | 87 - no.tiwas.booleantoolbox/coverage/favicon.png | Bin 445 -> 0 bytes no.tiwas.booleantoolbox/coverage/index.html | 176 - .../lcov-report/BaseLogicDriver.js.html | 1516 -------- .../lcov-report/BaseLogicUnit.js.html | 3226 ----------------- .../lcov-report/FormulaEvaluator.js.html | 1174 ------ .../coverage/lcov-report/Logger.js.html | 1303 ------- .../coverage/lcov-report/base.css | 224 -- .../coverage/lcov-report/block-navigation.js | 87 - .../coverage/lcov-report/favicon.png | Bin 445 -> 0 bytes .../coverage/lcov-report/index.html | 176 - .../coverage/lcov-report/loggerConfig.js.html | 199 - .../coverage/lcov-report/prettify.css | 1 - .../coverage/lcov-report/prettify.js | 2 - .../lcov-report/sort-arrow-sprite.png | Bin 138 -> 0 bytes .../coverage/lcov-report/sorter.js | 210 -- no.tiwas.booleantoolbox/coverage/lcov.info | 1776 --------- .../coverage/loggerConfig.js.html | 199 - no.tiwas.booleantoolbox/coverage/prettify.css | 1 - no.tiwas.booleantoolbox/coverage/prettify.js | 2 - .../coverage/sort-arrow-sprite.png | Bin 138 -> 0 bytes no.tiwas.booleantoolbox/coverage/sorter.js | 210 -- 30 files changed, 5 insertions(+), 18051 deletions(-) delete mode 100644 SESSION_SUMMARY.md delete mode 100644 no.tiwas.booleantoolbox/coverage/BaseLogicDriver.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/BaseLogicUnit.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/FormulaEvaluator.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/Logger.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/base.css delete mode 100644 no.tiwas.booleantoolbox/coverage/block-navigation.js delete mode 100644 no.tiwas.booleantoolbox/coverage/favicon.png delete mode 100644 no.tiwas.booleantoolbox/coverage/index.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicDriver.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicUnit.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/FormulaEvaluator.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/Logger.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/base.css delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/block-navigation.js delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/favicon.png delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/index.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/loggerConfig.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/prettify.css delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/prettify.js delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/sort-arrow-sprite.png delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov-report/sorter.js delete mode 100644 no.tiwas.booleantoolbox/coverage/lcov.info delete mode 100644 no.tiwas.booleantoolbox/coverage/loggerConfig.js.html delete mode 100644 no.tiwas.booleantoolbox/coverage/prettify.css delete mode 100644 no.tiwas.booleantoolbox/coverage/prettify.js delete mode 100644 no.tiwas.booleantoolbox/coverage/sort-arrow-sprite.png delete mode 100644 no.tiwas.booleantoolbox/coverage/sorter.js diff --git a/.gitignore b/.gitignore index c4263aa..e5b5765 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ npm-debug.log patch-files/ *.backup/ *.old/ +coverage/ # Old version no.tiwas.booleantoolbox.old/ diff --git a/SESSION_SUMMARY.md b/SESSION_SUMMARY.md deleted file mode 100644 index 2f6724e..0000000 --- a/SESSION_SUMMARY.md +++ /dev/null @@ -1,38 +0,0 @@ -# Session Summary - -## Active Task: Web API Integration & Save Functionality - -### What has been done: -1. **"Save to Device" Feature:** - * Implemented a button to save configuration changes directly back to the Homey device via the Web API. - * **Challenge:** Encountered persistent `403 Forbidden` ("Missing Scopes") errors when trying to use `setDeviceSettings`, despite the user apparently granting "Control your Devices" permission. - * **Resolution (Partial):** Implemented a robust fallback. If the automatic save fails (due to permission issues), the app now catches the error, alerts the user, and automatically opens the Import/Merge modal with the JSON pre-filled, ready for manual copying. -2. **Scope Investigation:** - * Conducted extensive testing with various OAuth scopes to resolve `403` and "No Homeys found" issues. - * **Discovery:** The "View your Homeys" permission (required to list devices) appears to be an implicit scope only granted when *no* specific scopes are requested (default/legacy behavior), or via a scope we couldn't isolate (`homey.user.self` etc. was not enough). - * **Current Config:** The login function now requests *default* scopes (empty list) to ensure "View your Homeys" is granted, allowing device listing to work reliably. -3. **UI & DX Improvements:** - * Updated the "Create one here" link to point correctly to the Homey API Clients portal. - * Updated UI text to list recommended scopes (`homey`, `homey.device.control` etc.) even if we request defaults in code. - * Removed excessive `console.log` debugging statements to clean up the console. - * Switched authentication strategy to `strategy: 'cloud'` in `selectHomey` to attempt to bypass local permission issues, though the 403 persists for writing. -4. **Scope Mode & Save Fallback Enhancements (2025-12-02):** - * Added a scope-mode selector (Default vs Explicit) plus display of granted scopes and auth strategy after login. - * Warns before save if write scopes (`homey.device.control`/`homey.app.control`) are missing; persists scope-mode choice locally. - * Save now retries with a `strategy: 'local'` authentication before falling back to manual copy/import, with clearer error messaging. - -### Current Status: -* **State Editor API (`tools/state-editor-api.html`):** - * **Authentication:** Uses default scopes to ensure device listing works. - * **Listing:** Successfully lists Homeys and State Devices. - * **Reading:** Successfully loads configuration from devices. - * **Writing:** "Save to Device" attempts to write to the device. If it fails (likely due to scope restrictions on 3rd party API clients), it retries once with local auth before gracefully falling back to offering the JSON for manual copy-paste. - * **Code Quality:** Cleaned up debug logs and fallback code. - -### Next Steps / Unresolved Issues: -1. **403 Forbidden on Save:** The underlying reason why `setDeviceSettings` fails despite "Control your Devices" permission remains verified but unresolved. It may require a specific scope not available to standard API clients or `homey.app.control`. Now surfaced in the UI via granted scopes. -2. **User Verification:** User should verify whether the local-auth retry succeeds in their environment; otherwise confirm the manual copy-paste fallback is acceptable. - -### File Tracker: -* `tools/state-editor-api.html` (Modified - Feature implementation & Fallback) -* `SESSION_SUMMARY.md` (This file - Updated) diff --git a/WORKLOG.md b/WORKLOG.md index dfed316..bc05c3e 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -14,6 +14,8 @@ - `DOCUMENTATION_PROMPT_TEMPLATE.md`: removed three references to the conventions file by name. - `.gitignore`: neutral comment above the `PROJECT_DOCUMENTATION.md` pattern. - This worklog: the first-pass entry no longer repeats the names it removed. +- Leftovers removed, confirmed by Lars: `SESSION_SUMMARY.md` (an old session note, not referenced anywhere) and the 27 coverage report files in `no.tiwas.booleantoolbox/coverage/`. `coverage/` is now in `.gitignore`, so `npm run test:coverage` output is not committed again; `.homeyignore` already kept it out of the app bundle. +- Old branches: the nine merged branches left on GitHub (six of them with tool names) were deleted by Lars. In the working environment, all worktrees and session branches were removed. ### Not changed - Commit history on `main` (trailers and merge messages with tool branch names), for the reasons given in the first pass. @@ -21,7 +23,8 @@ - The circadian source image still carries the generator's embedded content-credential metadata; only the file name changed. ### Verification -- No test run: the change touches documentation, ignore files and an image name only. `source.png` is not referenced by any manifest or code, and the Homey CLI for `npm run test:package` is not available here. +- `npx jest --runInBand`: 23 suites, 358 tests passed. +- `source.png`, `SESSION_SUMMARY.md` and `coverage/` are not referenced by any manifest, code or config. `npm run test:package` was not run (Homey CLI not available here). ## 2026-10-05 — Remove assistant names and attribution diff --git a/no.tiwas.booleantoolbox/coverage/BaseLogicDriver.js.html b/no.tiwas.booleantoolbox/coverage/BaseLogicDriver.js.html deleted file mode 100644 index c71790b..0000000 --- a/no.tiwas.booleantoolbox/coverage/BaseLogicDriver.js.html +++ /dev/null @@ -1,1516 +0,0 @@ - - - - - - Code coverage report for BaseLogicDriver.js - - - - - - - - - -
-
-

All files BaseLogicDriver.js

-
- -
- 0% - Statements - 0/178 -
- - -
- 0% - Branches - 0/107 -
- - -
- 0% - Functions - 0/19 -
- - -
- 0% - Lines - 0/174 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
"use strict";
-const Homey = require("homey");
-const Logger = require("./Logger");
- 
-async function formulaAutocompleteHelper(query, args) {
-    const device = args.device;
-    if (!device || typeof device.getFormulas !== "function") {
-        console.warn(
-            `formulaAutocompleteHelper: Invalid device or missing getFormulas in args for autocomplete.`,
-        );
-        return [];
-    }
-    try {
-        const formulas = device.getFormulas();
-        if (!Array.isArray(formulas)) {
-            console.warn(
-                `getFormulas didn't return array for ${device.getName()}.`,
-            );
-            return [];
-        }
-        const lowerQuery = query ? query.toLowerCase() : "";
-        return formulas.filter(
-            (f) =>
-                f &&
-                f.name &&
-                (!query || f.name.toLowerCase().includes(lowerQuery)),
-        );
-    } catch (e) {
-        console.error(
-            `Error in formulaAutocompleteHelper for ${device.getName()}`,
-            e,
-        );
-        return [];
-    }
-}
- 
-async function inputAutocompleteHelper(query, args) {
-    const device = args.device;
-    if (!device || typeof device.getInputOptions !== "function") {
-        console.warn(
-            `inputAutocompleteHelper: Invalid device or missing getInputOptions in args for autocomplete.`,
-        );
-        return [];
-    }
-    try {
-        const inputs = device.getInputOptions(args);
-        if (!Array.isArray(inputs)) {
-            console.warn(
-                `getInputOptions didn't return array for ${device.getName()}.`,
-            );
-            return [];
-        }
-        const lowerQuery = query ? query.toLowerCase() : "";
-        return inputs.filter(
-            (i) =>
-                i &&
-                i.name &&
-                (!query || i.name.toLowerCase().includes(lowerQuery)),
-        );
-    } catch (e) {
-        console.error(
-            `Error in inputAutocompleteHelper for ${device.getName()}`,
-            e,
-        );
-        return [];
-    }
-}
-// --- End Autocomplete Helper Functions ---
- 
-class BaseLogicDriver extends Homey.Driver {
-    static logicUnitCardsRegistered = false;
- 
-    async onInit() {
-        const driverName = `Driver: ${this.id}`;
-        this.logger = new Logger(this, driverName);
- 
-        // Set numInputs based on driver ID
-        const driverId = this.id;
-        let numInputsParsed = 2;
-        try {
-            const parts = driverId.split("-");
-            const numStr = parts[parts.length - 1];
-            const num = parseInt(numStr);
-            if (!isNaN(num) && num > 0) {
-                numInputsParsed = num;
-            } else if (driverId !== "logic-device") {
-                this.logger.warn(
-                    `Could not parse number of inputs from driver ID '${driverId}'. Using default: ${numInputsParsed}.`,
-                    {},
-                );
-            }
-        } catch (e) {
-            this.logger.error(
-                `Error parsing numInputs from driver ID '${driverId}'. Using default: ${numInputsParsed}.`,
-                e,
-            );
-        }
-        this.numInputs = numInputsParsed;
- 
-        this.logger.info("driver.ready", {
-            driverId: this.id,
-            numInputs: this.numInputs,
-        });
- 
-        if (!BaseLogicDriver.logicUnitCardsRegistered) {
-            this.logger.info(
-                "First Logic Unit driver initializing. Registering shared flow cards...",
-            );
-            await this.registerFlowCards();
-            BaseLogicDriver.logicUnitCardsRegistered = true;
-        } else {
-            this.logger.debug(
-                "Shared flow cards already registered by another Logic Unit driver.",
-            );
-        }
-    }
- 
-    async registerFlowCards() {
-        this.logger.debug("driver.registering_flow_cards");
- 
-        // --- Triggers ---
-        const triggerCards = [
-            {
-                id: "formula_changed_to_false_lu",
-            },
-            {
-                id: "formula_changed_to_true_lu",
-            },
-            {
-                id: "formula_timeout_lu",
-            },
-        ];
-        triggerCards.forEach((cardInfo) => {
-            try {
-                const card = this.homey.flow.getTriggerCard(cardInfo.id);
-                card.registerRunListener(async (args, state) => {
-                    return (
-                        args &&
-                        args.device &&
-                        args.device.driver &&
-                        args.device.driver.id &&
-                        args.device.driver.id.startsWith("logic-unit-")
-                    );
-                });
-                this.registerAutocomplete(
-                    card,
-                    "formula",
-                    formulaAutocompleteHelper,
-                );
-                this.logger.debug(
-                    ` -> OK: TRIGGER card registered: '${cardInfo.id}'`,
-                );
-            } catch (e) {
-                this.logger.error(
-                    ` -> FAILED: Registering TRIGGER card '${cardInfo.id}'`,
-                    e,
-                );
-            }
-        });
- 
-        // --- Actions ---
-        const actionCards = [
-            {
-                id: "set_input_value_lu",
-                handler: "onFlowActionSetInput",
-            },
-            {
-                id: "evaluate_formula_lu",
-                handler: "onFlowActionEvaluateFormula",
-            },
-            {
-                id: "evaluate_all_formulas_lu",
-                handler: "onFlowActionReEvaluateAll",
-            },
-            {
-                id: "clear_error_state_lu",
-                handler: "onFlowActionClearError",
-            },
-            {
-                id: "set_all_inputs_lu",
-                handler: "setAllInputsFromFlow",
-            },
-            {
-                id: "set_input_lu",
-                handler: "setInputForAllFormulasFromFlow",
-            },
-        ];
-        actionCards.forEach((cardInfo) => {
-            try {
-                const card = this.homey.flow.getActionCard(cardInfo.id);
-                card.registerRunListener(async (args, state) => {
-                    const device = args.device;
-                    if (!device)
-                        throw new Error(
-                            this.homey.__("errors.invalid_device_instance"),
-                        );
-                    const methodName = cardInfo.handler;
-                    if (typeof device[methodName] !== "function")
-                        throw new Error(
-                            this.homey.__("errors.method_missing_on_device", {
-                                methodName,
-                            }),
-                        );
-                    this.logger.flow(
-                        `Executing ACTION '${cardInfo.id}' on device ${device.getName()}`,
-                    );
-                    return await device[methodName](args, state);
-                });
- 
-                // --- Autocomplete ---
-                if (
-                    [
-                        "set_input_value_lu",
-                        "evaluate_formula_lu",
-                        "clear_error_state_lu",
-                    ].includes(cardInfo.id)
-                ) {
-                    this.registerAutocomplete(
-                        card,
-                        "formula",
-                        formulaAutocompleteHelper,
-                    );
-                }
-                if (
-                    ["set_input_value_lu", "set_input_lu"].includes(cardInfo.id)
-                ) {
-                    this.registerAutocomplete(
-                        card,
-                        "input",
-                        inputAutocompleteHelper,
-                    );
-                }
-                this.logger.debug(
-                    ` -> OK: ACTION card registered: '${cardInfo.id}'`,
-                );
-            } catch (e) {
-                this.logger.error(
-                    ` -> FAILED: Registering ACTION card '${cardInfo.id}'`,
-                    e,
-                );
-            }
-        });
- 
-        // --- Conditions ---
-        const conditionCards = [
-            {
-                id: "formula_has_timed_out_lu",
-                checkType: "timeout",
-            },
-            {
-                id: "formula_result_is_lu",
-                checkTypeFromArg: "what_is",
-            },
-            {
-                id: "has_any_error_lu",
-                checkType: "has_error",
-            },
-        ];
-        conditionCards.forEach((cardInfo) => {
-            try {
-                const card = this.homey.flow.getConditionCard(cardInfo.id);
-                card.registerRunListener(async (args, state) => {
-                    const device = args.device;
-                    if (!device) return false;
-                    if (typeof device.onFlowCondition !== "function")
-                        return false;
- 
-                    let checkType;
-                    if (cardInfo.checkType) {
-                        checkType = cardInfo.checkType;
-                    } else if (cardInfo.checkTypeFromArg) {
-                        checkType = args[cardInfo.checkTypeFromArg] === "true";
-                    }
- 
-                    this.logger.flow(
-                        `Executing CONDITION '${cardInfo.id}' on device ${device.getName()}`,
-                    );
-                    return await device.onFlowCondition(args, state, checkType);
-                });
- 
-                if (
-                    [
-                        "formula_has_timed_out_lu",
-                        "formula_result_is_lu",
-                    ].includes(cardInfo.id)
-                ) {
-                    this.registerAutocomplete(
-                        card,
-                        "formula",
-                        formulaAutocompleteHelper,
-                    );
-                }
-                this.logger.debug(
-                    ` -> OK: CONDITION card registered: '${cardInfo.id}'`,
-                );
-            } catch (e) {
-                this.logger.error(
-                    ` -> FAILED: Registering CONDITION card '${cardInfo.id}'`,
-                    e,
-                );
-            }
-        });
-    }
- 
-    // --- Helper for Autocomplete Registration ---
-    registerAutocomplete(card, argName, helperFn) {
-        try {
-            if (typeof helperFn !== "function") {
-                throw new Error(
-                    this.homey.__("errors.helper_not_function", { argName }),
-                );
-            }
-            card.registerArgumentAutocompleteListener(
-                argName,
-                async (query, args) => {
-                    const device = args?.device;
- 
-                    if (argName === "formula" || argName === "input") {
-                        if (!device) {
-                            this.logger.warn(
-                                `Autocomplete for device arg '${argName}' on card '${card.id}' called without device context.`,
-                            );
-                            return [];
-                        }
- 
-                        let requiredMethod = "";
-                        if (argName === "formula")
-                            requiredMethod = "getFormulas";
-                        if (argName === "input")
-                            requiredMethod = "getInputOptions";
- 
-                        if (typeof device[requiredMethod] !== "function") {
-                            this.logger.warn(
-                                `Autocomplete for device arg '${argName}' on card '${card.id}': Device ${device.getName()} missing method ${requiredMethod}.`,
-                            );
-                            return [];
-                        }
-                    }
- 
-                    try {
-                        return await helperFn(query, args);
-                    } catch (autocompleteError) {
-                        this.logger.error(
-                            `Error during autocomplete for ${argName} on card '${card.id}'`,
-                            autocompleteError,
-                        );
-                        return [];
-                    }
-                },
-            );
-            this.logger.debug(
-                `Registered ${argName.toUpperCase()} autocomplete for card '${card.id}'`,
-            );
-        } catch (e) {
-            this.logger.error(
-                ` -> FAILED to register autocomplete for '${argName}' on card '${card.id}'`,
-                e,
-            );
-        }
-    }
- 
-    async ensureUniqueDeviceName(name) {
-        try {
-            if (
-                !this.homey.app ||
-                !this.homey.app.api ||
-                !this.homey.app.api.devices ||
-                typeof this.homey.app.api.devices.getDevices !== "function"
-            ) {
-                this.logger.warn(
-                    "ensureUniqueDeviceName: Homey API not ready, returning original name.",
-                    {},
-                );
-                return String(name || "");
-            }
-            const all = await this.homey.app.api.devices.getDevices();
-            if (!all || typeof all !== "object") {
-                this.logger.error(
-                    "ensureUniqueDeviceName: Unexpected response from getDevices:",
-                    all,
-                );
-                return String(name || "");
-            }
-            const existing = new Set(
-                Object.values(all)
-                    .map((d) =>
-                        d && typeof d === "object"
-                            ? String(d.name || "").trim()
-                            : "",
-                    )
-                    .filter(Boolean),
-            );
-            const nameStr = String(name || "");
-            if (!existing.has(nameStr)) return nameStr;
-            const base = nameStr.replace(/\s+\d+$/, "").trim();
-            const m = nameStr.match(/\s+(\d+)$/);
-            let n = m ? parseInt(m[1], 10) + 1 : 2;
-            if (isNaN(n)) n = 2;
-            let candidate = `${base} ${n}`;
-            let safetyCounter = 0;
-            while (existing.has(candidate) && safetyCounter < 100) {
-                n++;
-                candidate = `${base} ${n}`;
-                safetyCounter++;
-            }
-            if (safetyCounter >= 100) {
-                this.logger.error(
-                    "ensureUniqueDeviceName: Could not find unique name after 100 attempts, returning original + timestamp.",
-                    {},
-                );
-                return `${nameStr}-${Date.now()}`;
-            }
-            return candidate;
-        } catch (e) {
-            this.logger.error("driver.ensure_unique_failed", e);
-            return String(name || "");
-        }
-    }
- 
-    async onPair(session) {
-        this.logger.info("pair.session_started", {});
-        session.setHandler("list_devices", async () => {
-            try {
-                this.logger.debug("Pair handler: list_devices called", {});
-                let localeString;
-                try {
-                    localeString = this.homey.__("device.logic_unit_name");
-                } catch (e) {
-                    this.logger.error(
-                        "Error getting localization for 'device.logic_unit_name'",
-                        e,
-                    );
-                    localeString = this.homey.__(
-                        "device.logic_unit_name_fallback",
-                    );
-                }
-                const baseName = String(localeString || "").replace(
-                    "{count}",
-                    this.numInputs,
-                );
-                const name = await this.ensureUniqueDeviceName(baseName);
-                const defaultFormulaName = this.homey.__(
-                    "pair.default_formula_name",
-                );
-                const deviceData = {
-                    name: name,
-                    data: {
-                        id: `${this.id}-${Math.random().toString(16).slice(2)}`,
-                        numInputs: this.numInputs,
-                    },
-                    settings: {
-                        formulas: `[{"id":"f1","name":"${defaultFormulaName}","expression":"${this.getDefaultExpression()}","enabled":true,"timeout":0,"firstImpression":false}]`,
-                    },
-                };
-                this.logger.debug(
-                    "Pair handler: list_devices returning:",
-                    deviceData,
-                );
-                return [deviceData];
-            } catch (e) {
-                this.logger.error("Error in list_devices pair handler", e);
-                throw e;
-            }
-        });
-        this.logger.debug("Pair handler: list_devices registered", {});
-    }
- 
-    getDefaultExpression() {
-        const letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"];
-        const inputsToUse = Math.min(this.numInputs, letters.length);
-        if (inputsToUse <= 0) return "true";
-        return letters.slice(0, inputsToUse).join(" AND ");
-    }
-}
- 
-module.exports = BaseLogicDriver;
-module.exports.formulaAutocompleteHelper = formulaAutocompleteHelper;
-module.exports.inputAutocompleteHelper = inputAutocompleteHelper;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/BaseLogicUnit.js.html b/no.tiwas.booleantoolbox/coverage/BaseLogicUnit.js.html deleted file mode 100644 index 6df15c2..0000000 --- a/no.tiwas.booleantoolbox/coverage/BaseLogicUnit.js.html +++ /dev/null @@ -1,3226 +0,0 @@ - - - - - - Code coverage report for BaseLogicUnit.js - - - - - - - - - -
-
-

All files BaseLogicUnit.js

-
- -
- 0% - Statements - 0/459 -
- - -
- 0% - Branches - 0/283 -
- - -
- 0% - Functions - 0/67 -
- - -
- 0% - Lines - 0/438 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478 -479 -480 -481 -482 -483 -484 -485 -486 -487 -488 -489 -490 -491 -492 -493 -494 -495 -496 -497 -498 -499 -500 -501 -502 -503 -504 -505 -506 -507 -508 -509 -510 -511 -512 -513 -514 -515 -516 -517 -518 -519 -520 -521 -522 -523 -524 -525 -526 -527 -528 -529 -530 -531 -532 -533 -534 -535 -536 -537 -538 -539 -540 -541 -542 -543 -544 -545 -546 -547 -548 -549 -550 -551 -552 -553 -554 -555 -556 -557 -558 -559 -560 -561 -562 -563 -564 -565 -566 -567 -568 -569 -570 -571 -572 -573 -574 -575 -576 -577 -578 -579 -580 -581 -582 -583 -584 -585 -586 -587 -588 -589 -590 -591 -592 -593 -594 -595 -596 -597 -598 -599 -600 -601 -602 -603 -604 -605 -606 -607 -608 -609 -610 -611 -612 -613 -614 -615 -616 -617 -618 -619 -620 -621 -622 -623 -624 -625 -626 -627 -628 -629 -630 -631 -632 -633 -634 -635 -636 -637 -638 -639 -640 -641 -642 -643 -644 -645 -646 -647 -648 -649 -650 -651 -652 -653 -654 -655 -656 -657 -658 -659 -660 -661 -662 -663 -664 -665 -666 -667 -668 -669 -670 -671 -672 -673 -674 -675 -676 -677 -678 -679 -680 -681 -682 -683 -684 -685 -686 -687 -688 -689 -690 -691 -692 -693 -694 -695 -696 -697 -698 -699 -700 -701 -702 -703 -704 -705 -706 -707 -708 -709 -710 -711 -712 -713 -714 -715 -716 -717 -718 -719 -720 -721 -722 -723 -724 -725 -726 -727 -728 -729 -730 -731 -732 -733 -734 -735 -736 -737 -738 -739 -740 -741 -742 -743 -744 -745 -746 -747 -748 -749 -750 -751 -752 -753 -754 -755 -756 -757 -758 -759 -760 -761 -762 -763 -764 -765 -766 -767 -768 -769 -770 -771 -772 -773 -774 -775 -776 -777 -778 -779 -780 -781 -782 -783 -784 -785 -786 -787 -788 -789 -790 -791 -792 -793 -794 -795 -796 -797 -798 -799 -800 -801 -802 -803 -804 -805 -806 -807 -808 -809 -810 -811 -812 -813 -814 -815 -816 -817 -818 -819 -820 -821 -822 -823 -824 -825 -826 -827 -828 -829 -830 -831 -832 -833 -834 -835 -836 -837 -838 -839 -840 -841 -842 -843 -844 -845 -846 -847 -848 -849 -850 -851 -852 -853 -854 -855 -856 -857 -858 -859 -860 -861 -862 -863 -864 -865 -866 -867 -868 -869 -870 -871 -872 -873 -874 -875 -876 -877 -878 -879 -880 -881 -882 -883 -884 -885 -886 -887 -888 -889 -890 -891 -892 -893 -894 -895 -896 -897 -898 -899 -900 -901 -902 -903 -904 -905 -906 -907 -908 -909 -910 -911 -912 -913 -914 -915 -916 -917 -918 -919 -920 -921 -922 -923 -924 -925 -926 -927 -928 -929 -930 -931 -932 -933 -934 -935 -936 -937 -938 -939 -940 -941 -942 -943 -944 -945 -946 -947 -948 -949 -950 -951 -952 -953 -954 -955 -956 -957 -958 -959 -960 -961 -962 -963 -964 -965 -966 -967 -968 -969 -970 -971 -972 -973 -974 -975 -976 -977 -978 -979 -980 -981 -982 -983 -984 -985 -986 -987 -988 -989 -990 -991 -992 -993 -994 -995 -996 -997 -998 -999 -1000 -1001 -1002 -1003 -1004 -1005 -1006 -1007 -1008 -1009 -1010 -1011 -1012 -1013 -1014 -1015 -1016 -1017 -1018 -1019 -1020 -1021 -1022 -1023 -1024 -1025 -1026 -1027 -1028 -1029 -1030 -1031 -1032 -1033 -1034 -1035 -1036 -1037 -1038 -1039 -1040 -1041 -1042 -1043 -1044 -1045 -1046 -1047 -1048  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
"use strict";
-const Homey = require("homey");
-const Logger = require("./Logger");
- 
-module.exports = class BaseLogicUnit extends Homey.Device {
-  async safeSetCapabilityValue(cap, value) {
-    if (this._isDeleting) return;
-    try {
-      if (!this.hasCapability(cap)) {
-        return;
-      }
-      await this.setCapabilityValue(cap, value);
-    } catch (e) {
-      const msg = e?.message || String(e);
-      if (e?.statusCode === 404 || /not\s*found/i.test(msg)) {
-        this.logger.debug("device.capability_skip_deleted", {
-          capability: cap,
-        });
-      } else {
-        this.logger.error("device.capability_update_failed", {
-          capability: cap,
-          message: msg,
-        });
-      }
-    }
-  }
- 
-  async onInit() {
-    const driverName = `Device: ${this.driver ? this.driver.id : "unknown-driver"}`;
-    this.logger = new Logger(this, driverName);
-    this.logger.device("device.initializing", {
-      name: this.getName(),
-    });
- 
-    if (!this.hasCapability("onoff")) {
-      await this.addCapability("onoff").catch((e) =>
-        this.logger.error("Failed to add 'onoff' capability", e),
-      );
-    }
-    if (!this.hasCapability("alarm_generic")) {
-      await this.addCapability("alarm_generic").catch((e) =>
-        this.logger.error("Failed to add 'alarm_generic' capability", e),
-      );
-    }
- 
-    this.numInputs = this.getData().numInputs ?? 2;
-    this.availableInputs = this.getAvailableInputIds();
-    this.logger.debug(
-      `Device initialized with ${this.numInputs} inputs: ${this.availableInputs.join(", ")}`,
-      {},
-    );
- 
-    await this.initializeFormulas();
-    await this.evaluateAllFormulasInitial();
-    this.startTimeoutChecks();
- 
-    // REGISTRATION IS REMOVED FROM HERE
-    this.logger.info("device.initialized", {
-      name: this.getName(),
-      count: this.numInputs,
-    });
-  }
- 
-  async onFlowCondition(args, state, checkType) {
-    this.logger.flow(`onFlowCondition called for checkType: '${checkType}'`, {
-      formulaId: args.formula?.id,
-    });
- 
-    if (checkType === "has_error") {
-      const hasTimeout = this.formulas.some((f) => f.timedOut === true);
-      this.logger.flow(`Condition 'has_any_error' check result: ${hasTimeout}`);
-      return hasTimeout;
-    }
- 
-    const cardIdFromArgs = args.cardId;
-    if (
-      (cardIdFromArgs === "formula_result_is_ld" ||
-        cardIdFromArgs === "formula_has_timed_out_ld") &&
-      !args.formula
-    ) {
-      this.logger.warn(
-        "onFlowCondition: _ld card called without formula on LU device. Checking first formula as fallback.",
-      );
-      if (this.formulas.length > 0) args.formula = this.formulas[0];
-      else return false;
-    }
- 
-    if (!args.formula || !args.formula.id) {
-      this.logger.warn(
-        "onFlowCondition: No formula specified in args for formula-specific check.",
-        {
-          checkType,
-        },
-      );
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const formula = this.formulas.find((f) => f.id === formulaId);
- 
-    if (!formula) {
-      this.logger.warn("onFlowCondition: Invalid formula ID received.", {
-        formulaId,
-      });
-      return false;
-    }
- 
-    if (checkType === "timeout") {
-      const isTimedOut = formula.timedOut === true;
-      this.logger.flow(
-        `Condition 'formula_has_timed_out' check for '${formula.name}': Result=${isTimedOut}`,
-      );
-      return isTimedOut;
-    } else if (typeof checkType === "boolean") {
-      const currentResult = formula.result;
-      const desiredResult = checkType;
-      const evaluatedResult = currentResult === true;
-      const conditionMet = evaluatedResult === desiredResult;
-      this.logger.flow(
-        `Condition 'formula_result_is' check for '${formula.name}': Current=${currentResult}, Desired=${desiredResult}, Match=${conditionMet}`,
-      );
-      return conditionMet;
-    } else {
-      this.logger.error(
-        `onFlowCondition: Unknown checkType received: '${checkType}'`,
-      );
-      return false;
-    }
-  }
- 
-  async onFlowActionSetInput(args, state) {
-    if (!args.formula || !args.input || !args.formula.id || !args.input.id) {
-      this.logger.warn("onFlowActionSetInput: Missing formula or input ID.", {
-        args,
-      });
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const inputId = args.input.id;
-    const value = args.value === "true";
-    this.logger.flow("onFlowActionSetInput: Setting input", {
-      formula: formulaId,
-      input: inputId,
-      value: value,
-    });
-    try {
-      await this.setInputForFormula(formulaId, inputId, value);
-      return true;
-    } catch (e) {
-      this.logger.error(
-        `Error during onFlowActionSetInput for ${formulaId}/${inputId}`,
-        e,
-      );
-      return false;
-    }
-  }
- 
-  async onFlowActionEvaluateFormula(args, state) {
-    const cardIdFromArgs = args.cardId;
-    if (
-      (!args.formula || !args.formula.id) &&
-      cardIdFromArgs === "evaluate_formula_ld"
-    ) {
-      this.logger.warn(
-        `onFlowActionEvaluateFormula (LD) called on LU device ${this.getName()}. Falling back to re-evaluate all.`,
-      );
-      return this.onFlowActionReEvaluateAll(args, state);
-    }
-    if (!args.formula || !args.formula.id) {
-      this.logger.warn("onFlowActionEvaluateFormula: Missing formula ID.", {
-        args,
-      });
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("onFlowActionEvaluateFormula: Invalid formula ID.", {
-        formulaId,
-      });
-      return false;
-    }
-    this.logger.flow(
-      `onFlowActionEvaluateFormula: Evaluating '${formula.name}' (Resetting locks)`,
-    );
-    try {
-      await this.evaluateFormula(formulaId, true);
-      return true;
-    } catch (e) {
-      this.logger.error(
-        `Error during onFlowActionEvaluateFormula for ${formulaId}`,
-        e,
-      );
-      return false;
-    }
-  }
- 
-  async onFlowActionClearError(args, state) {
-    const cardIdFromArgs = args.cardId;
-    if (
-      (!args.formula || !args.formula.id) &&
-      cardIdFromArgs === "clear_error_ld"
-    ) {
-      this.logger.warn(
-        `onFlowActionClearError (LD) called on LU device ${this.getName()}. Clearing all formula timeouts.`,
-      );
-      this.formulas.forEach((f) => {
-        f.timedOut = false;
-      });
-      this.logger.info("notifications.error_cleared_all", {});
-      return true;
-    }
-    if (!args.formula || !args.formula.id) {
-      this.logger.warn("onFlowActionClearError: Missing formula ID.", {
-        args,
-      });
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("onFlowActionClearError: Invalid formula ID.", {
-        formulaId,
-      });
-      return false;
-    }
-    this.logger.flow(
-      `onFlowActionClearError: Clearing timeout state for '${formula.name}'`,
-    );
-    formula.timedOut = false;
-    this.logger.info("notifications.error_cleared", {
-      formulaName: formula.name,
-    });
-    return true;
-  }
- 
-  async onFlowActionReEvaluateAll(args, state) {
-    this.logger.flow(
-      "onFlowActionReEvaluateAll: Re-evaluating all formulas",
-      {},
-    );
-    try {
-      await this.evaluateAllFormulas();
-      return true;
-    } catch (e) {
-      this.logger.error("Error during onFlowActionReEvaluateAll", e);
-      return false;
-    }
-  }
- 
-  async setAllInputsFromFlow(args, state) {
-    const device = this;
-    const valuesJson = args.values;
-    this.logger.flow(
-      `Executing Action 'set_all_inputs' on device '${device.getName()}'`,
-    );
-    if (typeof valuesJson !== "string" || valuesJson.trim() === "") {
-      this.logger.warn(
-        `RunListener set_all_inputs: Empty or invalid JSON provided.`,
-      );
-      return false;
-    }
-    try {
-      const values = JSON.parse(valuesJson);
-      if (typeof values !== "object" || values === null) {
-        throw new Error("Parsed JSON is not an object.");
-      }
-      const promises = [];
-      const inputs = device.getAvailableInputIds
-        ? device.getAvailableInputIds()
-        : [];
-      const formulas = Array.isArray(device.formulas) ? device.formulas : [];
- 
-      for (const key in values) {
-        if (!inputs.includes(key.toLowerCase())) {
-          this.logger.warn(
-            `setAllInputsFromFlow: JSON contains invalid input key '${key}' for this device.`,
-          );
-        }
-      }
- 
-      for (const inputId of inputs) {
-        const key = inputId.toUpperCase();
-        if (values.hasOwnProperty(key)) {
-          formulas.forEach((f) => {
-            if (
-              f &&
-              f.enabled &&
-              typeof device.setInputForFormula === "function"
-            ) {
-              const boolValue =
-                values[key] === true ||
-                String(values[key]).toLowerCase() === "true";
-              promises.push(
-                device.setInputForFormula(f.id, inputId, boolValue),
-              );
-            }
-          });
-        }
-      }
-      await Promise.all(promises);
-      this.logger.info(`set_all_inputs OK for ${device.getName()}`, {});
-      return true;
-    } catch (e) {
-      this.logger.error(`Error set_all_inputs for ${device.getName()}`, e);
-      return false;
-    }
-  }
- 
-  async setInputForAllFormulasFromFlow(args, state) {
-    const device = this;
-    const inputId = args.input?.id;
-    const value = args.value === "true";
- 
-    this.logger.flow(
-      `Executing Action 'set_input' on device '${device.getName()}'`,
-      {
-        input: inputId,
-        value,
-      },
-    );
- 
-    if (!inputId || !this.availableInputs.includes(inputId)) {
-      this.logger.error(
-        `RunListener set_input: Invalid or missing input ID: ${inputId}. Available: ${this.availableInputs.join(",")}`,
-      );
- 
-      return false;
-    }
- 
-    const promises = [];
-    const formulas = Array.isArray(device.formulas) ? device.formulas : [];
-    try {
-      formulas.forEach((f) => {
-        if (f && f.enabled && typeof device.setInputForFormula === "function") {
-          promises.push(device.setInputForFormula(f.id, inputId, value));
-        }
-      });
-      await Promise.all(promises);
-      this.logger.info(`set_input OK for ${device.getName()}`, {});
-      return true;
-    } catch (e) {
-      this.logger.error(`Error set_input for ${device.getName()}`, e);
-      return false;
-    }
-  }
- 
-  getAvailableInputIds() {
-    const allInputs = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
-    const count = Math.max(
-      0,
-      Math.min(allInputs.length, Number(this.numInputs) || 0),
-    );
-    return allInputs.slice(0, count);
-  }
- 
-  getAvailableInputsUppercase() {
-    return this.availableInputs.map((i) => i.toUpperCase());
-  }
- 
-  initializeFormulas() {
-    this.logger.debug("Initializing formulas from settings...", {});
-    const settings = this.getSettings();
-    try {
-      const formulasData = settings.formulas
-        ? JSON.parse(settings.formulas)
-        : [];
-      if (!Array.isArray(formulasData)) {
-        this.logger.error("errors.invalid_formula", {
-          message: "Formulas setting is not an array.",
-        });
-        this.formulas = [];
-      } else {
-        this.formulas = formulasData.map((f) => ({
-          id: f.id || `formula_${Math.random().toString(16).slice(2)}`,
-          name: f.name || this.homey.__("formula.unnamed_formula"),
-          expression: f.expression || "",
-          enabled: f.enabled !== false,
-          timeout: Number(f.timeout) || 0,
-          firstImpression: !!f.firstImpression,
-          inputStates: {},
-          lockedInputs: {},
-          lastInputTime: null,
-          result: null,
-          timedOut: false,
-        }));
-      }
- 
-      this.formulas.forEach((formula) => {
-        this.availableInputs.forEach((id) => {
-          formula.inputStates[id] = "undefined";
-          formula.lockedInputs[id] = false;
-        });
-      });
-    } catch (e) {
-      this.logger.error("errors.invalid_formula", e);
-      this.formulas = [];
-    }
- 
-    if (this.formulas.length === 0) {
-      this.logger.warn(
-        "No valid formulas found in settings, creating default.",
-        {},
-      );
-      const defaultFormula = {
-        id: "formula_1",
-        name: this.homey.__
-          ? this.homey.__("formula.default_name_alt")
-          : this.homey.__("formula.default_name_alt_fallback"),
-        expression: this.getDefaultExpression(),
-        enabled: true,
-        timeout: 0,
-        firstImpression: false,
-        inputStates: {},
-        lockedInputs: {},
-        lastInputTime: null,
-        result: null,
-        timedOut: false,
-      };
- 
-      this.availableInputs.forEach((id) => {
-        defaultFormula.inputStates[id] = "undefined";
-        defaultFormula.lockedInputs[id] = false;
-      });
-      this.formulas = [defaultFormula];
-    }
-    this.logger.info("formula.initialized", {
-      count: this.formulas.length,
-    });
-    this.formulas.forEach((f) =>
-      this.logger.debug("formula.details", {
-        name: f.name,
-        expression: f.expression,
-        enabled: f.enabled,
-      }),
-    );
-  }
- 
-  getDefaultExpression() {
-    const inputs = this.getAvailableInputsUppercase();
-    return inputs.length > 0 ? inputs.join(" AND ") : "true";
-  }
- 
-  getFormulas() {
-    return this.formulas
-      .filter((f) => f.enabled)
-      .map((f) => ({
-        id: f.id,
-        name: f.name,
-        description:
-          f.expression ||
-          (this.homey.__
-            ? this.homey.__("formula.no_expression")
-            : "(no expression)"),
-      }));
-  }
- 
-  getInputOptions() {
-    return this.getAvailableInputsUppercase().map((input) => ({
-      id: input.toLowerCase(),
-      name: input,
-    }));
-  }
- 
-  validateExpression(expression) {
-    this.logger.debug(`Validating expression: "${expression || ""}"`, {});
-    if (
-      !expression ||
-      typeof expression !== "string" ||
-      expression.trim() === ""
-    ) {
-      return {
-        valid: false,
-        error: this.homey.__
-          ? this.homey.__("formula.expression_empty")
-          : "Expression empty",
-      };
-    }
-    const upper = expression.toUpperCase();
-    const inputs = this.getAvailableInputsUppercase();
-    if (!inputs.length && upper.match(/[A-J]/)) {
-      return {
-        valid: false,
-        error: this.homey.__("formula.error_inputs_used_but_none_configured"),
-      };
-    }
-    let validationInputs = inputs.length > 0 ? inputs : ["TEMP_VALIDATION_VAR"];
- 
-    const tokenRe = new RegExp(
-      `\\b(?:AND|OR|XOR|NOT)\\b|&&|\\|\\||\\^|!=|\\*|\\+|!|\\(|\\)|\\b(?:${validationInputs.join("|")})\\b|\\bTRUE\\b|\\bFALSE\\b`,
-      "gi",
-    );
-    const stripped = upper.replace(tokenRe, "").replace(/\s+/g, "");
-    if (stripped.length > 0) {
-      return {
-        valid: false,
-        error: this.homey.__
-          ? this.homey.__("formula.invalid_tokens", {
-              tokens: stripped,
-            })
-          : `Invalid tokens: ${stripped}`,
-      };
-    }
- 
-    let depth = 0;
-    for (const ch of upper) {
-      if (ch === "(") depth++;
-      else if (ch === ")") depth--;
-      if (depth < 0)
-        return {
-          valid: false,
-          error: this.homey.__("formula.error_unbalanced_parentheses"),
-        };
-    }
-    if (depth !== 0)
-      return {
-        valid: false,
-        error: "Unbalanced parentheses",
-      };
- 
-    // Attempt to evaluate with dummy values to catch syntax errors
-    let testExpr = upper
-      .replace(/\bAND\b|&|\*/gi, "&&") // Normalize AND operators
-      .replace(/\bOR\b|\||\+/gi, "||") // Normalize OR operators
-      .replace(/\bXOR\b|\^|!=/gi, "!=") // Normalize XOR operators (use JS !=)
-      .replace(/\bNOT\b|!/gi, "!"); // Normalize NOT operator
- 
-    // Replace input letters and TRUE/FALSE with boolean literals
-    for (const key of validationInputs) {
-      const re = new RegExp(`\\b${key}\\b`, "g");
-      testExpr = testExpr.replace(re, "true");
-    }
-    testExpr = testExpr
-      .replace(/\bTRUE\b/g, "true")
-      .replace(/\bFALSE\b/g, "false");
- 
-    // Try creating a function to catch final syntax errors
-    try {
-      const fn = new Function(`return ${testExpr};`);
-      fn(); // Execute once to be sure
-      return {
-        valid: true,
-      };
-    } catch (e) {
-      return {
-        valid: false,
-        error: this.homey.__
-          ? this.homey.__("formula.invalid_syntax", {
-              message: e.message,
-            })
-          : `Syntax error: ${e.message}`,
-      };
-    }
-  }
- 
-  parseExpression(expression) {
-    if (!expression || typeof expression !== "string") return [];
-    const inputs = this.getAvailableInputsUppercase();
-    if (!inputs.length) return [];
-    const varRe = new RegExp(`\\b(${inputs.join("|")})\\b`, "gi");
-    const matches = expression.match(varRe);
-    return matches ? [...new Set(matches.map((c) => c.toUpperCase()))] : [];
-  }
- 
-  async setInputForFormula(formulaId, inputId, value) {
-    if (this._isDeleting) return null;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("errors.invalid_formula", {
-        formulaId,
-      });
-      return null;
-    }
-    if (!this.availableInputs.includes(inputId)) {
-      this.logger.warn(
-        `setInputForFormula: Invalid inputId '${inputId}' for device with ${this.numInputs} inputs.`,
-      );
-      return null;
-    }
- 
-    if (
-      formula.firstImpression === true &&
-      formula.lockedInputs[inputId] === true
-    ) {
-      this.logger.debug("inputs.locked", {
-        input: inputId.toUpperCase(),
-        formula: formula.name,
-      });
-      return formula.result;
-    }
- 
-    const oldValue = formula.inputStates[inputId];
-    formula.inputStates[inputId] =
-      value === true || value === false ? value : "undefined";
-    formula.timedOut = false;
- 
-    if (
-      formula.firstImpression === true &&
-      formula.inputStates[inputId] !== "undefined" &&
-      formula.lockedInputs[inputId] !== true
-    ) {
-      formula.lockedInputs[inputId] = true;
-      this.logger.debug("inputs.locked_at_value", {
-        input: inputId.toUpperCase(),
-        value: formula.inputStates[inputId],
-        formula: formula.name,
-      });
-    }
- 
-    if (formula.inputStates[inputId] !== "undefined") {
-      formula.lastInputTime = Date.now();
-    }
- 
-    return await this.evaluateFormula(formulaId, false);
-  }
- 
-  async evaluateFormula(formulaId, resetLocks = false) {
-    if (this._isDeleting) return null;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula || !formula.enabled) {
-      this.logger.debug("errors.invalid_formula", {
-        formulaId,
-      });
-      return null;
-    }
- 
-    if (resetLocks === true && formula.firstImpression === true) {
-      this.availableInputs.forEach((id) => {
-        formula.lockedInputs[id] = false;
-      });
-      this.logger.debug("inputs.unlocked", {
-        formula: formula.name,
-      });
-    }
- 
-    const expression = formula.expression;
-    if (!expression) {
-      this.logger.debug("formula.invalid", {
-        formula: formula.name,
-        reason: "No expression",
-      });
-      formula.result = null;
-      return null;
-    }
- 
-    const requiredInputs = this.parseExpression(expression);
-    const allDefined = requiredInputs.every(
-      (inputIdUpper) =>
-        formula.inputStates[inputIdUpper.toLowerCase()] !== "undefined",
-    );
- 
-    if (!allDefined && requiredInputs.length > 0) {
-      const missing = requiredInputs.filter(
-        (id) => formula.inputStates[id.toLowerCase()] === "undefined",
-      );
-      this.logger.debug("inputs.waiting", {
-        formula: formula.name,
-        missing: missing.join(", "),
-      });
-      return formula.result;
-    }
- 
-    const values = {};
-    this.availableInputs.forEach((id) => {
-      values[id.toUpperCase()] = formula.inputStates[id];
-    });
-    this.logger.debug("formula.evaluating", {
-      formula: formula.name,
-    });
- 
-    let evalExpression = expression
-      .replace(/\bAND\b|&|\*/gi, "&&")
-      .replace(/\bOR\b|\||\+/gi, "||")
-      .replace(/\bXOR\b|\^|!=/gi, "!=")
-      .replace(/\bNOT\b|!/gi, "!");
- 
-    for (const key in values) {
-      if (requiredInputs.includes(key) && values[key] !== "undefined") {
-        const re = new RegExp(`\\b${key}\\b`, "gi");
-        evalExpression = evalExpression.replace(
-          re,
-          String(values[key] === true),
-        );
-      }
-    }
-    evalExpression = evalExpression
-      .replace(/\bTRUE\b/gi, "true")
-      .replace(/\bFALSE\b/gi, "false");
-    this.logger.formula("formula.evaluating_expression", {
-      expression: expression,
-      evalExpression: evalExpression,
-    });
- 
-    try {
-      const evaluateFn = new Function(`return ${evalExpression};`);
-      const result = !!evaluateFn();
-      this.logger.debug("formula.evaluated", {
-        formula: formula.name,
-        result: result,
-      });
- 
-      const previous = formula.result;
-      formula.result = result;
-      formula.timedOut = false;
- 
-      const overallDeviceState = this.formulas.some(
-        (f) => f.enabled && f.result === true,
-      );
-      const currentOnOffState = this.getCapabilityValue("onoff");
- 
-      if (currentOnOffState !== overallDeviceState) {
-        await this.safeSetCapabilityValue("onoff", overallDeviceState);
-        await this.safeSetCapabilityValue("alarm_generic", overallDeviceState);
-        this.logger.info(
-          `Overall device capability updated: ${overallDeviceState}`,
-        );
-      }
- 
-      if (result !== previous && previous !== null) {
-        const triggerData = {
-          formula: {
-            id: formula.id,
-            name: formula.name,
-          },
-        };
-        const state = {
-          formulaId: formula.id,
-        };
-        const triggerCardId = result
-          ? "formula_changed_to_true"
-          : "formula_changed_to_false";
-        try {
-          this.logger.flow(
-            `Triggering flow '${triggerCardId}' for '${formula.name}'`,
-          );
-          const card = this.homey.flow.getDeviceTriggerCard(triggerCardId);
-          await card.trigger(this, triggerData, state);
-        } catch (e) {
-          if (e.message && e.message.includes("Invalid Flow Card ID")) {
-            this.logger.error(
-              `FATAL: Trigger card '${triggerCardId}' not found. Check app.json/compose flow definitions.`,
-              e,
-            );
-          } else {
-            this.logger.error("flow.trigger_error", e);
-          }
-        }
-      }
-      return result;
-    } catch (e) {
-      this.logger.error("errors.evaluation_failed", e);
-      formula.result = null;
-      return null;
-    }
-  }
- 
-  async evaluateAllFormulas() {
-    this.logger.info("notifications.reevaluating", {});
-    const results = [];
-    for (const formula of this.formulas) {
-      if (formula.enabled) {
-        if (formula.firstImpression) {
-          this.availableInputs.forEach((id) => {
-            formula.lockedInputs[id] = false;
-          });
-          this.logger.debug("inputs.unlocked", {
-            formula: formula.name,
-          });
-        }
-        const result = await this.evaluateFormula(formula.id, false);
-        results.push({
-          id: formula.id,
-          name: formula.name,
-          result,
-        });
-      } else {
-        formula.result = null;
-      }
-    }
-    const overallDeviceState = this.formulas.some(
-      (f) => f.enabled && f.result === true,
-    );
-    if (this.getCapabilityValue("onoff") !== overallDeviceState) {
-      await this.safeSetCapabilityValue("onoff", overallDeviceState);
-      await this.safeSetCapabilityValue("alarm_generic", overallDeviceState);
-      this.logger.info(
-        `Overall device capability updated after re-evaluating all: ${overallDeviceState}`,
-      );
-    }
-    this.logger.debug("formula.evaluated_count", {
-      count: results.length,
-    });
-    return results;
-  }
- 
-  async evaluateAllFormulasInitial() {
-    this.logger.info("evaluation.initial_complete", {});
-    let anyEvaluated = false;
- 
-    for (const formula of this.formulas) {
-      if (!formula.enabled) {
-        formula.result = null;
-        continue;
-      }
-      const expr = formula.expression;
-      if (!expr) {
-        formula.result = null;
-        continue;
-      }
-      const required = this.parseExpression(expr);
-      this.logger.debug("debug.checking_formula", {
-        name: formula.name,
-        expression: expr,
-      });
-      if (required.length > 0) {
-        this.logger.debug("debug.required_inputs", {
-          inputs: required.join(", "),
-        });
-      }
- 
-      const allDefined = required.every(
-        (id) => formula.inputStates[id.toLowerCase()] !== "undefined",
-      );
- 
-      if (allDefined || required.length === 0) {
-        this.logger.debug("formula.all_inputs_defined", {
-          name: formula.name,
-        });
-        const result = await this.evaluateFormula(formula.id);
-        if (result !== null) {
-          anyEvaluated = true;
-        }
-      } else {
-        const missing = required.filter(
-          (id) => formula.inputStates[id.toLowerCase()] === "undefined",
-        );
-        this.logger.debug("inputs.waiting", {
-          formula: formula.name,
-          missing: missing.join(", "),
-        });
-        formula.result = null;
-      }
-    }
- 
-    const finalState = this.formulas.some(
-      (f) => f.enabled && f.result === true,
-    );
- 
-    this.logger.info(
-      `Initial evaluation complete. Final state: ${finalState} (anyEvaluated=${anyEvaluated})`,
-    );
- 
-    await this.safeSetCapabilityValue("onoff", finalState);
-    await this.safeSetCapabilityValue("alarm_generic", finalState);
- 
-    if (
-      !anyEvaluated &&
-      this.formulas.some(
-        (f) =>
-          f.enabled &&
-          f.expression &&
-          this.parseExpression(f.expression).length > 0,
-      )
-    ) {
-      this.logger.warn("evaluation.no_formulas_ready", {});
-    }
-  }
- 
-  getFormulaResult(formulaId) {
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("errors.invalid_formula", {
-        formulaId,
-      });
-      return null;
-    }
-    this.logger.debug("formula.result_debug", {
-      name: formula.name,
-      id: formulaId,
-      result: formula.result,
-      type: typeof formula.result,
-    });
-    return formula.result === true;
-  }
- 
-  hasFormulaTimedOut(formulaId) {
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    return !!(formula && formula.timedOut);
-  }
- 
-  startTimeoutChecks() {
-    if (this.timeoutInterval) {
-      clearInterval(this.timeoutInterval);
-    }
-    this.timeoutInterval = setInterval(() => {
-      this.checkTimeouts();
-    }, 1000);
-    this.logger.debug("Started timeout check interval.", {});
-  }
- 
-  checkTimeouts() {
-    const now = Date.now();
-    this.formulas.forEach((formula) => {
-      if (
-        !formula.enabled ||
-        formula.timedOut ||
-        !formula.timeout ||
-        formula.timeout <= 0
-      )
-        return;
- 
-      const requiredInputs = this.parseExpression(formula.expression);
-      if (
-        requiredInputs.length === 0 ||
-        requiredInputs.every(
-          (id) => formula.inputStates[id.toLowerCase()] !== "undefined",
-        )
-      ) {
-        return;
-      }
- 
-      if (!formula.lastInputTime) return;
- 
-      const timeoutMs = formula.timeout * 1000;
-      const elapsed = now - formula.lastInputTime;
- 
-      if (elapsed >= timeoutMs) {
-        this.logger.info("formula.timed_out", {
-          name: formula.name,
-          timeout: formula.timeout,
-        });
-        formula.timedOut = true;
- 
-        const triggerData = {
-          formula: {
-            id: formula.id,
-            name: formula.name,
-          },
-        };
-        const state = {
-          formulaId: formula.id,
-        };
-        try {
-          const card = this.homey.flow.getDeviceTriggerCard("formula_timeout");
-          card
-            .trigger(this, triggerData, state)
-            .catch((err) => this.logger.error("timeout.error", err));
-        } catch (e) {
-          if (e.message && e.message.includes("Invalid Flow Card ID")) {
-            this.logger.error(
-              `FATAL: Trigger card 'formula_timeout' not found. Check app.json/compose flow definitions.`,
-              e,
-            );
-          } else {
-            this.logger.error(
-              this.homey.__("errors.trigger_timeout_card_failed"),
-              e,
-            );
-          }
-        }
-      }
-    });
-  }
- 
-  async onSettings({ oldSettings, newSettings, changedKeys }) {
-    this.logger.info("settings.changed", {
-      keys: changedKeys.join(", "),
-    });
-    if (this.timeoutInterval) {
-      clearInterval(this.timeoutInterval);
-      this.timeoutInterval = null;
-    }
- 
-    await this.initializeFormulas();
- 
-    if (changedKeys.includes("formulas")) {
-      for (const formula of this.formulas) {
-        const validation = this.validateExpression(formula.expression);
-        if (!validation.valid) {
-          this.homey.notifications
-            .createNotification({
-              excerpt: this.homey.__("notifications.invalid_formula_config", {
-                formulaName: formula.name,
-                error: validation.error,
-              }),
-            })
-            .catch((e) =>
-              this.logger.error(
-                this.homey.__("errors.notification_failed_invalid_formula"),
-                e,
-              ),
-            );
-          this.logger.error(
-            `Invalid formula configuration saved for '${formula.name}'`,
-            {
-              error: validation.error,
-            },
-          );
-        }
-      }
-    }
- 
-    await this.evaluateAllFormulasInitial();
- 
-    this.startTimeoutChecks();
-    this.logger.info("settings.applied", {});
- 
-    const formatSettings = {};
-    let needsFormat = false;
-    if (changedKeys.includes("formulas")) {
-      try {
-        const parsed = JSON.parse(newSettings.formulas);
-        const formatted = JSON.stringify(parsed, null, 2);
-        if (formatted !== newSettings.formulas) {
-          formatSettings.formulas = formatted;
-          needsFormat = true;
-          this.logger.debug("settings.formatting", {
-            type: "formulas",
-          });
-        }
-      } catch (e) {}
-    }
-    if (needsFormat) {
-      setTimeout(async () => {
-        try {
-          this.logger.debug("settings.applying_formatted", {});
-          await this.setSettings(formatSettings);
-          this.logger.info("settings.auto_formatted", {});
-        } catch (e) {
-          this.logger.error("settings.format_failed", e);
-        }
-      }, 500);
-    }
-  }
- 
-  async onDeleted() {
-    this._isDeleting = true;
-    this.logger.device("device.deleted_cleanup", {
-      name: this.getName(),
-    });
-    if (this.timeoutInterval) {
-      clearInterval(this.timeoutInterval);
-      this.timeoutInterval = null;
-    }
-    this.logger.info("device.cleanup_complete", {
-      name: this.getName(),
-    });
-  }
-};
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/FormulaEvaluator.js.html b/no.tiwas.booleantoolbox/coverage/FormulaEvaluator.js.html deleted file mode 100644 index ffc5dc7..0000000 --- a/no.tiwas.booleantoolbox/coverage/FormulaEvaluator.js.html +++ /dev/null @@ -1,1174 +0,0 @@ - - - - - - Code coverage report for FormulaEvaluator.js - - - - - - - - - -
-
-

All files FormulaEvaluator.js

-
- -
- 97.5% - Statements - 156/160 -
- - -
- 96.03% - Branches - 97/101 -
- - -
- 100% - Functions - 10/10 -
- - -
- 97.5% - Lines - 156/160 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -52x -52x -  -  -  -  -  -  -  -  -108x -6x -  -  -102x -102x -102x -  -102x -  -630x -242x -242x -  -  -  -388x -25x -25x -25x -  -363x -24x -24x -24x -  -  -  -339x -263x -263x -2x -2x -2x -  -261x -2x -2x -2x -  -259x -2x -2x -2x -  -  -  -  -333x -3x -3x -3x -  -330x -3x -3x -3x -  -327x -3x -3x -3x -  -324x -3x -3x -3x -  -321x -2x -2x -2x -  -319x -2x -2x -2x -  -  -  -317x -302x -302x -526x -526x -  -  -302x -  -  -302x -72x -230x -16x -214x -2x -212x -12x -200x -6x -194x -4x -190x -  -187x -  -3x -  -299x -  -  -  -15x -11x -11x -3x -8x -6x -  -2x -  -9x -9x -  -  -  -4x -  -  -93x -  -  -  -  -  -  -  -  -71x -71x -  -71x -1x -  -  -70x -  -64x -3x -  -  -61x -  -  -  -  -  -  -94x -  -88x -20x -20x -20x -  -  -  -  -  -  -  -88x -  -  -  -  -  -  -114x -  -108x -3x -3x -3x -  -  -  -  -  -  -  -108x -  -  -  -  -  -  -117x -  -113x -71x -71x -69x -  -  -  -  -  -  -  -111x -  -  -  -  -  -  -200x -12x -12x -12x -  -  -  -  -  -  -188x -  -  -  -  -  -  -188x -2x -  -  -186x -  -  -186x -24x -24x -24x -3x -  -21x -21x -  -  -  -162x -12x -12x -  -  -  -  -  -  -150x -149x -149x -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -270x -  -  -  -270x -  -14x -  -  -153x -3x -  -150x -  -  -11x -11x -  -  -  -  -92x -92x -  -90x -  -62x -  -21x -  -7x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -67x -60x -54x -  -  -  -1x
/**
- * AST-Based Formula Evaluator
- * 
- * Securely evaluates boolean formulas without using eval() or new Function()
- * by parsing expressions into an Abstract Syntax Tree (AST) and evaluating it.
- * 
- * Supported operators:
- * - AND: AND, &&, *, &
- * - OR: OR, ||, +, |
- * - XOR: XOR, ^, !=
- * - NOT: NOT, !
- * 
- * Supported values:
- * - Variables: A-J (case-insensitive)
- * - Literals: TRUE, FALSE, true, false, 1, 0
- * - Parentheses: ( )
- */
- 
-class FormulaEvaluator {
-  constructor() {
-    this.tokens = [];
-    this.position = 0;
-  }
- 
-  /**
-   * Tokenize the expression into tokens
-   * @param {string} expression - The formula expression to tokenize
-   * @returns {Array} Array of tokens
-   */
-  tokenize(expression) {
-    if (!expression || typeof expression !== 'string') {
-      throw new Error('Expression must be a non-empty string');
-    }
- 
-    const tokens = [];
-    const expr = expression.trim();
-    let i = 0;
- 
-    while (i < expr.length) {
-      // Skip whitespace
-      if (/\s/.test(expr[i])) {
-        i++;
-        continue;
-      }
- 
-      // Parentheses
-      if (expr[i] === '(') {
-        tokens.push({ type: 'LPAREN', value: '(' });
-        i++;
-        continue;
-      }
-      if (expr[i] === ')') {
-        tokens.push({ type: 'RPAREN', value: ')' });
-        i++;
-        continue;
-      }
- 
-      // Two-character operators
-      if (i < expr.length - 1) {
-        const twoChar = expr.substring(i, i + 2);
-        if (twoChar === '&&') {
-          tokens.push({ type: 'AND', value: '&&' });
-          i += 2;
-          continue;
-        }
-        if (twoChar === '||') {
-          tokens.push({ type: 'OR', value: '||' });
-          i += 2;
-          continue;
-        }
-        if (twoChar === '!=') {
-          tokens.push({ type: 'XOR', value: '!=' });
-          i += 2;
-          continue;
-        }
-      }
- 
-      // Single-character operators
-      if (expr[i] === '&') {
-        tokens.push({ type: 'AND', value: '&' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '*') {
-        tokens.push({ type: 'AND', value: '*' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '|') {
-        tokens.push({ type: 'OR', value: '|' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '+') {
-        tokens.push({ type: 'OR', value: '+' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '^') {
-        tokens.push({ type: 'XOR', value: '^' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '!') {
-        tokens.push({ type: 'NOT', value: '!' });
-        i++;
-        continue;
-      }
- 
-      // Keywords and variables
-      if (/[a-zA-Z]/.test(expr[i])) {
-        let word = '';
-        while (i < expr.length && /[a-zA-Z0-9]/.test(expr[i])) {
-          word += expr[i];
-          i++;
-        }
- 
-        const upper = word.toUpperCase();
-        
-        // Keywords
-        if (upper === 'AND') {
-          tokens.push({ type: 'AND', value: 'AND' });
-        } else if (upper === 'OR') {
-          tokens.push({ type: 'OR', value: 'OR' });
-        } else if (upper === 'XOR') {
-          tokens.push({ type: 'XOR', value: 'XOR' });
-        } else if (upper === 'NOT') {
-          tokens.push({ type: 'NOT', value: 'NOT' });
-        } else if (upper === 'TRUE') {
-          tokens.push({ type: 'LITERAL', value: true });
-        } else if (upper === 'FALSE') {
-          tokens.push({ type: 'LITERAL', value: false });
-        } else if (/^[A-J]$/.test(upper)) {
-          // Variables A-J
-          tokens.push({ type: 'VARIABLE', value: upper });
-        } else {
-          throw new Error(`Invalid identifier: ${word}`);
-        }
-        continue;
-      }
- 
-      // Numbers (0 or 1 as boolean literals)
-      if (/[0-9]/.test(expr[i])) {
-        const num = expr[i];
-        if (num === '0') {
-          tokens.push({ type: 'LITERAL', value: false });
-        } else if (num === '1') {
-          tokens.push({ type: 'LITERAL', value: true });
-        } else {
-          throw new Error(`Invalid number: ${num}. Only 0 and 1 are allowed.`);
-        }
-        i++;
-        continue;
-      }
- 
-      // Unknown character
-      throw new Error(`Unexpected character: ${expr[i]}`);
-    }
- 
-    return tokens;
-  }
- 
-  /**
-   * Parse tokens into an Abstract Syntax Tree (AST)
-   * @param {Array} tokens - Array of tokens from tokenize()
-   * @returns {Object} AST root node
-   */
-  parse(tokens) {
-    this.tokens = tokens;
-    this.position = 0;
- 
-    if (tokens.length === 0) {
-      throw new Error('Empty expression');
-    }
- 
-    const ast = this.parseOr();
- 
-    if (this.position < this.tokens.length) {
-      throw new Error(`Unexpected token: ${this.tokens[this.position].value}`);
-    }
- 
-    return ast;
-  }
- 
-  /**
-   * Parse OR expressions (lowest precedence)
-   */
-  parseOr() {
-    let left = this.parseXor();
- 
-    while (this.position < this.tokens.length && this.tokens[this.position].type === 'OR') {
-      this.position++;
-      const right = this.parseXor();
-      left = {
-        type: 'BinaryOp',
-        operator: 'OR',
-        left: left,
-        right: right
-      };
-    }
- 
-    return left;
-  }
- 
-  /**
-   * Parse XOR expressions
-   */
-  parseXor() {
-    let left = this.parseAnd();
- 
-    while (this.position < this.tokens.length && this.tokens[this.position].type === 'XOR') {
-      this.position++;
-      const right = this.parseAnd();
-      left = {
-        type: 'BinaryOp',
-        operator: 'XOR',
-        left: left,
-        right: right
-      };
-    }
- 
-    return left;
-  }
- 
-  /**
-   * Parse AND expressions
-   */
-  parseAnd() {
-    let left = this.parseUnary();
- 
-    while (this.position < this.tokens.length && this.tokens[this.position].type === 'AND') {
-      this.position++;
-      const right = this.parseUnary();
-      left = {
-        type: 'BinaryOp',
-        operator: 'AND',
-        left: left,
-        right: right
-      };
-    }
- 
-    return left;
-  }
- 
-  /**
-   * Parse unary expressions (NOT)
-   */
-  parseUnary() {
-    if (this.position < this.tokens.length && this.tokens[this.position].type === 'NOT') {
-      this.position++;
-      const operand = this.parseUnary(); // Allow multiple NOTs
-      return {
-        type: 'UnaryOp',
-        operator: 'NOT',
-        operand: operand
-      };
-    }
- 
-    return this.parsePrimary();
-  }
- 
-  /**
-   * Parse primary expressions (literals, variables, parentheses)
-   */
-  parsePrimary() {
-    if (this.position >= this.tokens.length) {
-      throw new Error('Unexpected end of expression');
-    }
- 
-    const token = this.tokens[this.position];
- 
-    // Parentheses
-    if (token.type === 'LPAREN') {
-      this.position++;
-      const expr = this.parseOr();
-      if (this.position >= this.tokens.length || this.tokens[this.position].type !== 'RPAREN') {
-        throw new Error('Unbalanced parentheses');
-      }
-      this.position++;
-      return expr;
-    }
- 
-    // Literals
-    if (token.type === 'LITERAL') {
-      this.position++;
-      return {
-        type: 'Literal',
-        value: token.value
-      };
-    }
- 
-    // Variables
-    if (token.type === 'VARIABLE') {
-      this.position++;
-      return {
-        type: 'Variable',
-        name: token.value
-      };
-    }
- 
-    throw new Error(`Unexpected token: ${token.value}`);
-  }
- 
-  /**
-   * Evaluate an AST with given variable values
-   * @param {Object} ast - The AST to evaluate
-   * @param {Object} variables - Object mapping variable names to boolean values
-   * @returns {boolean} The result of the evaluation
-   */
-  evaluateAST(ast, variables = {}) {
-    Iif (!ast) {
-      throw new Error('Invalid AST');
-    }
- 
-    switch (ast.type) {
-      case 'Literal':
-        return !!ast.value;
- 
-      case 'Variable':
-        if (!(ast.name in variables)) {
-          throw new Error(`Undefined variable: ${ast.name}`);
-        }
-        return !!variables[ast.name];
- 
-      case 'UnaryOp':
-        Eif (ast.operator === 'NOT') {
-          return !this.evaluateAST(ast.operand, variables);
-        }
-        throw new Error(`Unknown unary operator: ${ast.operator}`);
- 
-      case 'BinaryOp':
-        const left = this.evaluateAST(ast.left, variables);
-        const right = this.evaluateAST(ast.right, variables);
- 
-        switch (ast.operator) {
-          case 'AND':
-            return left && right;
-          case 'OR':
-            return left || right;
-          case 'XOR':
-            return left !== right;
-          default:
-            throw new Error(`Unknown binary operator: ${ast.operator}`);
-        }
- 
-      default:
-        throw new Error(`Unknown AST node type: ${ast.type}`);
-    }
-  }
- 
-  /**
-   * Validate and evaluate an expression
-   * @param {string} expression - The formula expression
-   * @param {Object} variables - Object mapping variable names to boolean values
-   * @returns {boolean} The result of the evaluation
-   */
-  evaluate(expression, variables = {}) {
-    const tokens = this.tokenize(expression);
-    const ast = this.parse(tokens);
-    return this.evaluateAST(ast, variables);
-  }
-}
- 
-module.exports = FormulaEvaluator;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/Logger.js.html b/no.tiwas.booleantoolbox/coverage/Logger.js.html deleted file mode 100644 index 6c000f2..0000000 --- a/no.tiwas.booleantoolbox/coverage/Logger.js.html +++ /dev/null @@ -1,1303 +0,0 @@ - - - - - - Code coverage report for Logger.js - - - - - - - - - -
-
-

All files Logger.js

-
- -
- 0% - Statements - 0/131 -
- - -
- 0% - Branches - 0/88 -
- - -
- 0% - Functions - 0/28 -
- - -
- 0% - Lines - 0/131 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
"use strict";
- 
-/**
- * Advanced Logger for Homey Apps
- *
- * NEW: Supports global configuration via loggerConfig.js
- * FIX: Manually performs variable substitution as this.homey.__() fails to do so.
- * FIX (User): Added log level (e.g., [INFO ], [DEBUG]) to the console output.
- * FIX (User): Moved log level to the front of the output.
- * FIX (User): Fixed 'minLevel = 0 || ...' bug.
- * FIX (User): Added config loaded logging and static cache.
- * FIX (User): Moved config log to appear *after* the banner.
- * FIX (User): Fixed bug in _getDefaultConfig catch block return shape.
- * FIX (User): Fixed null-logging in banner().
- * FIX (User): Reverted info() to log as INFO. Code should be explicit.
- */
- 
-class Logger {
-  static LEVELS = {
-    DEBUG: 0,
-    INFO: 1,
-    WARN: 2,
-    ERROR: 3,
-    NONE: 4,
-  };
- 
-  static SYMBOLS = {
-    DEBUG: "🔍",
-    INFO: "✅",
-    WARN: "⚠️",
-    ERROR: "❌",
-    TIMER: "⏱️",
-    FORMULA: "📐",
-    INPUT: "📥",
-    OUTPUT: "📤",
-    DEVICE: "🔌",
-    API: "🌐",
-    FLOW: "🔄",
-  };
- 
-  static _globalConfig = null;
- 
-  /**
-   * Get default configuration
-   * Tries to load from loggerConfig.js, falls back to defaults
-   * @private
-   */
-  static _getDefaultConfig() {
-    // Use cache if available
-    if (Logger._globalConfig) {
-      return Logger._globalConfig;
-    }
- 
-    try {
-      const config = require("./loggerConfig");
-      Logger._globalConfig = { config: config, source: "custom" };
-      return Logger._globalConfig;
-    } catch (e) {
-      const defaultConfig = {
-        defaultLevel: "DEBUG", // ENDRET: Fallback til DEBUG
-        categoryLevels: {},
-        options: {},
-      };
-      // --- FIKS: Returner samme objektform som i 'try' ---
-      Logger._globalConfig = { config: defaultConfig, source: "default" };
-      return Logger._globalConfig;
-      // --- SLUTT FIKS ---
-    }
-  }
- 
-  constructor(context, category = "App", options = {}) {
-    // --- FIKS I KONSTRUKTØR ---
-    // Finner det *ekte* homey-objektet uansett om 'context' er App eller Device
-    if (context && context.homey) {
-      this.homey = context.homey;
-    } else if (context && context.app && context.app.homey) {
-      // Fallback
-      this.homey = context.app.homey;
-    } else {
-      console.error(
-        `Logger: Could not find 'homey' object on context for category '${category}'.`,
-      );
-      // Oppretter en dummy for å unngå krasj
-      this.homey = {
-        app: { log: console.log, error: console.error },
-        __: (s) => s, // Returnerer bare nøkkelen
-      };
-    }
-    // --- SLUTT FIKS ---
- 
-    this.context = context;
-    this.category = category;
- 
-    // Load global config
-    const { config: globalConfig, source: configSource } =
-      Logger._getDefaultConfig();
-    this.configSource = configSource; // Lagrer kilden for bruk i banner()
- 
-    // Determine log level (priority: options > categoryLevels > defaultLevel)
-    let level = options.level;
-    if (!level && globalConfig.categoryLevels[category]) {
-      level = globalConfig.categoryLevels[category];
-    }
-    if (!level) {
-      level = globalConfig.defaultLevel || "DEBUG"; // ENDRET: Fallback til DEBUG
-    }
- 
-    this.options = {
-      level: level.toUpperCase(),
-      timestamps:
-        options.timestamps || globalConfig.options.timestamps || false,
-      colors: options.colors || globalConfig.options.colors || false,
-    }; // --- FIKS: Manglende } lagt til ---
- 
-    // --- FIKS: Korrekt sjekk for 0 (DEBUG) ---
-    if (Logger.LEVELS[this.options.level] !== undefined) {
-      this.minLevel = Logger.LEVELS[this.options.level];
-    } else {
-      this.minLevel = Logger.LEVELS.DEBUG; // Fallback
-    }
-    // --- SLUTT FIKS ---
- 
-    this.timers = new Map();
-  }
- 
-  setLevel(level) {
-    const upperLevel = level.toUpperCase();
-    if (Logger.LEVELS[upperLevel] !== undefined) {
-      this.minLevel = Logger.LEVELS[upperLevel];
-      this.options.level = upperLevel;
-    }
-  }
- 
-  getLevel() {
-    return this.options.level;
-  }
- 
-  _shouldLog(level) {
-    return Logger.LEVELS[level] >= this.minLevel;
-  }
- 
-  _getPrefix(symbol) {
-    return `${symbol} [${this.category}]`;
-  }
- 
-  _formatMessage(keyOrMessage, data) {
-    let message = keyOrMessage; // Start with the original key/message
- 
-    // 1. Attempt Translation IF it looks like a key and __ exists
-    if (
-      typeof keyOrMessage === "string" &&
-      keyOrMessage.includes(".") &&
-      this.homey &&
-      typeof this.homey.__ === "function"
-    ) {
-      try {
-        const translated = this.homey.__(keyOrMessage, data);
-        // Use translation ONLY if it's a non-empty string and DIFFERENT from the key
-        if (
-          typeof translated === "string" &&
-          translated.trim() !== "" &&
-          translated !== keyOrMessage
-        ) {
-          message = translated;
-        }
-        // If translated is null, undefined, empty, or same as key, 'message' remains keyOrMessage
-      } catch (e) {
-        // If translation throws error, 'message' remains keyOrMessage
-        // console.error(`Logger: Error during this.homey.__ for key '${keyOrMessage}'. Using key.`, e);
-      }
-    }
- 
-    // 2. Ensure 'message' is now DEFINITELY a string (fallback if translation failed badly)
-    let messageStr = String(message ?? keyOrMessage); // Use keyOrMessage if message became null/undefined
- 
-    // 3. Perform Manual Substitution on the resulting string
-    if (data && typeof data === "object" && data !== null) {
-      try {
-        messageStr = messageStr.replace(/\{([^{}]+)\}/g, (match, key) => {
-          // Use value from data if exists and is not null/undefined, otherwise keep placeholder
-          return data.hasOwnProperty(key) &&
-            data[key] !== null &&
-            data[key] !== undefined
-            ? String(data[key]) // Ensure replacement is a string
-            : match;
-        });
-      } catch (e) {
-        console.error(
-          `Logger: Manual substitution failed for: "${messageStr}"`,
-          e,
-        );
-        // messageStr remains as it was before substitution attempt
-      }
-    }
- 
-    // Return the final string
-    return messageStr;
-  }
- 
-  _log(level, symbol, keyOrMessage, data) {
-    if (!this._shouldLog(level)) {
-      return;
-    }
- 
-    // --- FIKS: Log-nivå lagt til ---
-    const levelString = `[${level.padEnd(5)}]`;
-    const prefix = this._getPrefix(symbol);
-    const formattedMessage = this._formatMessage(keyOrMessage, data);
- 
-    try {
-      // Bruk ALLTID this.homey.app.log
-      // --- FIKS: Flyttet levelString FØRST ---
-      this.homey.app.log(levelString, prefix, formattedMessage);
-    } catch (error) {
-      console.error("Logger internal error:", error);
-      console.log(levelString, prefix, formattedMessage); // Fallback til console.log
-    }
-  }
- 
-  _logError(keyOrMessage, error) {
-    if (!this._shouldLog("ERROR")) {
-      return;
-    }
- 
-    // --- FIKS: Log-nivå lagt til ---
-    const levelString = `[${"ERROR".padEnd(5)}]`;
- 
-    const prefix = this._getPrefix(Logger.SYMBOLS.ERROR);
- 
-    // Data-objektet kan være gjemt i 'error' hvis det ikke er en ekte Error
-    let data = error instanceof Error ? null : error;
-    const formattedMessage = this._formatMessage(keyOrMessage, data);
- 
-    try {
-      // --- FIKS: Flyttet levelString FØRST ---
-      this.homey.app.error(levelString, prefix, formattedMessage);
-      if (error instanceof Error) {
-        this.homey.app.error(error); // Logg stack trace hvis det er en Error
-      } else if (data) {
-        // Hvis 'error' bare var data, er den allerede i formattedMessage
-      }
-    } catch (err) {
-      console.error("Logger internal error (error):", err);
-      console.error(levelString, prefix, formattedMessage);
-      if (error) {
-        console.error(error);
-      }
-    }
-  }
- 
-  debug(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.DEBUG, message, data);
-  }
- 
-  // --- FIKS: info() logger nå som INFO ---
-  info(message, data) {
-    this._log("INFO", Logger.SYMBOLS.INFO, message, data);
-  }
- 
-  warn(message, data) {
-    this._log("WARN", Logger.SYMBOLS.WARN, message, data);
-  }
- 
-  error(message, error) {
-    this._logError(message, error);
-  }
- 
-  formula(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.FORMULA, message, data);
-  }
- 
-  input(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.INPUT, message, data);
-  }
- 
-  output(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.OUTPUT, message, data);
-  }
- 
-  // --- FIKS: device() logger nå som DEBUG ---
-  device(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.DEVICE, message, data);
-  }
- 
-  api(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.API, message, data);
-  }
- 
-  // --- FIKS: flow() logger nå som DEBUG ---
-  flow(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.FLOW, message, data);
-  }
- 
-  timeStart(label) {
-    this.timers.set(label, Date.now());
-    if (this._shouldLog("DEBUG")) {
-      this._log("DEBUG", Logger.SYMBOLS.TIMER, `Timer started: ${label}`);
-    }
-  }
- 
-  timeEnd(label) {
-    const startTime = this.timers.get(label);
-    if (!startTime) {
-      this.warn(`Timer '${label}' was not started`);
-      return 0;
-    }
- 
-    const duration = Date.now() - startTime;
-    this.timers.delete(label);
- 
-    if (this._shouldLog("DEBUG")) {
-      this._log(
-        "DEBUG",
-        Logger.SYMBOLS.TIMER,
-        `Timer '${label}': ${duration}ms`,
-      );
-    }
- 
-    return duration;
-  }
- 
-  dump(label, object) {
-    if (!this._shouldLog("DEBUG")) {
-      return;
-    }
- 
-    try {
-      const formatted = JSON.stringify(object, null, 2);
-      this._log("DEBUG", Logger.SYMBOLS.DEBUG, `${label}:\n${formatted}`);
-    } catch (error) {
-      this._log("DEBUG", Logger.SYMBOLS.DEBUG, `${label}:`, object);
-    }
-  }
- 
-  once(key, level, message, data) {
-    if (!this._onceKeys) {
-      this._onceKeys = new Set();
-    }
- 
-    if (this._onceKeys.has(key)) {
-      return;
-    }
- 
-    this._onceKeys.add(key);
-    const method = level.toLowerCase();
-    if (typeof this[method] === "function") {
-      this[method](message, data);
-    } else {
-      console.warn(
-        `Logger: Invalid level "${level}" provided to 'once' method.`,
-      );
-    }
-  }
- 
-  child(subCategory) {
-    return new Logger(
-      this.context,
-      `${this.category}:${subCategory}`,
-      this.options,
-    );
-  }
- 
-  separator() {
-    if (this._shouldLog("DEBUG")) {
-      this._log("DEBUG", "", "─".repeat(50));
-    }
-  }
- 
-  banner(message) {
-    if (this._shouldLog("INFO")) {
-      const line = "═".repeat(message.length + 4);
-      // Banner kaller logg-funksjonen direkte for å unngå prefiks
-      try {
-        this.homey.app.log(line);
-        this.homey.app.log(`  ${message}  `);
-        this.homey.app.log(line);
- 
-        // --- FIKS: Bruker this.homey.app.log direkte ---
-        // _log() feilet fordi this.homey.__() ikke var klar for strenger
-        // som ikke var locale-nøkler på dette tidlige stadiet.
-        if (this.configSource) {
-          const configMessage =
-            this.configSource === "custom"
-              ? "Loaded custom configuration from ./loggerConfig.js"
-              : "Using default log settings (./loggerConfig.js not found)";
- 
-          // Logger med [INFO] og 🔍-symbolet, som du foreslo
-          // Bruker this.homey.app.log direkte for å unngå _formatMessage-feil
-          this.homey.app.log(
-            `[INFO ] ${Logger.SYMBOLS.DEBUG} [${this.category}]`,
-            configMessage,
-          );
- 
-          // Nullstill, så den ikke logger dette igjen hvis banner() kalles på nytt
-          this.configSource = null;
-        }
-        // --- SLUTT FIKS ---
-      } catch (e) {
-        console.log(line);
-        console.log(`  ${message}  `);
-        console.log(line);
-      }
-    }
-  }
-}
- 
-module.exports = Logger;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/base.css b/no.tiwas.booleantoolbox/coverage/base.css deleted file mode 100644 index f418035..0000000 --- a/no.tiwas.booleantoolbox/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/no.tiwas.booleantoolbox/coverage/block-navigation.js b/no.tiwas.booleantoolbox/coverage/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/no.tiwas.booleantoolbox/coverage/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/no.tiwas.booleantoolbox/coverage/favicon.png b/no.tiwas.booleantoolbox/coverage/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 16.79% - Statements - 156/929 -
- - -
- 16.75% - Branches - 97/579 -
- - -
- 8.06% - Functions - 10/124 -
- - -
- 17.25% - Lines - 156/904 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
BaseLogicDriver.js -
-
0%0/1780%0/1070%0/190%0/174
BaseLogicUnit.js -
-
0%0/4590%0/2830%0/670%0/438
FormulaEvaluator.js -
-
97.5%156/16096.03%97/101100%10/1097.5%156/160
Logger.js -
-
0%0/1310%0/880%0/280%0/131
loggerConfig.js -
-
0%0/1100%0/0100%0/00%0/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicDriver.js.html b/no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicDriver.js.html deleted file mode 100644 index 9f3e9df..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicDriver.js.html +++ /dev/null @@ -1,1516 +0,0 @@ - - - - - - Code coverage report for BaseLogicDriver.js - - - - - - - - - -
-
-

All files BaseLogicDriver.js

-
- -
- 0% - Statements - 0/178 -
- - -
- 0% - Branches - 0/107 -
- - -
- 0% - Functions - 0/19 -
- - -
- 0% - Lines - 0/174 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
"use strict";
-const Homey = require("homey");
-const Logger = require("./Logger");
- 
-async function formulaAutocompleteHelper(query, args) {
-    const device = args.device;
-    if (!device || typeof device.getFormulas !== "function") {
-        console.warn(
-            `formulaAutocompleteHelper: Invalid device or missing getFormulas in args for autocomplete.`,
-        );
-        return [];
-    }
-    try {
-        const formulas = device.getFormulas();
-        if (!Array.isArray(formulas)) {
-            console.warn(
-                `getFormulas didn't return array for ${device.getName()}.`,
-            );
-            return [];
-        }
-        const lowerQuery = query ? query.toLowerCase() : "";
-        return formulas.filter(
-            (f) =>
-                f &&
-                f.name &&
-                (!query || f.name.toLowerCase().includes(lowerQuery)),
-        );
-    } catch (e) {
-        console.error(
-            `Error in formulaAutocompleteHelper for ${device.getName()}`,
-            e,
-        );
-        return [];
-    }
-}
- 
-async function inputAutocompleteHelper(query, args) {
-    const device = args.device;
-    if (!device || typeof device.getInputOptions !== "function") {
-        console.warn(
-            `inputAutocompleteHelper: Invalid device or missing getInputOptions in args for autocomplete.`,
-        );
-        return [];
-    }
-    try {
-        const inputs = device.getInputOptions(args);
-        if (!Array.isArray(inputs)) {
-            console.warn(
-                `getInputOptions didn't return array for ${device.getName()}.`,
-            );
-            return [];
-        }
-        const lowerQuery = query ? query.toLowerCase() : "";
-        return inputs.filter(
-            (i) =>
-                i &&
-                i.name &&
-                (!query || i.name.toLowerCase().includes(lowerQuery)),
-        );
-    } catch (e) {
-        console.error(
-            `Error in inputAutocompleteHelper for ${device.getName()}`,
-            e,
-        );
-        return [];
-    }
-}
-// --- End Autocomplete Helper Functions ---
- 
-class BaseLogicDriver extends Homey.Driver {
-    static logicUnitCardsRegistered = false;
- 
-    async onInit() {
-        const driverName = `Driver: ${this.id}`;
-        this.logger = new Logger(this, driverName);
- 
-        // Set numInputs based on driver ID
-        const driverId = this.id;
-        let numInputsParsed = 2;
-        try {
-            const parts = driverId.split("-");
-            const numStr = parts[parts.length - 1];
-            const num = parseInt(numStr);
-            if (!isNaN(num) && num > 0) {
-                numInputsParsed = num;
-            } else if (driverId !== "logic-device") {
-                this.logger.warn(
-                    `Could not parse number of inputs from driver ID '${driverId}'. Using default: ${numInputsParsed}.`,
-                    {},
-                );
-            }
-        } catch (e) {
-            this.logger.error(
-                `Error parsing numInputs from driver ID '${driverId}'. Using default: ${numInputsParsed}.`,
-                e,
-            );
-        }
-        this.numInputs = numInputsParsed;
- 
-        this.logger.info("driver.ready", {
-            driverId: this.id,
-            numInputs: this.numInputs,
-        });
- 
-        if (!BaseLogicDriver.logicUnitCardsRegistered) {
-            this.logger.info(
-                "First Logic Unit driver initializing. Registering shared flow cards...",
-            );
-            await this.registerFlowCards();
-            BaseLogicDriver.logicUnitCardsRegistered = true;
-        } else {
-            this.logger.debug(
-                "Shared flow cards already registered by another Logic Unit driver.",
-            );
-        }
-    }
- 
-    async registerFlowCards() {
-        this.logger.debug("driver.registering_flow_cards");
- 
-        // --- Triggers ---
-        const triggerCards = [
-            {
-                id: "formula_changed_to_false_lu",
-            },
-            {
-                id: "formula_changed_to_true_lu",
-            },
-            {
-                id: "formula_timeout_lu",
-            },
-        ];
-        triggerCards.forEach((cardInfo) => {
-            try {
-                const card = this.homey.flow.getTriggerCard(cardInfo.id);
-                card.registerRunListener(async (args, state) => {
-                    return (
-                        args &&
-                        args.device &&
-                        args.device.driver &&
-                        args.device.driver.id &&
-                        args.device.driver.id.startsWith("logic-unit-")
-                    );
-                });
-                this.registerAutocomplete(
-                    card,
-                    "formula",
-                    formulaAutocompleteHelper,
-                );
-                this.logger.debug(
-                    ` -> OK: TRIGGER card registered: '${cardInfo.id}'`,
-                );
-            } catch (e) {
-                this.logger.error(
-                    ` -> FAILED: Registering TRIGGER card '${cardInfo.id}'`,
-                    e,
-                );
-            }
-        });
- 
-        // --- Actions ---
-        const actionCards = [
-            {
-                id: "set_input_value_lu",
-                handler: "onFlowActionSetInput",
-            },
-            {
-                id: "evaluate_formula_lu",
-                handler: "onFlowActionEvaluateFormula",
-            },
-            {
-                id: "evaluate_all_formulas_lu",
-                handler: "onFlowActionReEvaluateAll",
-            },
-            {
-                id: "clear_error_state_lu",
-                handler: "onFlowActionClearError",
-            },
-            {
-                id: "set_all_inputs_lu",
-                handler: "setAllInputsFromFlow",
-            },
-            {
-                id: "set_input_lu",
-                handler: "setInputForAllFormulasFromFlow",
-            },
-        ];
-        actionCards.forEach((cardInfo) => {
-            try {
-                const card = this.homey.flow.getActionCard(cardInfo.id);
-                card.registerRunListener(async (args, state) => {
-                    const device = args.device;
-                    if (!device)
-                        throw new Error(
-                            this.homey.__("errors.invalid_device_instance"),
-                        );
-                    const methodName = cardInfo.handler;
-                    if (typeof device[methodName] !== "function")
-                        throw new Error(
-                            this.homey.__("errors.method_missing_on_device", {
-                                methodName,
-                            }),
-                        );
-                    this.logger.flow(
-                        `Executing ACTION '${cardInfo.id}' on device ${device.getName()}`,
-                    );
-                    return await device[methodName](args, state);
-                });
- 
-                // --- Autocomplete ---
-                if (
-                    [
-                        "set_input_value_lu",
-                        "evaluate_formula_lu",
-                        "clear_error_state_lu",
-                    ].includes(cardInfo.id)
-                ) {
-                    this.registerAutocomplete(
-                        card,
-                        "formula",
-                        formulaAutocompleteHelper,
-                    );
-                }
-                if (
-                    ["set_input_value_lu", "set_input_lu"].includes(cardInfo.id)
-                ) {
-                    this.registerAutocomplete(
-                        card,
-                        "input",
-                        inputAutocompleteHelper,
-                    );
-                }
-                this.logger.debug(
-                    ` -> OK: ACTION card registered: '${cardInfo.id}'`,
-                );
-            } catch (e) {
-                this.logger.error(
-                    ` -> FAILED: Registering ACTION card '${cardInfo.id}'`,
-                    e,
-                );
-            }
-        });
- 
-        // --- Conditions ---
-        const conditionCards = [
-            {
-                id: "formula_has_timed_out_lu",
-                checkType: "timeout",
-            },
-            {
-                id: "formula_result_is_lu",
-                checkTypeFromArg: "what_is",
-            },
-            {
-                id: "has_any_error_lu",
-                checkType: "has_error",
-            },
-        ];
-        conditionCards.forEach((cardInfo) => {
-            try {
-                const card = this.homey.flow.getConditionCard(cardInfo.id);
-                card.registerRunListener(async (args, state) => {
-                    const device = args.device;
-                    if (!device) return false;
-                    if (typeof device.onFlowCondition !== "function")
-                        return false;
- 
-                    let checkType;
-                    if (cardInfo.checkType) {
-                        checkType = cardInfo.checkType;
-                    } else if (cardInfo.checkTypeFromArg) {
-                        checkType = args[cardInfo.checkTypeFromArg] === "true";
-                    }
- 
-                    this.logger.flow(
-                        `Executing CONDITION '${cardInfo.id}' on device ${device.getName()}`,
-                    );
-                    return await device.onFlowCondition(args, state, checkType);
-                });
- 
-                if (
-                    [
-                        "formula_has_timed_out_lu",
-                        "formula_result_is_lu",
-                    ].includes(cardInfo.id)
-                ) {
-                    this.registerAutocomplete(
-                        card,
-                        "formula",
-                        formulaAutocompleteHelper,
-                    );
-                }
-                this.logger.debug(
-                    ` -> OK: CONDITION card registered: '${cardInfo.id}'`,
-                );
-            } catch (e) {
-                this.logger.error(
-                    ` -> FAILED: Registering CONDITION card '${cardInfo.id}'`,
-                    e,
-                );
-            }
-        });
-    }
- 
-    // --- Helper for Autocomplete Registration ---
-    registerAutocomplete(card, argName, helperFn) {
-        try {
-            if (typeof helperFn !== "function") {
-                throw new Error(
-                    this.homey.__("errors.helper_not_function", { argName }),
-                );
-            }
-            card.registerArgumentAutocompleteListener(
-                argName,
-                async (query, args) => {
-                    const device = args?.device;
- 
-                    if (argName === "formula" || argName === "input") {
-                        if (!device) {
-                            this.logger.warn(
-                                `Autocomplete for device arg '${argName}' on card '${card.id}' called without device context.`,
-                            );
-                            return [];
-                        }
- 
-                        let requiredMethod = "";
-                        if (argName === "formula")
-                            requiredMethod = "getFormulas";
-                        if (argName === "input")
-                            requiredMethod = "getInputOptions";
- 
-                        if (typeof device[requiredMethod] !== "function") {
-                            this.logger.warn(
-                                `Autocomplete for device arg '${argName}' on card '${card.id}': Device ${device.getName()} missing method ${requiredMethod}.`,
-                            );
-                            return [];
-                        }
-                    }
- 
-                    try {
-                        return await helperFn(query, args);
-                    } catch (autocompleteError) {
-                        this.logger.error(
-                            `Error during autocomplete for ${argName} on card '${card.id}'`,
-                            autocompleteError,
-                        );
-                        return [];
-                    }
-                },
-            );
-            this.logger.debug(
-                `Registered ${argName.toUpperCase()} autocomplete for card '${card.id}'`,
-            );
-        } catch (e) {
-            this.logger.error(
-                ` -> FAILED to register autocomplete for '${argName}' on card '${card.id}'`,
-                e,
-            );
-        }
-    }
- 
-    async ensureUniqueDeviceName(name) {
-        try {
-            if (
-                !this.homey.app ||
-                !this.homey.app.api ||
-                !this.homey.app.api.devices ||
-                typeof this.homey.app.api.devices.getDevices !== "function"
-            ) {
-                this.logger.warn(
-                    "ensureUniqueDeviceName: Homey API not ready, returning original name.",
-                    {},
-                );
-                return String(name || "");
-            }
-            const all = await this.homey.app.api.devices.getDevices();
-            if (!all || typeof all !== "object") {
-                this.logger.error(
-                    "ensureUniqueDeviceName: Unexpected response from getDevices:",
-                    all,
-                );
-                return String(name || "");
-            }
-            const existing = new Set(
-                Object.values(all)
-                    .map((d) =>
-                        d && typeof d === "object"
-                            ? String(d.name || "").trim()
-                            : "",
-                    )
-                    .filter(Boolean),
-            );
-            const nameStr = String(name || "");
-            if (!existing.has(nameStr)) return nameStr;
-            const base = nameStr.replace(/\s+\d+$/, "").trim();
-            const m = nameStr.match(/\s+(\d+)$/);
-            let n = m ? parseInt(m[1], 10) + 1 : 2;
-            if (isNaN(n)) n = 2;
-            let candidate = `${base} ${n}`;
-            let safetyCounter = 0;
-            while (existing.has(candidate) && safetyCounter < 100) {
-                n++;
-                candidate = `${base} ${n}`;
-                safetyCounter++;
-            }
-            if (safetyCounter >= 100) {
-                this.logger.error(
-                    "ensureUniqueDeviceName: Could not find unique name after 100 attempts, returning original + timestamp.",
-                    {},
-                );
-                return `${nameStr}-${Date.now()}`;
-            }
-            return candidate;
-        } catch (e) {
-            this.logger.error("driver.ensure_unique_failed", e);
-            return String(name || "");
-        }
-    }
- 
-    async onPair(session) {
-        this.logger.info("pair.session_started", {});
-        session.setHandler("list_devices", async () => {
-            try {
-                this.logger.debug("Pair handler: list_devices called", {});
-                let localeString;
-                try {
-                    localeString = this.homey.__("device.logic_unit_name");
-                } catch (e) {
-                    this.logger.error(
-                        "Error getting localization for 'device.logic_unit_name'",
-                        e,
-                    );
-                    localeString = this.homey.__(
-                        "device.logic_unit_name_fallback",
-                    );
-                }
-                const baseName = String(localeString || "").replace(
-                    "{count}",
-                    this.numInputs,
-                );
-                const name = await this.ensureUniqueDeviceName(baseName);
-                const defaultFormulaName = this.homey.__(
-                    "pair.default_formula_name",
-                );
-                const deviceData = {
-                    name: name,
-                    data: {
-                        id: `${this.id}-${Math.random().toString(16).slice(2)}`,
-                        numInputs: this.numInputs,
-                    },
-                    settings: {
-                        formulas: `[{"id":"f1","name":"${defaultFormulaName}","expression":"${this.getDefaultExpression()}","enabled":true,"timeout":0,"firstImpression":false}]`,
-                    },
-                };
-                this.logger.debug(
-                    "Pair handler: list_devices returning:",
-                    deviceData,
-                );
-                return [deviceData];
-            } catch (e) {
-                this.logger.error("Error in list_devices pair handler", e);
-                throw e;
-            }
-        });
-        this.logger.debug("Pair handler: list_devices registered", {});
-    }
- 
-    getDefaultExpression() {
-        const letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"];
-        const inputsToUse = Math.min(this.numInputs, letters.length);
-        if (inputsToUse <= 0) return "true";
-        return letters.slice(0, inputsToUse).join(" AND ");
-    }
-}
- 
-module.exports = BaseLogicDriver;
-module.exports.formulaAutocompleteHelper = formulaAutocompleteHelper;
-module.exports.inputAutocompleteHelper = inputAutocompleteHelper;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicUnit.js.html b/no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicUnit.js.html deleted file mode 100644 index 3fb7cdd..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/BaseLogicUnit.js.html +++ /dev/null @@ -1,3226 +0,0 @@ - - - - - - Code coverage report for BaseLogicUnit.js - - - - - - - - - -
-
-

All files BaseLogicUnit.js

-
- -
- 0% - Statements - 0/459 -
- - -
- 0% - Branches - 0/283 -
- - -
- 0% - Functions - 0/67 -
- - -
- 0% - Lines - 0/438 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478 -479 -480 -481 -482 -483 -484 -485 -486 -487 -488 -489 -490 -491 -492 -493 -494 -495 -496 -497 -498 -499 -500 -501 -502 -503 -504 -505 -506 -507 -508 -509 -510 -511 -512 -513 -514 -515 -516 -517 -518 -519 -520 -521 -522 -523 -524 -525 -526 -527 -528 -529 -530 -531 -532 -533 -534 -535 -536 -537 -538 -539 -540 -541 -542 -543 -544 -545 -546 -547 -548 -549 -550 -551 -552 -553 -554 -555 -556 -557 -558 -559 -560 -561 -562 -563 -564 -565 -566 -567 -568 -569 -570 -571 -572 -573 -574 -575 -576 -577 -578 -579 -580 -581 -582 -583 -584 -585 -586 -587 -588 -589 -590 -591 -592 -593 -594 -595 -596 -597 -598 -599 -600 -601 -602 -603 -604 -605 -606 -607 -608 -609 -610 -611 -612 -613 -614 -615 -616 -617 -618 -619 -620 -621 -622 -623 -624 -625 -626 -627 -628 -629 -630 -631 -632 -633 -634 -635 -636 -637 -638 -639 -640 -641 -642 -643 -644 -645 -646 -647 -648 -649 -650 -651 -652 -653 -654 -655 -656 -657 -658 -659 -660 -661 -662 -663 -664 -665 -666 -667 -668 -669 -670 -671 -672 -673 -674 -675 -676 -677 -678 -679 -680 -681 -682 -683 -684 -685 -686 -687 -688 -689 -690 -691 -692 -693 -694 -695 -696 -697 -698 -699 -700 -701 -702 -703 -704 -705 -706 -707 -708 -709 -710 -711 -712 -713 -714 -715 -716 -717 -718 -719 -720 -721 -722 -723 -724 -725 -726 -727 -728 -729 -730 -731 -732 -733 -734 -735 -736 -737 -738 -739 -740 -741 -742 -743 -744 -745 -746 -747 -748 -749 -750 -751 -752 -753 -754 -755 -756 -757 -758 -759 -760 -761 -762 -763 -764 -765 -766 -767 -768 -769 -770 -771 -772 -773 -774 -775 -776 -777 -778 -779 -780 -781 -782 -783 -784 -785 -786 -787 -788 -789 -790 -791 -792 -793 -794 -795 -796 -797 -798 -799 -800 -801 -802 -803 -804 -805 -806 -807 -808 -809 -810 -811 -812 -813 -814 -815 -816 -817 -818 -819 -820 -821 -822 -823 -824 -825 -826 -827 -828 -829 -830 -831 -832 -833 -834 -835 -836 -837 -838 -839 -840 -841 -842 -843 -844 -845 -846 -847 -848 -849 -850 -851 -852 -853 -854 -855 -856 -857 -858 -859 -860 -861 -862 -863 -864 -865 -866 -867 -868 -869 -870 -871 -872 -873 -874 -875 -876 -877 -878 -879 -880 -881 -882 -883 -884 -885 -886 -887 -888 -889 -890 -891 -892 -893 -894 -895 -896 -897 -898 -899 -900 -901 -902 -903 -904 -905 -906 -907 -908 -909 -910 -911 -912 -913 -914 -915 -916 -917 -918 -919 -920 -921 -922 -923 -924 -925 -926 -927 -928 -929 -930 -931 -932 -933 -934 -935 -936 -937 -938 -939 -940 -941 -942 -943 -944 -945 -946 -947 -948 -949 -950 -951 -952 -953 -954 -955 -956 -957 -958 -959 -960 -961 -962 -963 -964 -965 -966 -967 -968 -969 -970 -971 -972 -973 -974 -975 -976 -977 -978 -979 -980 -981 -982 -983 -984 -985 -986 -987 -988 -989 -990 -991 -992 -993 -994 -995 -996 -997 -998 -999 -1000 -1001 -1002 -1003 -1004 -1005 -1006 -1007 -1008 -1009 -1010 -1011 -1012 -1013 -1014 -1015 -1016 -1017 -1018 -1019 -1020 -1021 -1022 -1023 -1024 -1025 -1026 -1027 -1028 -1029 -1030 -1031 -1032 -1033 -1034 -1035 -1036 -1037 -1038 -1039 -1040 -1041 -1042 -1043 -1044 -1045 -1046 -1047 -1048  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
"use strict";
-const Homey = require("homey");
-const Logger = require("./Logger");
- 
-module.exports = class BaseLogicUnit extends Homey.Device {
-  async safeSetCapabilityValue(cap, value) {
-    if (this._isDeleting) return;
-    try {
-      if (!this.hasCapability(cap)) {
-        return;
-      }
-      await this.setCapabilityValue(cap, value);
-    } catch (e) {
-      const msg = e?.message || String(e);
-      if (e?.statusCode === 404 || /not\s*found/i.test(msg)) {
-        this.logger.debug("device.capability_skip_deleted", {
-          capability: cap,
-        });
-      } else {
-        this.logger.error("device.capability_update_failed", {
-          capability: cap,
-          message: msg,
-        });
-      }
-    }
-  }
- 
-  async onInit() {
-    const driverName = `Device: ${this.driver ? this.driver.id : "unknown-driver"}`;
-    this.logger = new Logger(this, driverName);
-    this.logger.device("device.initializing", {
-      name: this.getName(),
-    });
- 
-    if (!this.hasCapability("onoff")) {
-      await this.addCapability("onoff").catch((e) =>
-        this.logger.error("Failed to add 'onoff' capability", e),
-      );
-    }
-    if (!this.hasCapability("alarm_generic")) {
-      await this.addCapability("alarm_generic").catch((e) =>
-        this.logger.error("Failed to add 'alarm_generic' capability", e),
-      );
-    }
- 
-    this.numInputs = this.getData().numInputs ?? 2;
-    this.availableInputs = this.getAvailableInputIds();
-    this.logger.debug(
-      `Device initialized with ${this.numInputs} inputs: ${this.availableInputs.join(", ")}`,
-      {},
-    );
- 
-    await this.initializeFormulas();
-    await this.evaluateAllFormulasInitial();
-    this.startTimeoutChecks();
- 
-    // REGISTRATION IS REMOVED FROM HERE
-    this.logger.info("device.initialized", {
-      name: this.getName(),
-      count: this.numInputs,
-    });
-  }
- 
-  async onFlowCondition(args, state, checkType) {
-    this.logger.flow(`onFlowCondition called for checkType: '${checkType}'`, {
-      formulaId: args.formula?.id,
-    });
- 
-    if (checkType === "has_error") {
-      const hasTimeout = this.formulas.some((f) => f.timedOut === true);
-      this.logger.flow(`Condition 'has_any_error' check result: ${hasTimeout}`);
-      return hasTimeout;
-    }
- 
-    const cardIdFromArgs = args.cardId;
-    if (
-      (cardIdFromArgs === "formula_result_is_ld" ||
-        cardIdFromArgs === "formula_has_timed_out_ld") &&
-      !args.formula
-    ) {
-      this.logger.warn(
-        "onFlowCondition: _ld card called without formula on LU device. Checking first formula as fallback.",
-      );
-      if (this.formulas.length > 0) args.formula = this.formulas[0];
-      else return false;
-    }
- 
-    if (!args.formula || !args.formula.id) {
-      this.logger.warn(
-        "onFlowCondition: No formula specified in args for formula-specific check.",
-        {
-          checkType,
-        },
-      );
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const formula = this.formulas.find((f) => f.id === formulaId);
- 
-    if (!formula) {
-      this.logger.warn("onFlowCondition: Invalid formula ID received.", {
-        formulaId,
-      });
-      return false;
-    }
- 
-    if (checkType === "timeout") {
-      const isTimedOut = formula.timedOut === true;
-      this.logger.flow(
-        `Condition 'formula_has_timed_out' check for '${formula.name}': Result=${isTimedOut}`,
-      );
-      return isTimedOut;
-    } else if (typeof checkType === "boolean") {
-      const currentResult = formula.result;
-      const desiredResult = checkType;
-      const evaluatedResult = currentResult === true;
-      const conditionMet = evaluatedResult === desiredResult;
-      this.logger.flow(
-        `Condition 'formula_result_is' check for '${formula.name}': Current=${currentResult}, Desired=${desiredResult}, Match=${conditionMet}`,
-      );
-      return conditionMet;
-    } else {
-      this.logger.error(
-        `onFlowCondition: Unknown checkType received: '${checkType}'`,
-      );
-      return false;
-    }
-  }
- 
-  async onFlowActionSetInput(args, state) {
-    if (!args.formula || !args.input || !args.formula.id || !args.input.id) {
-      this.logger.warn("onFlowActionSetInput: Missing formula or input ID.", {
-        args,
-      });
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const inputId = args.input.id;
-    const value = args.value === "true";
-    this.logger.flow("onFlowActionSetInput: Setting input", {
-      formula: formulaId,
-      input: inputId,
-      value: value,
-    });
-    try {
-      await this.setInputForFormula(formulaId, inputId, value);
-      return true;
-    } catch (e) {
-      this.logger.error(
-        `Error during onFlowActionSetInput for ${formulaId}/${inputId}`,
-        e,
-      );
-      return false;
-    }
-  }
- 
-  async onFlowActionEvaluateFormula(args, state) {
-    const cardIdFromArgs = args.cardId;
-    if (
-      (!args.formula || !args.formula.id) &&
-      cardIdFromArgs === "evaluate_formula_ld"
-    ) {
-      this.logger.warn(
-        `onFlowActionEvaluateFormula (LD) called on LU device ${this.getName()}. Falling back to re-evaluate all.`,
-      );
-      return this.onFlowActionReEvaluateAll(args, state);
-    }
-    if (!args.formula || !args.formula.id) {
-      this.logger.warn("onFlowActionEvaluateFormula: Missing formula ID.", {
-        args,
-      });
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("onFlowActionEvaluateFormula: Invalid formula ID.", {
-        formulaId,
-      });
-      return false;
-    }
-    this.logger.flow(
-      `onFlowActionEvaluateFormula: Evaluating '${formula.name}' (Resetting locks)`,
-    );
-    try {
-      await this.evaluateFormula(formulaId, true);
-      return true;
-    } catch (e) {
-      this.logger.error(
-        `Error during onFlowActionEvaluateFormula for ${formulaId}`,
-        e,
-      );
-      return false;
-    }
-  }
- 
-  async onFlowActionClearError(args, state) {
-    const cardIdFromArgs = args.cardId;
-    if (
-      (!args.formula || !args.formula.id) &&
-      cardIdFromArgs === "clear_error_ld"
-    ) {
-      this.logger.warn(
-        `onFlowActionClearError (LD) called on LU device ${this.getName()}. Clearing all formula timeouts.`,
-      );
-      this.formulas.forEach((f) => {
-        f.timedOut = false;
-      });
-      this.logger.info("notifications.error_cleared_all", {});
-      return true;
-    }
-    if (!args.formula || !args.formula.id) {
-      this.logger.warn("onFlowActionClearError: Missing formula ID.", {
-        args,
-      });
-      return false;
-    }
-    const formulaId = args.formula.id;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("onFlowActionClearError: Invalid formula ID.", {
-        formulaId,
-      });
-      return false;
-    }
-    this.logger.flow(
-      `onFlowActionClearError: Clearing timeout state for '${formula.name}'`,
-    );
-    formula.timedOut = false;
-    this.logger.info("notifications.error_cleared", {
-      formulaName: formula.name,
-    });
-    return true;
-  }
- 
-  async onFlowActionReEvaluateAll(args, state) {
-    this.logger.flow(
-      "onFlowActionReEvaluateAll: Re-evaluating all formulas",
-      {},
-    );
-    try {
-      await this.evaluateAllFormulas();
-      return true;
-    } catch (e) {
-      this.logger.error("Error during onFlowActionReEvaluateAll", e);
-      return false;
-    }
-  }
- 
-  async setAllInputsFromFlow(args, state) {
-    const device = this;
-    const valuesJson = args.values;
-    this.logger.flow(
-      `Executing Action 'set_all_inputs' on device '${device.getName()}'`,
-    );
-    if (typeof valuesJson !== "string" || valuesJson.trim() === "") {
-      this.logger.warn(
-        `RunListener set_all_inputs: Empty or invalid JSON provided.`,
-      );
-      return false;
-    }
-    try {
-      const values = JSON.parse(valuesJson);
-      if (typeof values !== "object" || values === null) {
-        throw new Error("Parsed JSON is not an object.");
-      }
-      const promises = [];
-      const inputs = device.getAvailableInputIds
-        ? device.getAvailableInputIds()
-        : [];
-      const formulas = Array.isArray(device.formulas) ? device.formulas : [];
- 
-      for (const key in values) {
-        if (!inputs.includes(key.toLowerCase())) {
-          this.logger.warn(
-            `setAllInputsFromFlow: JSON contains invalid input key '${key}' for this device.`,
-          );
-        }
-      }
- 
-      for (const inputId of inputs) {
-        const key = inputId.toUpperCase();
-        if (values.hasOwnProperty(key)) {
-          formulas.forEach((f) => {
-            if (
-              f &&
-              f.enabled &&
-              typeof device.setInputForFormula === "function"
-            ) {
-              const boolValue =
-                values[key] === true ||
-                String(values[key]).toLowerCase() === "true";
-              promises.push(
-                device.setInputForFormula(f.id, inputId, boolValue),
-              );
-            }
-          });
-        }
-      }
-      await Promise.all(promises);
-      this.logger.info(`set_all_inputs OK for ${device.getName()}`, {});
-      return true;
-    } catch (e) {
-      this.logger.error(`Error set_all_inputs for ${device.getName()}`, e);
-      return false;
-    }
-  }
- 
-  async setInputForAllFormulasFromFlow(args, state) {
-    const device = this;
-    const inputId = args.input?.id;
-    const value = args.value === "true";
- 
-    this.logger.flow(
-      `Executing Action 'set_input' on device '${device.getName()}'`,
-      {
-        input: inputId,
-        value,
-      },
-    );
- 
-    if (!inputId || !this.availableInputs.includes(inputId)) {
-      this.logger.error(
-        `RunListener set_input: Invalid or missing input ID: ${inputId}. Available: ${this.availableInputs.join(",")}`,
-      );
- 
-      return false;
-    }
- 
-    const promises = [];
-    const formulas = Array.isArray(device.formulas) ? device.formulas : [];
-    try {
-      formulas.forEach((f) => {
-        if (f && f.enabled && typeof device.setInputForFormula === "function") {
-          promises.push(device.setInputForFormula(f.id, inputId, value));
-        }
-      });
-      await Promise.all(promises);
-      this.logger.info(`set_input OK for ${device.getName()}`, {});
-      return true;
-    } catch (e) {
-      this.logger.error(`Error set_input for ${device.getName()}`, e);
-      return false;
-    }
-  }
- 
-  getAvailableInputIds() {
-    const allInputs = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
-    const count = Math.max(
-      0,
-      Math.min(allInputs.length, Number(this.numInputs) || 0),
-    );
-    return allInputs.slice(0, count);
-  }
- 
-  getAvailableInputsUppercase() {
-    return this.availableInputs.map((i) => i.toUpperCase());
-  }
- 
-  initializeFormulas() {
-    this.logger.debug("Initializing formulas from settings...", {});
-    const settings = this.getSettings();
-    try {
-      const formulasData = settings.formulas
-        ? JSON.parse(settings.formulas)
-        : [];
-      if (!Array.isArray(formulasData)) {
-        this.logger.error("errors.invalid_formula", {
-          message: "Formulas setting is not an array.",
-        });
-        this.formulas = [];
-      } else {
-        this.formulas = formulasData.map((f) => ({
-          id: f.id || `formula_${Math.random().toString(16).slice(2)}`,
-          name: f.name || this.homey.__("formula.unnamed_formula"),
-          expression: f.expression || "",
-          enabled: f.enabled !== false,
-          timeout: Number(f.timeout) || 0,
-          firstImpression: !!f.firstImpression,
-          inputStates: {},
-          lockedInputs: {},
-          lastInputTime: null,
-          result: null,
-          timedOut: false,
-        }));
-      }
- 
-      this.formulas.forEach((formula) => {
-        this.availableInputs.forEach((id) => {
-          formula.inputStates[id] = "undefined";
-          formula.lockedInputs[id] = false;
-        });
-      });
-    } catch (e) {
-      this.logger.error("errors.invalid_formula", e);
-      this.formulas = [];
-    }
- 
-    if (this.formulas.length === 0) {
-      this.logger.warn(
-        "No valid formulas found in settings, creating default.",
-        {},
-      );
-      const defaultFormula = {
-        id: "formula_1",
-        name: this.homey.__
-          ? this.homey.__("formula.default_name_alt")
-          : this.homey.__("formula.default_name_alt_fallback"),
-        expression: this.getDefaultExpression(),
-        enabled: true,
-        timeout: 0,
-        firstImpression: false,
-        inputStates: {},
-        lockedInputs: {},
-        lastInputTime: null,
-        result: null,
-        timedOut: false,
-      };
- 
-      this.availableInputs.forEach((id) => {
-        defaultFormula.inputStates[id] = "undefined";
-        defaultFormula.lockedInputs[id] = false;
-      });
-      this.formulas = [defaultFormula];
-    }
-    this.logger.info("formula.initialized", {
-      count: this.formulas.length,
-    });
-    this.formulas.forEach((f) =>
-      this.logger.debug("formula.details", {
-        name: f.name,
-        expression: f.expression,
-        enabled: f.enabled,
-      }),
-    );
-  }
- 
-  getDefaultExpression() {
-    const inputs = this.getAvailableInputsUppercase();
-    return inputs.length > 0 ? inputs.join(" AND ") : "true";
-  }
- 
-  getFormulas() {
-    return this.formulas
-      .filter((f) => f.enabled)
-      .map((f) => ({
-        id: f.id,
-        name: f.name,
-        description:
-          f.expression ||
-          (this.homey.__
-            ? this.homey.__("formula.no_expression")
-            : "(no expression)"),
-      }));
-  }
- 
-  getInputOptions() {
-    return this.getAvailableInputsUppercase().map((input) => ({
-      id: input.toLowerCase(),
-      name: input,
-    }));
-  }
- 
-  validateExpression(expression) {
-    this.logger.debug(`Validating expression: "${expression || ""}"`, {});
-    if (
-      !expression ||
-      typeof expression !== "string" ||
-      expression.trim() === ""
-    ) {
-      return {
-        valid: false,
-        error: this.homey.__
-          ? this.homey.__("formula.expression_empty")
-          : "Expression empty",
-      };
-    }
-    const upper = expression.toUpperCase();
-    const inputs = this.getAvailableInputsUppercase();
-    if (!inputs.length && upper.match(/[A-J]/)) {
-      return {
-        valid: false,
-        error: this.homey.__("formula.error_inputs_used_but_none_configured"),
-      };
-    }
-    let validationInputs = inputs.length > 0 ? inputs : ["TEMP_VALIDATION_VAR"];
- 
-    const tokenRe = new RegExp(
-      `\\b(?:AND|OR|XOR|NOT)\\b|&&|\\|\\||\\^|!=|\\*|\\+|!|\\(|\\)|\\b(?:${validationInputs.join("|")})\\b|\\bTRUE\\b|\\bFALSE\\b`,
-      "gi",
-    );
-    const stripped = upper.replace(tokenRe, "").replace(/\s+/g, "");
-    if (stripped.length > 0) {
-      return {
-        valid: false,
-        error: this.homey.__
-          ? this.homey.__("formula.invalid_tokens", {
-              tokens: stripped,
-            })
-          : `Invalid tokens: ${stripped}`,
-      };
-    }
- 
-    let depth = 0;
-    for (const ch of upper) {
-      if (ch === "(") depth++;
-      else if (ch === ")") depth--;
-      if (depth < 0)
-        return {
-          valid: false,
-          error: this.homey.__("formula.error_unbalanced_parentheses"),
-        };
-    }
-    if (depth !== 0)
-      return {
-        valid: false,
-        error: "Unbalanced parentheses",
-      };
- 
-    // Attempt to evaluate with dummy values to catch syntax errors
-    let testExpr = upper
-      .replace(/\bAND\b|&|\*/gi, "&&") // Normalize AND operators
-      .replace(/\bOR\b|\||\+/gi, "||") // Normalize OR operators
-      .replace(/\bXOR\b|\^|!=/gi, "!=") // Normalize XOR operators (use JS !=)
-      .replace(/\bNOT\b|!/gi, "!"); // Normalize NOT operator
- 
-    // Replace input letters and TRUE/FALSE with boolean literals
-    for (const key of validationInputs) {
-      const re = new RegExp(`\\b${key}\\b`, "g");
-      testExpr = testExpr.replace(re, "true");
-    }
-    testExpr = testExpr
-      .replace(/\bTRUE\b/g, "true")
-      .replace(/\bFALSE\b/g, "false");
- 
-    // Try creating a function to catch final syntax errors
-    try {
-      const fn = new Function(`return ${testExpr};`);
-      fn(); // Execute once to be sure
-      return {
-        valid: true,
-      };
-    } catch (e) {
-      return {
-        valid: false,
-        error: this.homey.__
-          ? this.homey.__("formula.invalid_syntax", {
-              message: e.message,
-            })
-          : `Syntax error: ${e.message}`,
-      };
-    }
-  }
- 
-  parseExpression(expression) {
-    if (!expression || typeof expression !== "string") return [];
-    const inputs = this.getAvailableInputsUppercase();
-    if (!inputs.length) return [];
-    const varRe = new RegExp(`\\b(${inputs.join("|")})\\b`, "gi");
-    const matches = expression.match(varRe);
-    return matches ? [...new Set(matches.map((c) => c.toUpperCase()))] : [];
-  }
- 
-  async setInputForFormula(formulaId, inputId, value) {
-    if (this._isDeleting) return null;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("errors.invalid_formula", {
-        formulaId,
-      });
-      return null;
-    }
-    if (!this.availableInputs.includes(inputId)) {
-      this.logger.warn(
-        `setInputForFormula: Invalid inputId '${inputId}' for device with ${this.numInputs} inputs.`,
-      );
-      return null;
-    }
- 
-    if (
-      formula.firstImpression === true &&
-      formula.lockedInputs[inputId] === true
-    ) {
-      this.logger.debug("inputs.locked", {
-        input: inputId.toUpperCase(),
-        formula: formula.name,
-      });
-      return formula.result;
-    }
- 
-    const oldValue = formula.inputStates[inputId];
-    formula.inputStates[inputId] =
-      value === true || value === false ? value : "undefined";
-    formula.timedOut = false;
- 
-    if (
-      formula.firstImpression === true &&
-      formula.inputStates[inputId] !== "undefined" &&
-      formula.lockedInputs[inputId] !== true
-    ) {
-      formula.lockedInputs[inputId] = true;
-      this.logger.debug("inputs.locked_at_value", {
-        input: inputId.toUpperCase(),
-        value: formula.inputStates[inputId],
-        formula: formula.name,
-      });
-    }
- 
-    if (formula.inputStates[inputId] !== "undefined") {
-      formula.lastInputTime = Date.now();
-    }
- 
-    return await this.evaluateFormula(formulaId, false);
-  }
- 
-  async evaluateFormula(formulaId, resetLocks = false) {
-    if (this._isDeleting) return null;
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula || !formula.enabled) {
-      this.logger.debug("errors.invalid_formula", {
-        formulaId,
-      });
-      return null;
-    }
- 
-    if (resetLocks === true && formula.firstImpression === true) {
-      this.availableInputs.forEach((id) => {
-        formula.lockedInputs[id] = false;
-      });
-      this.logger.debug("inputs.unlocked", {
-        formula: formula.name,
-      });
-    }
- 
-    const expression = formula.expression;
-    if (!expression) {
-      this.logger.debug("formula.invalid", {
-        formula: formula.name,
-        reason: "No expression",
-      });
-      formula.result = null;
-      return null;
-    }
- 
-    const requiredInputs = this.parseExpression(expression);
-    const allDefined = requiredInputs.every(
-      (inputIdUpper) =>
-        formula.inputStates[inputIdUpper.toLowerCase()] !== "undefined",
-    );
- 
-    if (!allDefined && requiredInputs.length > 0) {
-      const missing = requiredInputs.filter(
-        (id) => formula.inputStates[id.toLowerCase()] === "undefined",
-      );
-      this.logger.debug("inputs.waiting", {
-        formula: formula.name,
-        missing: missing.join(", "),
-      });
-      return formula.result;
-    }
- 
-    const values = {};
-    this.availableInputs.forEach((id) => {
-      values[id.toUpperCase()] = formula.inputStates[id];
-    });
-    this.logger.debug("formula.evaluating", {
-      formula: formula.name,
-    });
- 
-    let evalExpression = expression
-      .replace(/\bAND\b|&|\*/gi, "&&")
-      .replace(/\bOR\b|\||\+/gi, "||")
-      .replace(/\bXOR\b|\^|!=/gi, "!=")
-      .replace(/\bNOT\b|!/gi, "!");
- 
-    for (const key in values) {
-      if (requiredInputs.includes(key) && values[key] !== "undefined") {
-        const re = new RegExp(`\\b${key}\\b`, "gi");
-        evalExpression = evalExpression.replace(
-          re,
-          String(values[key] === true),
-        );
-      }
-    }
-    evalExpression = evalExpression
-      .replace(/\bTRUE\b/gi, "true")
-      .replace(/\bFALSE\b/gi, "false");
-    this.logger.formula("formula.evaluating_expression", {
-      expression: expression,
-      evalExpression: evalExpression,
-    });
- 
-    try {
-      const evaluateFn = new Function(`return ${evalExpression};`);
-      const result = !!evaluateFn();
-      this.logger.debug("formula.evaluated", {
-        formula: formula.name,
-        result: result,
-      });
- 
-      const previous = formula.result;
-      formula.result = result;
-      formula.timedOut = false;
- 
-      const overallDeviceState = this.formulas.some(
-        (f) => f.enabled && f.result === true,
-      );
-      const currentOnOffState = this.getCapabilityValue("onoff");
- 
-      if (currentOnOffState !== overallDeviceState) {
-        await this.safeSetCapabilityValue("onoff", overallDeviceState);
-        await this.safeSetCapabilityValue("alarm_generic", overallDeviceState);
-        this.logger.info(
-          `Overall device capability updated: ${overallDeviceState}`,
-        );
-      }
- 
-      if (result !== previous && previous !== null) {
-        const triggerData = {
-          formula: {
-            id: formula.id,
-            name: formula.name,
-          },
-        };
-        const state = {
-          formulaId: formula.id,
-        };
-        const triggerCardId = result
-          ? "formula_changed_to_true"
-          : "formula_changed_to_false";
-        try {
-          this.logger.flow(
-            `Triggering flow '${triggerCardId}' for '${formula.name}'`,
-          );
-          const card = this.homey.flow.getDeviceTriggerCard(triggerCardId);
-          await card.trigger(this, triggerData, state);
-        } catch (e) {
-          if (e.message && e.message.includes("Invalid Flow Card ID")) {
-            this.logger.error(
-              `FATAL: Trigger card '${triggerCardId}' not found. Check app.json/compose flow definitions.`,
-              e,
-            );
-          } else {
-            this.logger.error("flow.trigger_error", e);
-          }
-        }
-      }
-      return result;
-    } catch (e) {
-      this.logger.error("errors.evaluation_failed", e);
-      formula.result = null;
-      return null;
-    }
-  }
- 
-  async evaluateAllFormulas() {
-    this.logger.info("notifications.reevaluating", {});
-    const results = [];
-    for (const formula of this.formulas) {
-      if (formula.enabled) {
-        if (formula.firstImpression) {
-          this.availableInputs.forEach((id) => {
-            formula.lockedInputs[id] = false;
-          });
-          this.logger.debug("inputs.unlocked", {
-            formula: formula.name,
-          });
-        }
-        const result = await this.evaluateFormula(formula.id, false);
-        results.push({
-          id: formula.id,
-          name: formula.name,
-          result,
-        });
-      } else {
-        formula.result = null;
-      }
-    }
-    const overallDeviceState = this.formulas.some(
-      (f) => f.enabled && f.result === true,
-    );
-    if (this.getCapabilityValue("onoff") !== overallDeviceState) {
-      await this.safeSetCapabilityValue("onoff", overallDeviceState);
-      await this.safeSetCapabilityValue("alarm_generic", overallDeviceState);
-      this.logger.info(
-        `Overall device capability updated after re-evaluating all: ${overallDeviceState}`,
-      );
-    }
-    this.logger.debug("formula.evaluated_count", {
-      count: results.length,
-    });
-    return results;
-  }
- 
-  async evaluateAllFormulasInitial() {
-    this.logger.info("evaluation.initial_complete", {});
-    let anyEvaluated = false;
- 
-    for (const formula of this.formulas) {
-      if (!formula.enabled) {
-        formula.result = null;
-        continue;
-      }
-      const expr = formula.expression;
-      if (!expr) {
-        formula.result = null;
-        continue;
-      }
-      const required = this.parseExpression(expr);
-      this.logger.debug("debug.checking_formula", {
-        name: formula.name,
-        expression: expr,
-      });
-      if (required.length > 0) {
-        this.logger.debug("debug.required_inputs", {
-          inputs: required.join(", "),
-        });
-      }
- 
-      const allDefined = required.every(
-        (id) => formula.inputStates[id.toLowerCase()] !== "undefined",
-      );
- 
-      if (allDefined || required.length === 0) {
-        this.logger.debug("formula.all_inputs_defined", {
-          name: formula.name,
-        });
-        const result = await this.evaluateFormula(formula.id);
-        if (result !== null) {
-          anyEvaluated = true;
-        }
-      } else {
-        const missing = required.filter(
-          (id) => formula.inputStates[id.toLowerCase()] === "undefined",
-        );
-        this.logger.debug("inputs.waiting", {
-          formula: formula.name,
-          missing: missing.join(", "),
-        });
-        formula.result = null;
-      }
-    }
- 
-    const finalState = this.formulas.some(
-      (f) => f.enabled && f.result === true,
-    );
- 
-    this.logger.info(
-      `Initial evaluation complete. Final state: ${finalState} (anyEvaluated=${anyEvaluated})`,
-    );
- 
-    await this.safeSetCapabilityValue("onoff", finalState);
-    await this.safeSetCapabilityValue("alarm_generic", finalState);
- 
-    if (
-      !anyEvaluated &&
-      this.formulas.some(
-        (f) =>
-          f.enabled &&
-          f.expression &&
-          this.parseExpression(f.expression).length > 0,
-      )
-    ) {
-      this.logger.warn("evaluation.no_formulas_ready", {});
-    }
-  }
- 
-  getFormulaResult(formulaId) {
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    if (!formula) {
-      this.logger.warn("errors.invalid_formula", {
-        formulaId,
-      });
-      return null;
-    }
-    this.logger.debug("formula.result_debug", {
-      name: formula.name,
-      id: formulaId,
-      result: formula.result,
-      type: typeof formula.result,
-    });
-    return formula.result === true;
-  }
- 
-  hasFormulaTimedOut(formulaId) {
-    const formula = this.formulas.find((f) => f.id === formulaId);
-    return !!(formula && formula.timedOut);
-  }
- 
-  startTimeoutChecks() {
-    if (this.timeoutInterval) {
-      clearInterval(this.timeoutInterval);
-    }
-    this.timeoutInterval = setInterval(() => {
-      this.checkTimeouts();
-    }, 1000);
-    this.logger.debug("Started timeout check interval.", {});
-  }
- 
-  checkTimeouts() {
-    const now = Date.now();
-    this.formulas.forEach((formula) => {
-      if (
-        !formula.enabled ||
-        formula.timedOut ||
-        !formula.timeout ||
-        formula.timeout <= 0
-      )
-        return;
- 
-      const requiredInputs = this.parseExpression(formula.expression);
-      if (
-        requiredInputs.length === 0 ||
-        requiredInputs.every(
-          (id) => formula.inputStates[id.toLowerCase()] !== "undefined",
-        )
-      ) {
-        return;
-      }
- 
-      if (!formula.lastInputTime) return;
- 
-      const timeoutMs = formula.timeout * 1000;
-      const elapsed = now - formula.lastInputTime;
- 
-      if (elapsed >= timeoutMs) {
-        this.logger.info("formula.timed_out", {
-          name: formula.name,
-          timeout: formula.timeout,
-        });
-        formula.timedOut = true;
- 
-        const triggerData = {
-          formula: {
-            id: formula.id,
-            name: formula.name,
-          },
-        };
-        const state = {
-          formulaId: formula.id,
-        };
-        try {
-          const card = this.homey.flow.getDeviceTriggerCard("formula_timeout");
-          card
-            .trigger(this, triggerData, state)
-            .catch((err) => this.logger.error("timeout.error", err));
-        } catch (e) {
-          if (e.message && e.message.includes("Invalid Flow Card ID")) {
-            this.logger.error(
-              `FATAL: Trigger card 'formula_timeout' not found. Check app.json/compose flow definitions.`,
-              e,
-            );
-          } else {
-            this.logger.error(
-              this.homey.__("errors.trigger_timeout_card_failed"),
-              e,
-            );
-          }
-        }
-      }
-    });
-  }
- 
-  async onSettings({ oldSettings, newSettings, changedKeys }) {
-    this.logger.info("settings.changed", {
-      keys: changedKeys.join(", "),
-    });
-    if (this.timeoutInterval) {
-      clearInterval(this.timeoutInterval);
-      this.timeoutInterval = null;
-    }
- 
-    await this.initializeFormulas();
- 
-    if (changedKeys.includes("formulas")) {
-      for (const formula of this.formulas) {
-        const validation = this.validateExpression(formula.expression);
-        if (!validation.valid) {
-          this.homey.notifications
-            .createNotification({
-              excerpt: this.homey.__("notifications.invalid_formula_config", {
-                formulaName: formula.name,
-                error: validation.error,
-              }),
-            })
-            .catch((e) =>
-              this.logger.error(
-                this.homey.__("errors.notification_failed_invalid_formula"),
-                e,
-              ),
-            );
-          this.logger.error(
-            `Invalid formula configuration saved for '${formula.name}'`,
-            {
-              error: validation.error,
-            },
-          );
-        }
-      }
-    }
- 
-    await this.evaluateAllFormulasInitial();
- 
-    this.startTimeoutChecks();
-    this.logger.info("settings.applied", {});
- 
-    const formatSettings = {};
-    let needsFormat = false;
-    if (changedKeys.includes("formulas")) {
-      try {
-        const parsed = JSON.parse(newSettings.formulas);
-        const formatted = JSON.stringify(parsed, null, 2);
-        if (formatted !== newSettings.formulas) {
-          formatSettings.formulas = formatted;
-          needsFormat = true;
-          this.logger.debug("settings.formatting", {
-            type: "formulas",
-          });
-        }
-      } catch (e) {}
-    }
-    if (needsFormat) {
-      setTimeout(async () => {
-        try {
-          this.logger.debug("settings.applying_formatted", {});
-          await this.setSettings(formatSettings);
-          this.logger.info("settings.auto_formatted", {});
-        } catch (e) {
-          this.logger.error("settings.format_failed", e);
-        }
-      }, 500);
-    }
-  }
- 
-  async onDeleted() {
-    this._isDeleting = true;
-    this.logger.device("device.deleted_cleanup", {
-      name: this.getName(),
-    });
-    if (this.timeoutInterval) {
-      clearInterval(this.timeoutInterval);
-      this.timeoutInterval = null;
-    }
-    this.logger.info("device.cleanup_complete", {
-      name: this.getName(),
-    });
-  }
-};
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/FormulaEvaluator.js.html b/no.tiwas.booleantoolbox/coverage/lcov-report/FormulaEvaluator.js.html deleted file mode 100644 index 7ea54bf..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/FormulaEvaluator.js.html +++ /dev/null @@ -1,1174 +0,0 @@ - - - - - - Code coverage report for FormulaEvaluator.js - - - - - - - - - -
-
-

All files FormulaEvaluator.js

-
- -
- 97.5% - Statements - 156/160 -
- - -
- 96.03% - Branches - 97/101 -
- - -
- 100% - Functions - 10/10 -
- - -
- 97.5% - Lines - 156/160 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -52x -52x -  -  -  -  -  -  -  -  -108x -6x -  -  -102x -102x -102x -  -102x -  -630x -242x -242x -  -  -  -388x -25x -25x -25x -  -363x -24x -24x -24x -  -  -  -339x -263x -263x -2x -2x -2x -  -261x -2x -2x -2x -  -259x -2x -2x -2x -  -  -  -  -333x -3x -3x -3x -  -330x -3x -3x -3x -  -327x -3x -3x -3x -  -324x -3x -3x -3x -  -321x -2x -2x -2x -  -319x -2x -2x -2x -  -  -  -317x -302x -302x -526x -526x -  -  -302x -  -  -302x -72x -230x -16x -214x -2x -212x -12x -200x -6x -194x -4x -190x -  -187x -  -3x -  -299x -  -  -  -15x -11x -11x -3x -8x -6x -  -2x -  -9x -9x -  -  -  -4x -  -  -93x -  -  -  -  -  -  -  -  -71x -71x -  -71x -1x -  -  -70x -  -64x -3x -  -  -61x -  -  -  -  -  -  -94x -  -88x -20x -20x -20x -  -  -  -  -  -  -  -88x -  -  -  -  -  -  -114x -  -108x -3x -3x -3x -  -  -  -  -  -  -  -108x -  -  -  -  -  -  -117x -  -113x -71x -71x -69x -  -  -  -  -  -  -  -111x -  -  -  -  -  -  -200x -12x -12x -12x -  -  -  -  -  -  -188x -  -  -  -  -  -  -188x -2x -  -  -186x -  -  -186x -24x -24x -24x -3x -  -21x -21x -  -  -  -162x -12x -12x -  -  -  -  -  -  -150x -149x -149x -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -270x -  -  -  -270x -  -14x -  -  -153x -3x -  -150x -  -  -11x -11x -  -  -  -  -92x -92x -  -90x -  -62x -  -21x -  -7x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -67x -60x -54x -  -  -  -1x
/**
- * AST-Based Formula Evaluator
- * 
- * Securely evaluates boolean formulas without using eval() or new Function()
- * by parsing expressions into an Abstract Syntax Tree (AST) and evaluating it.
- * 
- * Supported operators:
- * - AND: AND, &&, *, &
- * - OR: OR, ||, +, |
- * - XOR: XOR, ^, !=
- * - NOT: NOT, !
- * 
- * Supported values:
- * - Variables: A-J (case-insensitive)
- * - Literals: TRUE, FALSE, true, false, 1, 0
- * - Parentheses: ( )
- */
- 
-class FormulaEvaluator {
-  constructor() {
-    this.tokens = [];
-    this.position = 0;
-  }
- 
-  /**
-   * Tokenize the expression into tokens
-   * @param {string} expression - The formula expression to tokenize
-   * @returns {Array} Array of tokens
-   */
-  tokenize(expression) {
-    if (!expression || typeof expression !== 'string') {
-      throw new Error('Expression must be a non-empty string');
-    }
- 
-    const tokens = [];
-    const expr = expression.trim();
-    let i = 0;
- 
-    while (i < expr.length) {
-      // Skip whitespace
-      if (/\s/.test(expr[i])) {
-        i++;
-        continue;
-      }
- 
-      // Parentheses
-      if (expr[i] === '(') {
-        tokens.push({ type: 'LPAREN', value: '(' });
-        i++;
-        continue;
-      }
-      if (expr[i] === ')') {
-        tokens.push({ type: 'RPAREN', value: ')' });
-        i++;
-        continue;
-      }
- 
-      // Two-character operators
-      if (i < expr.length - 1) {
-        const twoChar = expr.substring(i, i + 2);
-        if (twoChar === '&&') {
-          tokens.push({ type: 'AND', value: '&&' });
-          i += 2;
-          continue;
-        }
-        if (twoChar === '||') {
-          tokens.push({ type: 'OR', value: '||' });
-          i += 2;
-          continue;
-        }
-        if (twoChar === '!=') {
-          tokens.push({ type: 'XOR', value: '!=' });
-          i += 2;
-          continue;
-        }
-      }
- 
-      // Single-character operators
-      if (expr[i] === '&') {
-        tokens.push({ type: 'AND', value: '&' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '*') {
-        tokens.push({ type: 'AND', value: '*' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '|') {
-        tokens.push({ type: 'OR', value: '|' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '+') {
-        tokens.push({ type: 'OR', value: '+' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '^') {
-        tokens.push({ type: 'XOR', value: '^' });
-        i++;
-        continue;
-      }
-      if (expr[i] === '!') {
-        tokens.push({ type: 'NOT', value: '!' });
-        i++;
-        continue;
-      }
- 
-      // Keywords and variables
-      if (/[a-zA-Z]/.test(expr[i])) {
-        let word = '';
-        while (i < expr.length && /[a-zA-Z0-9]/.test(expr[i])) {
-          word += expr[i];
-          i++;
-        }
- 
-        const upper = word.toUpperCase();
-        
-        // Keywords
-        if (upper === 'AND') {
-          tokens.push({ type: 'AND', value: 'AND' });
-        } else if (upper === 'OR') {
-          tokens.push({ type: 'OR', value: 'OR' });
-        } else if (upper === 'XOR') {
-          tokens.push({ type: 'XOR', value: 'XOR' });
-        } else if (upper === 'NOT') {
-          tokens.push({ type: 'NOT', value: 'NOT' });
-        } else if (upper === 'TRUE') {
-          tokens.push({ type: 'LITERAL', value: true });
-        } else if (upper === 'FALSE') {
-          tokens.push({ type: 'LITERAL', value: false });
-        } else if (/^[A-J]$/.test(upper)) {
-          // Variables A-J
-          tokens.push({ type: 'VARIABLE', value: upper });
-        } else {
-          throw new Error(`Invalid identifier: ${word}`);
-        }
-        continue;
-      }
- 
-      // Numbers (0 or 1 as boolean literals)
-      if (/[0-9]/.test(expr[i])) {
-        const num = expr[i];
-        if (num === '0') {
-          tokens.push({ type: 'LITERAL', value: false });
-        } else if (num === '1') {
-          tokens.push({ type: 'LITERAL', value: true });
-        } else {
-          throw new Error(`Invalid number: ${num}. Only 0 and 1 are allowed.`);
-        }
-        i++;
-        continue;
-      }
- 
-      // Unknown character
-      throw new Error(`Unexpected character: ${expr[i]}`);
-    }
- 
-    return tokens;
-  }
- 
-  /**
-   * Parse tokens into an Abstract Syntax Tree (AST)
-   * @param {Array} tokens - Array of tokens from tokenize()
-   * @returns {Object} AST root node
-   */
-  parse(tokens) {
-    this.tokens = tokens;
-    this.position = 0;
- 
-    if (tokens.length === 0) {
-      throw new Error('Empty expression');
-    }
- 
-    const ast = this.parseOr();
- 
-    if (this.position < this.tokens.length) {
-      throw new Error(`Unexpected token: ${this.tokens[this.position].value}`);
-    }
- 
-    return ast;
-  }
- 
-  /**
-   * Parse OR expressions (lowest precedence)
-   */
-  parseOr() {
-    let left = this.parseXor();
- 
-    while (this.position < this.tokens.length && this.tokens[this.position].type === 'OR') {
-      this.position++;
-      const right = this.parseXor();
-      left = {
-        type: 'BinaryOp',
-        operator: 'OR',
-        left: left,
-        right: right
-      };
-    }
- 
-    return left;
-  }
- 
-  /**
-   * Parse XOR expressions
-   */
-  parseXor() {
-    let left = this.parseAnd();
- 
-    while (this.position < this.tokens.length && this.tokens[this.position].type === 'XOR') {
-      this.position++;
-      const right = this.parseAnd();
-      left = {
-        type: 'BinaryOp',
-        operator: 'XOR',
-        left: left,
-        right: right
-      };
-    }
- 
-    return left;
-  }
- 
-  /**
-   * Parse AND expressions
-   */
-  parseAnd() {
-    let left = this.parseUnary();
- 
-    while (this.position < this.tokens.length && this.tokens[this.position].type === 'AND') {
-      this.position++;
-      const right = this.parseUnary();
-      left = {
-        type: 'BinaryOp',
-        operator: 'AND',
-        left: left,
-        right: right
-      };
-    }
- 
-    return left;
-  }
- 
-  /**
-   * Parse unary expressions (NOT)
-   */
-  parseUnary() {
-    if (this.position < this.tokens.length && this.tokens[this.position].type === 'NOT') {
-      this.position++;
-      const operand = this.parseUnary(); // Allow multiple NOTs
-      return {
-        type: 'UnaryOp',
-        operator: 'NOT',
-        operand: operand
-      };
-    }
- 
-    return this.parsePrimary();
-  }
- 
-  /**
-   * Parse primary expressions (literals, variables, parentheses)
-   */
-  parsePrimary() {
-    if (this.position >= this.tokens.length) {
-      throw new Error('Unexpected end of expression');
-    }
- 
-    const token = this.tokens[this.position];
- 
-    // Parentheses
-    if (token.type === 'LPAREN') {
-      this.position++;
-      const expr = this.parseOr();
-      if (this.position >= this.tokens.length || this.tokens[this.position].type !== 'RPAREN') {
-        throw new Error('Unbalanced parentheses');
-      }
-      this.position++;
-      return expr;
-    }
- 
-    // Literals
-    if (token.type === 'LITERAL') {
-      this.position++;
-      return {
-        type: 'Literal',
-        value: token.value
-      };
-    }
- 
-    // Variables
-    if (token.type === 'VARIABLE') {
-      this.position++;
-      return {
-        type: 'Variable',
-        name: token.value
-      };
-    }
- 
-    throw new Error(`Unexpected token: ${token.value}`);
-  }
- 
-  /**
-   * Evaluate an AST with given variable values
-   * @param {Object} ast - The AST to evaluate
-   * @param {Object} variables - Object mapping variable names to boolean values
-   * @returns {boolean} The result of the evaluation
-   */
-  evaluateAST(ast, variables = {}) {
-    Iif (!ast) {
-      throw new Error('Invalid AST');
-    }
- 
-    switch (ast.type) {
-      case 'Literal':
-        return !!ast.value;
- 
-      case 'Variable':
-        if (!(ast.name in variables)) {
-          throw new Error(`Undefined variable: ${ast.name}`);
-        }
-        return !!variables[ast.name];
- 
-      case 'UnaryOp':
-        Eif (ast.operator === 'NOT') {
-          return !this.evaluateAST(ast.operand, variables);
-        }
-        throw new Error(`Unknown unary operator: ${ast.operator}`);
- 
-      case 'BinaryOp':
-        const left = this.evaluateAST(ast.left, variables);
-        const right = this.evaluateAST(ast.right, variables);
- 
-        switch (ast.operator) {
-          case 'AND':
-            return left && right;
-          case 'OR':
-            return left || right;
-          case 'XOR':
-            return left !== right;
-          default:
-            throw new Error(`Unknown binary operator: ${ast.operator}`);
-        }
- 
-      default:
-        throw new Error(`Unknown AST node type: ${ast.type}`);
-    }
-  }
- 
-  /**
-   * Validate and evaluate an expression
-   * @param {string} expression - The formula expression
-   * @param {Object} variables - Object mapping variable names to boolean values
-   * @returns {boolean} The result of the evaluation
-   */
-  evaluate(expression, variables = {}) {
-    const tokens = this.tokenize(expression);
-    const ast = this.parse(tokens);
-    return this.evaluateAST(ast, variables);
-  }
-}
- 
-module.exports = FormulaEvaluator;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/Logger.js.html b/no.tiwas.booleantoolbox/coverage/lcov-report/Logger.js.html deleted file mode 100644 index a956465..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/Logger.js.html +++ /dev/null @@ -1,1303 +0,0 @@ - - - - - - Code coverage report for Logger.js - - - - - - - - - -
-
-

All files Logger.js

-
- -
- 0% - Statements - 0/131 -
- - -
- 0% - Branches - 0/88 -
- - -
- 0% - Functions - 0/28 -
- - -
- 0% - Lines - 0/131 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
"use strict";
- 
-/**
- * Advanced Logger for Homey Apps
- *
- * NEW: Supports global configuration via loggerConfig.js
- * FIX: Manually performs variable substitution as this.homey.__() fails to do so.
- * FIX (User): Added log level (e.g., [INFO ], [DEBUG]) to the console output.
- * FIX (User): Moved log level to the front of the output.
- * FIX (User): Fixed 'minLevel = 0 || ...' bug.
- * FIX (User): Added config loaded logging and static cache.
- * FIX (User): Moved config log to appear *after* the banner.
- * FIX (User): Fixed bug in _getDefaultConfig catch block return shape.
- * FIX (User): Fixed null-logging in banner().
- * FIX (User): Reverted info() to log as INFO. Code should be explicit.
- */
- 
-class Logger {
-  static LEVELS = {
-    DEBUG: 0,
-    INFO: 1,
-    WARN: 2,
-    ERROR: 3,
-    NONE: 4,
-  };
- 
-  static SYMBOLS = {
-    DEBUG: "🔍",
-    INFO: "✅",
-    WARN: "⚠️",
-    ERROR: "❌",
-    TIMER: "⏱️",
-    FORMULA: "📐",
-    INPUT: "📥",
-    OUTPUT: "📤",
-    DEVICE: "🔌",
-    API: "🌐",
-    FLOW: "🔄",
-  };
- 
-  static _globalConfig = null;
- 
-  /**
-   * Get default configuration
-   * Tries to load from loggerConfig.js, falls back to defaults
-   * @private
-   */
-  static _getDefaultConfig() {
-    // Use cache if available
-    if (Logger._globalConfig) {
-      return Logger._globalConfig;
-    }
- 
-    try {
-      const config = require("./loggerConfig");
-      Logger._globalConfig = { config: config, source: "custom" };
-      return Logger._globalConfig;
-    } catch (e) {
-      const defaultConfig = {
-        defaultLevel: "DEBUG", // ENDRET: Fallback til DEBUG
-        categoryLevels: {},
-        options: {},
-      };
-      // --- FIKS: Returner samme objektform som i 'try' ---
-      Logger._globalConfig = { config: defaultConfig, source: "default" };
-      return Logger._globalConfig;
-      // --- SLUTT FIKS ---
-    }
-  }
- 
-  constructor(context, category = "App", options = {}) {
-    // --- FIKS I KONSTRUKTØR ---
-    // Finner det *ekte* homey-objektet uansett om 'context' er App eller Device
-    if (context && context.homey) {
-      this.homey = context.homey;
-    } else if (context && context.app && context.app.homey) {
-      // Fallback
-      this.homey = context.app.homey;
-    } else {
-      console.error(
-        `Logger: Could not find 'homey' object on context for category '${category}'.`,
-      );
-      // Oppretter en dummy for å unngå krasj
-      this.homey = {
-        app: { log: console.log, error: console.error },
-        __: (s) => s, // Returnerer bare nøkkelen
-      };
-    }
-    // --- SLUTT FIKS ---
- 
-    this.context = context;
-    this.category = category;
- 
-    // Load global config
-    const { config: globalConfig, source: configSource } =
-      Logger._getDefaultConfig();
-    this.configSource = configSource; // Lagrer kilden for bruk i banner()
- 
-    // Determine log level (priority: options > categoryLevels > defaultLevel)
-    let level = options.level;
-    if (!level && globalConfig.categoryLevels[category]) {
-      level = globalConfig.categoryLevels[category];
-    }
-    if (!level) {
-      level = globalConfig.defaultLevel || "DEBUG"; // ENDRET: Fallback til DEBUG
-    }
- 
-    this.options = {
-      level: level.toUpperCase(),
-      timestamps:
-        options.timestamps || globalConfig.options.timestamps || false,
-      colors: options.colors || globalConfig.options.colors || false,
-    }; // --- FIKS: Manglende } lagt til ---
- 
-    // --- FIKS: Korrekt sjekk for 0 (DEBUG) ---
-    if (Logger.LEVELS[this.options.level] !== undefined) {
-      this.minLevel = Logger.LEVELS[this.options.level];
-    } else {
-      this.minLevel = Logger.LEVELS.DEBUG; // Fallback
-    }
-    // --- SLUTT FIKS ---
- 
-    this.timers = new Map();
-  }
- 
-  setLevel(level) {
-    const upperLevel = level.toUpperCase();
-    if (Logger.LEVELS[upperLevel] !== undefined) {
-      this.minLevel = Logger.LEVELS[upperLevel];
-      this.options.level = upperLevel;
-    }
-  }
- 
-  getLevel() {
-    return this.options.level;
-  }
- 
-  _shouldLog(level) {
-    return Logger.LEVELS[level] >= this.minLevel;
-  }
- 
-  _getPrefix(symbol) {
-    return `${symbol} [${this.category}]`;
-  }
- 
-  _formatMessage(keyOrMessage, data) {
-    let message = keyOrMessage; // Start with the original key/message
- 
-    // 1. Attempt Translation IF it looks like a key and __ exists
-    if (
-      typeof keyOrMessage === "string" &&
-      keyOrMessage.includes(".") &&
-      this.homey &&
-      typeof this.homey.__ === "function"
-    ) {
-      try {
-        const translated = this.homey.__(keyOrMessage, data);
-        // Use translation ONLY if it's a non-empty string and DIFFERENT from the key
-        if (
-          typeof translated === "string" &&
-          translated.trim() !== "" &&
-          translated !== keyOrMessage
-        ) {
-          message = translated;
-        }
-        // If translated is null, undefined, empty, or same as key, 'message' remains keyOrMessage
-      } catch (e) {
-        // If translation throws error, 'message' remains keyOrMessage
-        // console.error(`Logger: Error during this.homey.__ for key '${keyOrMessage}'. Using key.`, e);
-      }
-    }
- 
-    // 2. Ensure 'message' is now DEFINITELY a string (fallback if translation failed badly)
-    let messageStr = String(message ?? keyOrMessage); // Use keyOrMessage if message became null/undefined
- 
-    // 3. Perform Manual Substitution on the resulting string
-    if (data && typeof data === "object" && data !== null) {
-      try {
-        messageStr = messageStr.replace(/\{([^{}]+)\}/g, (match, key) => {
-          // Use value from data if exists and is not null/undefined, otherwise keep placeholder
-          return data.hasOwnProperty(key) &&
-            data[key] !== null &&
-            data[key] !== undefined
-            ? String(data[key]) // Ensure replacement is a string
-            : match;
-        });
-      } catch (e) {
-        console.error(
-          `Logger: Manual substitution failed for: "${messageStr}"`,
-          e,
-        );
-        // messageStr remains as it was before substitution attempt
-      }
-    }
- 
-    // Return the final string
-    return messageStr;
-  }
- 
-  _log(level, symbol, keyOrMessage, data) {
-    if (!this._shouldLog(level)) {
-      return;
-    }
- 
-    // --- FIKS: Log-nivå lagt til ---
-    const levelString = `[${level.padEnd(5)}]`;
-    const prefix = this._getPrefix(symbol);
-    const formattedMessage = this._formatMessage(keyOrMessage, data);
- 
-    try {
-      // Bruk ALLTID this.homey.app.log
-      // --- FIKS: Flyttet levelString FØRST ---
-      this.homey.app.log(levelString, prefix, formattedMessage);
-    } catch (error) {
-      console.error("Logger internal error:", error);
-      console.log(levelString, prefix, formattedMessage); // Fallback til console.log
-    }
-  }
- 
-  _logError(keyOrMessage, error) {
-    if (!this._shouldLog("ERROR")) {
-      return;
-    }
- 
-    // --- FIKS: Log-nivå lagt til ---
-    const levelString = `[${"ERROR".padEnd(5)}]`;
- 
-    const prefix = this._getPrefix(Logger.SYMBOLS.ERROR);
- 
-    // Data-objektet kan være gjemt i 'error' hvis det ikke er en ekte Error
-    let data = error instanceof Error ? null : error;
-    const formattedMessage = this._formatMessage(keyOrMessage, data);
- 
-    try {
-      // --- FIKS: Flyttet levelString FØRST ---
-      this.homey.app.error(levelString, prefix, formattedMessage);
-      if (error instanceof Error) {
-        this.homey.app.error(error); // Logg stack trace hvis det er en Error
-      } else if (data) {
-        // Hvis 'error' bare var data, er den allerede i formattedMessage
-      }
-    } catch (err) {
-      console.error("Logger internal error (error):", err);
-      console.error(levelString, prefix, formattedMessage);
-      if (error) {
-        console.error(error);
-      }
-    }
-  }
- 
-  debug(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.DEBUG, message, data);
-  }
- 
-  // --- FIKS: info() logger nå som INFO ---
-  info(message, data) {
-    this._log("INFO", Logger.SYMBOLS.INFO, message, data);
-  }
- 
-  warn(message, data) {
-    this._log("WARN", Logger.SYMBOLS.WARN, message, data);
-  }
- 
-  error(message, error) {
-    this._logError(message, error);
-  }
- 
-  formula(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.FORMULA, message, data);
-  }
- 
-  input(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.INPUT, message, data);
-  }
- 
-  output(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.OUTPUT, message, data);
-  }
- 
-  // --- FIKS: device() logger nå som DEBUG ---
-  device(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.DEVICE, message, data);
-  }
- 
-  api(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.API, message, data);
-  }
- 
-  // --- FIKS: flow() logger nå som DEBUG ---
-  flow(message, data) {
-    this._log("DEBUG", Logger.SYMBOLS.FLOW, message, data);
-  }
- 
-  timeStart(label) {
-    this.timers.set(label, Date.now());
-    if (this._shouldLog("DEBUG")) {
-      this._log("DEBUG", Logger.SYMBOLS.TIMER, `Timer started: ${label}`);
-    }
-  }
- 
-  timeEnd(label) {
-    const startTime = this.timers.get(label);
-    if (!startTime) {
-      this.warn(`Timer '${label}' was not started`);
-      return 0;
-    }
- 
-    const duration = Date.now() - startTime;
-    this.timers.delete(label);
- 
-    if (this._shouldLog("DEBUG")) {
-      this._log(
-        "DEBUG",
-        Logger.SYMBOLS.TIMER,
-        `Timer '${label}': ${duration}ms`,
-      );
-    }
- 
-    return duration;
-  }
- 
-  dump(label, object) {
-    if (!this._shouldLog("DEBUG")) {
-      return;
-    }
- 
-    try {
-      const formatted = JSON.stringify(object, null, 2);
-      this._log("DEBUG", Logger.SYMBOLS.DEBUG, `${label}:\n${formatted}`);
-    } catch (error) {
-      this._log("DEBUG", Logger.SYMBOLS.DEBUG, `${label}:`, object);
-    }
-  }
- 
-  once(key, level, message, data) {
-    if (!this._onceKeys) {
-      this._onceKeys = new Set();
-    }
- 
-    if (this._onceKeys.has(key)) {
-      return;
-    }
- 
-    this._onceKeys.add(key);
-    const method = level.toLowerCase();
-    if (typeof this[method] === "function") {
-      this[method](message, data);
-    } else {
-      console.warn(
-        `Logger: Invalid level "${level}" provided to 'once' method.`,
-      );
-    }
-  }
- 
-  child(subCategory) {
-    return new Logger(
-      this.context,
-      `${this.category}:${subCategory}`,
-      this.options,
-    );
-  }
- 
-  separator() {
-    if (this._shouldLog("DEBUG")) {
-      this._log("DEBUG", "", "─".repeat(50));
-    }
-  }
- 
-  banner(message) {
-    if (this._shouldLog("INFO")) {
-      const line = "═".repeat(message.length + 4);
-      // Banner kaller logg-funksjonen direkte for å unngå prefiks
-      try {
-        this.homey.app.log(line);
-        this.homey.app.log(`  ${message}  `);
-        this.homey.app.log(line);
- 
-        // --- FIKS: Bruker this.homey.app.log direkte ---
-        // _log() feilet fordi this.homey.__() ikke var klar for strenger
-        // som ikke var locale-nøkler på dette tidlige stadiet.
-        if (this.configSource) {
-          const configMessage =
-            this.configSource === "custom"
-              ? "Loaded custom configuration from ./loggerConfig.js"
-              : "Using default log settings (./loggerConfig.js not found)";
- 
-          // Logger med [INFO] og 🔍-symbolet, som du foreslo
-          // Bruker this.homey.app.log direkte for å unngå _formatMessage-feil
-          this.homey.app.log(
-            `[INFO ] ${Logger.SYMBOLS.DEBUG} [${this.category}]`,
-            configMessage,
-          );
- 
-          // Nullstill, så den ikke logger dette igjen hvis banner() kalles på nytt
-          this.configSource = null;
-        }
-        // --- SLUTT FIKS ---
-      } catch (e) {
-        console.log(line);
-        console.log(`  ${message}  `);
-        console.log(line);
-      }
-    }
-  }
-}
- 
-module.exports = Logger;
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/base.css b/no.tiwas.booleantoolbox/coverage/lcov-report/base.css deleted file mode 100644 index f418035..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/block-navigation.js b/no.tiwas.booleantoolbox/coverage/lcov-report/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/favicon.png b/no.tiwas.booleantoolbox/coverage/lcov-report/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 16.79% - Statements - 156/929 -
- - -
- 16.75% - Branches - 97/579 -
- - -
- 8.06% - Functions - 10/124 -
- - -
- 17.25% - Lines - 156/904 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
BaseLogicDriver.js -
-
0%0/1780%0/1070%0/190%0/174
BaseLogicUnit.js -
-
0%0/4590%0/2830%0/670%0/438
FormulaEvaluator.js -
-
97.5%156/16096.03%97/101100%10/1097.5%156/160
Logger.js -
-
0%0/1310%0/880%0/280%0/131
loggerConfig.js -
-
0%0/1100%0/0100%0/00%0/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/loggerConfig.js.html b/no.tiwas.booleantoolbox/coverage/lcov-report/loggerConfig.js.html deleted file mode 100644 index 3d9d9bc..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/loggerConfig.js.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - Code coverage report for loggerConfig.js - - - - - - - - - -
-
-

All files loggerConfig.js

-
- -
- 0% - Statements - 0/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 0% - Lines - 0/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
'use strict';
- 
-/**
- * Global Logger Configuration for Boolean Toolbox
- *
- * This is the central configuration for all logging in the app.
- * Place this file in: lib/loggerConfig.js
- *
- * USAGE:
- * - Adjust `defaultLevel` to change the standard log level for all modules.
- * - Add specific class names to `categoryLevels` for finer control over individual modules.
- * - The Logger will automatically pick up this file if it exists.
- */
-module.exports = {
-  // Default log level for any logger not specified below.
-  // Options: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
-  defaultLevel: 'debug',
- 
-  // Override log levels for specific categories (class names).
-  // This helps in reducing noise or enabling detailed logs for specific parts of the app.
-  categoryLevels: {
-    // Example: Show detailed logging for a specific driver during debugging.
-    // 'MyDeviceName': 'DEBUG',
- 
-    // Reduce noise from base classes which are generally stable.
-    // 'BaseLogicDriver': 'INFO',
-    // 'BaseLogicUnit': 'INFO',
- 
-    // Always show important app-level messages.
-    // 'App': 'INFO',
-  },
- 
-  // Global options (rarely needed).
-  options: {
-    timestamps: false, // Homey already adds timestamps to its logs.
-    colors: false,     // Not supported in the Homey development console.
-  },
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/prettify.css b/no.tiwas.booleantoolbox/coverage/lcov-report/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/prettify.js b/no.tiwas.booleantoolbox/coverage/lcov-report/prettify.js deleted file mode 100644 index b322523..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/sort-arrow-sprite.png b/no.tiwas.booleantoolbox/coverage/lcov-report/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/no.tiwas.booleantoolbox/coverage/lcov-report/sorter.js b/no.tiwas.booleantoolbox/coverage/lcov-report/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov-report/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/no.tiwas.booleantoolbox/coverage/lcov.info b/no.tiwas.booleantoolbox/coverage/lcov.info deleted file mode 100644 index 2119657..0000000 --- a/no.tiwas.booleantoolbox/coverage/lcov.info +++ /dev/null @@ -1,1776 +0,0 @@ -TN: -SF:lib\BaseLogicDriver.js -FN:5,formulaAutocompleteHelper -FN:23,(anonymous_1) -FN:37,inputAutocompleteHelper -FN:55,(anonymous_3) -FN:73,(anonymous_4) -FN:118,(anonymous_5) -FN:133,(anonymous_6) -FN:136,(anonymous_7) -FN:188,(anonymous_8) -FN:191,(anonymous_9) -FN:259,(anonymous_10) -FN:262,(anonymous_11) -FN:306,(anonymous_12) -FN:315,(anonymous_13) -FN:362,(anonymous_14) -FN:386,(anonymous_15) -FN:420,(anonymous_16) -FN:422,(anonymous_17) -FN:468,(anonymous_18) -FNF:19 -FNH:0 -FNDA:0,formulaAutocompleteHelper -FNDA:0,(anonymous_1) -FNDA:0,inputAutocompleteHelper -FNDA:0,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:0,(anonymous_5) -FNDA:0,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:0,(anonymous_8) -FNDA:0,(anonymous_9) -FNDA:0,(anonymous_10) -FNDA:0,(anonymous_11) -FNDA:0,(anonymous_12) -FNDA:0,(anonymous_13) -FNDA:0,(anonymous_14) -FNDA:0,(anonymous_15) -FNDA:0,(anonymous_16) -FNDA:0,(anonymous_17) -FNDA:0,(anonymous_18) -DA:2,0 -DA:3,0 -DA:6,0 -DA:7,0 -DA:8,0 -DA:11,0 -DA:13,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:19,0 -DA:21,0 -DA:22,0 -DA:24,0 -DA:29,0 -DA:33,0 -DA:38,0 -DA:39,0 -DA:40,0 -DA:43,0 -DA:45,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:51,0 -DA:53,0 -DA:54,0 -DA:56,0 -DA:61,0 -DA:65,0 -DA:71,0 -DA:74,0 -DA:75,0 -DA:78,0 -DA:79,0 -DA:80,0 -DA:81,0 -DA:82,0 -DA:83,0 -DA:84,0 -DA:85,0 -DA:86,0 -DA:87,0 -DA:93,0 -DA:98,0 -DA:100,0 -DA:105,0 -DA:106,0 -DA:109,0 -DA:110,0 -DA:112,0 -DA:119,0 -DA:122,0 -DA:133,0 -DA:134,0 -DA:135,0 -DA:136,0 -DA:137,0 -DA:145,0 -DA:150,0 -DA:154,0 -DA:162,0 -DA:188,0 -DA:189,0 -DA:190,0 -DA:191,0 -DA:192,0 -DA:193,0 -DA:194,0 -DA:197,0 -DA:198,0 -DA:199,0 -DA:204,0 -DA:207,0 -DA:211,0 -DA:218,0 -DA:224,0 -DA:227,0 -DA:233,0 -DA:237,0 -DA:245,0 -DA:259,0 -DA:260,0 -DA:261,0 -DA:262,0 -DA:263,0 -DA:264,0 -DA:265,0 -DA:266,0 -DA:269,0 -DA:270,0 -DA:271,0 -DA:272,0 -DA:275,0 -DA:278,0 -DA:281,0 -DA:287,0 -DA:293,0 -DA:297,0 -DA:307,0 -DA:308,0 -DA:309,0 -DA:313,0 -DA:316,0 -DA:318,0 -DA:319,0 -DA:320,0 -DA:323,0 -DA:326,0 -DA:327,0 -DA:328,0 -DA:329,0 -DA:330,0 -DA:332,0 -DA:333,0 -DA:336,0 -DA:340,0 -DA:341,0 -DA:343,0 -DA:347,0 -DA:351,0 -DA:355,0 -DA:363,0 -DA:364,0 -DA:370,0 -DA:374,0 -DA:376,0 -DA:377,0 -DA:378,0 -DA:382,0 -DA:384,0 -DA:387,0 -DA:393,0 -DA:394,0 -DA:395,0 -DA:396,0 -DA:397,0 -DA:398,0 -DA:399,0 -DA:400,0 -DA:401,0 -DA:402,0 -DA:403,0 -DA:404,0 -DA:406,0 -DA:407,0 -DA:411,0 -DA:413,0 -DA:415,0 -DA:416,0 -DA:421,0 -DA:422,0 -DA:423,0 -DA:424,0 -DA:426,0 -DA:427,0 -DA:429,0 -DA:433,0 -DA:437,0 -DA:441,0 -DA:442,0 -DA:445,0 -DA:455,0 -DA:459,0 -DA:461,0 -DA:462,0 -DA:465,0 -DA:469,0 -DA:470,0 -DA:471,0 -DA:472,0 -DA:476,0 -DA:477,0 -DA:478,0 -LF:174 -LH:0 -BRDA:7,0,0,0 -BRDA:7,0,1,0 -BRDA:7,1,0,0 -BRDA:7,1,1,0 -BRDA:15,2,0,0 -BRDA:15,2,1,0 -BRDA:21,3,0,0 -BRDA:21,3,1,0 -BRDA:24,4,0,0 -BRDA:24,4,1,0 -BRDA:24,4,2,0 -BRDA:24,4,3,0 -BRDA:39,5,0,0 -BRDA:39,5,1,0 -BRDA:39,6,0,0 -BRDA:39,6,1,0 -BRDA:47,7,0,0 -BRDA:47,7,1,0 -BRDA:53,8,0,0 -BRDA:53,8,1,0 -BRDA:56,9,0,0 -BRDA:56,9,1,0 -BRDA:56,9,2,0 -BRDA:56,9,3,0 -BRDA:84,10,0,0 -BRDA:84,10,1,0 -BRDA:84,11,0,0 -BRDA:84,11,1,0 -BRDA:86,12,0,0 -BRDA:86,12,1,0 -BRDA:105,13,0,0 -BRDA:105,13,1,0 -BRDA:138,14,0,0 -BRDA:138,14,1,0 -BRDA:138,14,2,0 -BRDA:138,14,3,0 -BRDA:138,14,4,0 -BRDA:193,15,0,0 -BRDA:193,15,1,0 -BRDA:198,16,0,0 -BRDA:198,16,1,0 -BRDA:211,17,0,0 -BRDA:211,17,1,0 -BRDA:224,18,0,0 -BRDA:224,18,1,0 -BRDA:264,19,0,0 -BRDA:264,19,1,0 -BRDA:265,20,0,0 -BRDA:265,20,1,0 -BRDA:269,21,0,0 -BRDA:269,21,1,0 -BRDA:271,22,0,0 -BRDA:271,22,1,0 -BRDA:281,23,0,0 -BRDA:281,23,1,0 -BRDA:308,24,0,0 -BRDA:308,24,1,0 -BRDA:318,25,0,0 -BRDA:318,25,1,0 -BRDA:318,26,0,0 -BRDA:318,26,1,0 -BRDA:319,27,0,0 -BRDA:319,27,1,0 -BRDA:327,28,0,0 -BRDA:327,28,1,0 -BRDA:329,29,0,0 -BRDA:329,29,1,0 -BRDA:332,30,0,0 -BRDA:332,30,1,0 -BRDA:364,31,0,0 -BRDA:364,31,1,0 -BRDA:365,32,0,0 -BRDA:365,32,1,0 -BRDA:365,32,2,0 -BRDA:365,32,3,0 -BRDA:374,33,0,0 -BRDA:374,33,1,0 -BRDA:377,34,0,0 -BRDA:377,34,1,0 -BRDA:377,35,0,0 -BRDA:377,35,1,0 -BRDA:382,36,0,0 -BRDA:382,36,1,0 -BRDA:387,37,0,0 -BRDA:387,37,1,0 -BRDA:387,38,0,0 -BRDA:387,38,1,0 -BRDA:388,39,0,0 -BRDA:388,39,1,0 -BRDA:393,40,0,0 -BRDA:393,40,1,0 -BRDA:394,41,0,0 -BRDA:394,41,1,0 -BRDA:397,42,0,0 -BRDA:397,42,1,0 -BRDA:398,43,0,0 -BRDA:398,43,1,0 -BRDA:401,44,0,0 -BRDA:401,44,1,0 -BRDA:406,45,0,0 -BRDA:406,45,1,0 -BRDA:416,46,0,0 -BRDA:416,46,1,0 -BRDA:437,47,0,0 -BRDA:437,47,1,0 -BRDA:471,48,0,0 -BRDA:471,48,1,0 -BRF:107 -BRH:0 -end_of_record -TN: -SF:lib\BaseLogicUnit.js -FN:6,(anonymous_0) -FN:28,(anonymous_1) -FN:36,(anonymous_2) -FN:41,(anonymous_3) -FN:64,(anonymous_4) -FN:70,(anonymous_5) -FN:98,(anonymous_6) -FN:130,(anonymous_7) -FN:157,(anonymous_8) -FN:175,(anonymous_9) -FN:197,(anonymous_10) -FN:206,(anonymous_11) -FN:219,(anonymous_12) -FN:236,(anonymous_13) -FN:250,(anonymous_14) -FN:284,(anonymous_15) -FN:309,(anonymous_16) -FN:333,(anonymous_17) -FN:347,(anonymous_18) -FN:356,(anonymous_19) -FN:357,(anonymous_20) -FN:360,(anonymous_21) -FN:373,(anonymous_22) -FN:388,(anonymous_23) -FN:389,(anonymous_24) -FN:420,(anonymous_25) -FN:429,(anonymous_26) -FN:438,(anonymous_27) -FN:443,(anonymous_28) -FN:445,(anonymous_29) -FN:446,(anonymous_30) -FN:457,(anonymous_31) -FN:458,(anonymous_32) -FN:464,(anonymous_33) -FN:555,(anonymous_34) -FN:561,(anonymous_35) -FN:564,(anonymous_36) -FN:566,(anonymous_37) -FN:616,(anonymous_38) -FN:618,(anonymous_39) -FN:627,(anonymous_40) -FN:647,(anonymous_41) -FN:653,(anonymous_42) -FN:663,(anonymous_43) -FN:706,(anonymous_44) -FN:756,(anonymous_45) -FN:762,(anonymous_46) -FN:780,(anonymous_47) -FN:795,(anonymous_48) -FN:821,(anonymous_49) -FN:834,(anonymous_50) -FN:845,(anonymous_51) -FN:858,(anonymous_52) -FN:868,(anonymous_53) -FN:869,(anonymous_54) -FN:885,(anonymous_55) -FN:886,(anonymous_56) -FN:890,(anonymous_57) -FN:894,(anonymous_58) -FN:900,(anonymous_59) -FN:902,(anonymous_60) -FN:915,(anonymous_61) -FN:946,(anonymous_62) -FN:964,(anonymous_63) -FN:986,(anonymous_64) -FN:1023,(anonymous_65) -FN:1035,(anonymous_66) -FNF:67 -FNH:0 -FNDA:0,(anonymous_0) -FNDA:0,(anonymous_1) -FNDA:0,(anonymous_2) -FNDA:0,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:0,(anonymous_5) -FNDA:0,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:0,(anonymous_8) -FNDA:0,(anonymous_9) -FNDA:0,(anonymous_10) -FNDA:0,(anonymous_11) -FNDA:0,(anonymous_12) -FNDA:0,(anonymous_13) -FNDA:0,(anonymous_14) -FNDA:0,(anonymous_15) -FNDA:0,(anonymous_16) -FNDA:0,(anonymous_17) -FNDA:0,(anonymous_18) -FNDA:0,(anonymous_19) -FNDA:0,(anonymous_20) -FNDA:0,(anonymous_21) -FNDA:0,(anonymous_22) -FNDA:0,(anonymous_23) -FNDA:0,(anonymous_24) -FNDA:0,(anonymous_25) -FNDA:0,(anonymous_26) -FNDA:0,(anonymous_27) -FNDA:0,(anonymous_28) -FNDA:0,(anonymous_29) -FNDA:0,(anonymous_30) -FNDA:0,(anonymous_31) -FNDA:0,(anonymous_32) -FNDA:0,(anonymous_33) -FNDA:0,(anonymous_34) -FNDA:0,(anonymous_35) -FNDA:0,(anonymous_36) -FNDA:0,(anonymous_37) -FNDA:0,(anonymous_38) -FNDA:0,(anonymous_39) -FNDA:0,(anonymous_40) -FNDA:0,(anonymous_41) -FNDA:0,(anonymous_42) -FNDA:0,(anonymous_43) -FNDA:0,(anonymous_44) -FNDA:0,(anonymous_45) -FNDA:0,(anonymous_46) -FNDA:0,(anonymous_47) -FNDA:0,(anonymous_48) -FNDA:0,(anonymous_49) -FNDA:0,(anonymous_50) -FNDA:0,(anonymous_51) -FNDA:0,(anonymous_52) -FNDA:0,(anonymous_53) -FNDA:0,(anonymous_54) -FNDA:0,(anonymous_55) -FNDA:0,(anonymous_56) -FNDA:0,(anonymous_57) -FNDA:0,(anonymous_58) -FNDA:0,(anonymous_59) -FNDA:0,(anonymous_60) -FNDA:0,(anonymous_61) -FNDA:0,(anonymous_62) -FNDA:0,(anonymous_63) -FNDA:0,(anonymous_64) -FNDA:0,(anonymous_65) -FNDA:0,(anonymous_66) -DA:2,0 -DA:3,0 -DA:5,0 -DA:7,0 -DA:8,0 -DA:9,0 -DA:10,0 -DA:12,0 -DA:14,0 -DA:15,0 -DA:16,0 -DA:20,0 -DA:29,0 -DA:30,0 -DA:31,0 -DA:35,0 -DA:36,0 -DA:37,0 -DA:40,0 -DA:41,0 -DA:42,0 -DA:46,0 -DA:47,0 -DA:48,0 -DA:53,0 -DA:54,0 -DA:55,0 -DA:58,0 -DA:65,0 -DA:69,0 -DA:70,0 -DA:71,0 -DA:72,0 -DA:75,0 -DA:76,0 -DA:81,0 -DA:84,0 -DA:85,0 -DA:88,0 -DA:89,0 -DA:95,0 -DA:97,0 -DA:98,0 -DA:100,0 -DA:101,0 -DA:104,0 -DA:107,0 -DA:108,0 -DA:109,0 -DA:112,0 -DA:113,0 -DA:114,0 -DA:115,0 -DA:116,0 -DA:117,0 -DA:118,0 -DA:121,0 -DA:123,0 -DA:126,0 -DA:131,0 -DA:132,0 -DA:135,0 -DA:137,0 -DA:138,0 -DA:139,0 -DA:140,0 -DA:145,0 -DA:146,0 -DA:147,0 -DA:149,0 -DA:153,0 -DA:158,0 -DA:159,0 -DA:163,0 -DA:166,0 -DA:168,0 -DA:169,0 -DA:172,0 -DA:174,0 -DA:175,0 -DA:176,0 -DA:177,0 -DA:180,0 -DA:182,0 -DA:185,0 -DA:186,0 -DA:187,0 -DA:189,0 -DA:193,0 -DA:198,0 -DA:199,0 -DA:203,0 -DA:206,0 -DA:207,0 -DA:209,0 -DA:210,0 -DA:212,0 -DA:213,0 -DA:216,0 -DA:218,0 -DA:219,0 -DA:220,0 -DA:221,0 -DA:224,0 -DA:226,0 -DA:229,0 -DA:230,0 -DA:233,0 -DA:237,0 -DA:241,0 -DA:242,0 -DA:243,0 -DA:245,0 -DA:246,0 -DA:251,0 -DA:252,0 -DA:253,0 -DA:256,0 -DA:257,0 -DA:260,0 -DA:262,0 -DA:263,0 -DA:264,0 -DA:265,0 -DA:267,0 -DA:268,0 -DA:271,0 -DA:273,0 -DA:274,0 -DA:275,0 -DA:281,0 -DA:282,0 -DA:283,0 -DA:284,0 -DA:285,0 -DA:291,0 -DA:293,0 -DA:300,0 -DA:301,0 -DA:302,0 -DA:304,0 -DA:305,0 -DA:310,0 -DA:311,0 -DA:312,0 -DA:314,0 -DA:322,0 -DA:323,0 -DA:327,0 -DA:330,0 -DA:331,0 -DA:332,0 -DA:333,0 -DA:334,0 -DA:335,0 -DA:338,0 -DA:339,0 -DA:340,0 -DA:342,0 -DA:343,0 -DA:348,0 -DA:349,0 -DA:353,0 -DA:357,0 -DA:361,0 -DA:362,0 -DA:363,0 -DA:364,0 -DA:367,0 -DA:368,0 -DA:371,0 -DA:373,0 -DA:388,0 -DA:389,0 -DA:390,0 -DA:391,0 -DA:395,0 -DA:396,0 -DA:399,0 -DA:400,0 -DA:404,0 -DA:420,0 -DA:421,0 -DA:422,0 -DA:424,0 -DA:426,0 -DA:429,0 -DA:430,0 -DA:439,0 -DA:440,0 -DA:444,0 -DA:445,0 -DA:446,0 -DA:458,0 -DA:465,0 -DA:466,0 -DA:471,0 -DA:478,0 -DA:479,0 -DA:480,0 -DA:481,0 -DA:486,0 -DA:488,0 -DA:492,0 -DA:493,0 -DA:494,0 -DA:504,0 -DA:505,0 -DA:506,0 -DA:507,0 -DA:508,0 -DA:509,0 -DA:514,0 -DA:515,0 -DA:521,0 -DA:528,0 -DA:529,0 -DA:530,0 -DA:532,0 -DA:537,0 -DA:538,0 -DA:539,0 -DA:540,0 -DA:544,0 -DA:556,0 -DA:557,0 -DA:558,0 -DA:559,0 -DA:560,0 -DA:561,0 -DA:565,0 -DA:566,0 -DA:567,0 -DA:568,0 -DA:571,0 -DA:573,0 -DA:574,0 -DA:577,0 -DA:580,0 -DA:584,0 -DA:588,0 -DA:591,0 -DA:592,0 -DA:594,0 -DA:596,0 -DA:601,0 -DA:602,0 -DA:609,0 -DA:610,0 -DA:613,0 -DA:617,0 -DA:618,0 -DA:619,0 -DA:620,0 -DA:623,0 -DA:626,0 -DA:627,0 -DA:628,0 -DA:630,0 -DA:635,0 -DA:636,0 -DA:637,0 -DA:641,0 -DA:642,0 -DA:645,0 -DA:646,0 -DA:648,0 -DA:651,0 -DA:652,0 -DA:653,0 -DA:655,0 -DA:659,0 -DA:662,0 -DA:663,0 -DA:664,0 -DA:666,0 -DA:670,0 -DA:676,0 -DA:677,0 -DA:678,0 -DA:679,0 -DA:685,0 -DA:688,0 -DA:693,0 -DA:694,0 -DA:695,0 -DA:696,0 -DA:701,0 -DA:702,0 -DA:703,0 -DA:705,0 -DA:706,0 -DA:708,0 -DA:710,0 -DA:711,0 -DA:712,0 -DA:713,0 -DA:718,0 -DA:719,0 -DA:725,0 -DA:728,0 -DA:731,0 -DA:732,0 -DA:735,0 -DA:736,0 -DA:738,0 -DA:739,0 -DA:744,0 -DA:748,0 -DA:750,0 -DA:751,0 -DA:752,0 -DA:757,0 -DA:758,0 -DA:759,0 -DA:760,0 -DA:761,0 -DA:762,0 -DA:763,0 -DA:765,0 -DA:769,0 -DA:770,0 -DA:776,0 -DA:779,0 -DA:780,0 -DA:782,0 -DA:783,0 -DA:784,0 -DA:785,0 -DA:789,0 -DA:792,0 -DA:796,0 -DA:797,0 -DA:799,0 -DA:800,0 -DA:801,0 -DA:802,0 -DA:804,0 -DA:805,0 -DA:806,0 -DA:807,0 -DA:809,0 -DA:810,0 -DA:814,0 -DA:815,0 -DA:820,0 -DA:821,0 -DA:824,0 -DA:825,0 -DA:828,0 -DA:829,0 -DA:830,0 -DA:833,0 -DA:834,0 -DA:836,0 -DA:840,0 -DA:844,0 -DA:845,0 -DA:848,0 -DA:852,0 -DA:853,0 -DA:855,0 -DA:859,0 -DA:864,0 -DA:869,0 -DA:870,0 -DA:871,0 -DA:874,0 -DA:876,0 -DA:882,0 -DA:886,0 -DA:887,0 -DA:891,0 -DA:892,0 -DA:894,0 -DA:895,0 -DA:897,0 -DA:901,0 -DA:902,0 -DA:903,0 -DA:909,0 -DA:911,0 -DA:912,0 -DA:915,0 -DA:918,0 -DA:921,0 -DA:923,0 -DA:924,0 -DA:926,0 -DA:927,0 -DA:931,0 -DA:933,0 -DA:939,0 -DA:942,0 -DA:943,0 -DA:944,0 -DA:946,0 -DA:948,0 -DA:949,0 -DA:954,0 -DA:965,0 -DA:968,0 -DA:969,0 -DA:970,0 -DA:973,0 -DA:975,0 -DA:976,0 -DA:977,0 -DA:978,0 -DA:979,0 -DA:987,0 -DA:992,0 -DA:1002,0 -DA:1004,0 -DA:1005,0 -DA:1007,0 -DA:1008,0 -DA:1009,0 -DA:1010,0 -DA:1011,0 -DA:1012,0 -DA:1013,0 -DA:1014,0 -DA:1015,0 -DA:1016,0 -DA:1022,0 -DA:1023,0 -DA:1024,0 -DA:1025,0 -DA:1026,0 -DA:1027,0 -DA:1029,0 -DA:1036,0 -DA:1037,0 -DA:1040,0 -DA:1041,0 -DA:1042,0 -DA:1044,0 -LF:438 -LH:0 -BRDA:7,0,0,0 -BRDA:7,0,1,0 -BRDA:9,1,0,0 -BRDA:9,1,1,0 -BRDA:14,2,0,0 -BRDA:14,2,1,0 -BRDA:15,3,0,0 -BRDA:15,3,1,0 -BRDA:15,4,0,0 -BRDA:15,4,1,0 -BRDA:29,5,0,0 -BRDA:29,5,1,0 -BRDA:35,6,0,0 -BRDA:35,6,1,0 -BRDA:40,7,0,0 -BRDA:40,7,1,0 -BRDA:46,8,0,0 -BRDA:46,8,1,0 -BRDA:69,9,0,0 -BRDA:69,9,1,0 -BRDA:76,10,0,0 -BRDA:76,10,1,0 -BRDA:77,11,0,0 -BRDA:77,11,1,0 -BRDA:77,11,2,0 -BRDA:84,12,0,0 -BRDA:84,12,1,0 -BRDA:88,13,0,0 -BRDA:88,13,1,0 -BRDA:88,14,0,0 -BRDA:88,14,1,0 -BRDA:100,15,0,0 -BRDA:100,15,1,0 -BRDA:107,16,0,0 -BRDA:107,16,1,0 -BRDA:113,17,0,0 -BRDA:113,17,1,0 -BRDA:131,18,0,0 -BRDA:131,18,1,0 -BRDA:131,19,0,0 -BRDA:131,19,1,0 -BRDA:131,19,2,0 -BRDA:131,19,3,0 -BRDA:159,20,0,0 -BRDA:159,20,1,0 -BRDA:160,21,0,0 -BRDA:160,21,1,0 -BRDA:160,21,2,0 -BRDA:168,22,0,0 -BRDA:168,22,1,0 -BRDA:168,23,0,0 -BRDA:168,23,1,0 -BRDA:176,24,0,0 -BRDA:176,24,1,0 -BRDA:199,25,0,0 -BRDA:199,25,1,0 -BRDA:200,26,0,0 -BRDA:200,26,1,0 -BRDA:200,26,2,0 -BRDA:212,27,0,0 -BRDA:212,27,1,0 -BRDA:212,28,0,0 -BRDA:212,28,1,0 -BRDA:220,29,0,0 -BRDA:220,29,1,0 -BRDA:256,30,0,0 -BRDA:256,30,1,0 -BRDA:256,31,0,0 -BRDA:256,31,1,0 -BRDA:264,32,0,0 -BRDA:264,32,1,0 -BRDA:264,33,0,0 -BRDA:264,33,1,0 -BRDA:268,34,0,0 -BRDA:268,34,1,0 -BRDA:271,35,0,0 -BRDA:271,35,1,0 -BRDA:274,36,0,0 -BRDA:274,36,1,0 -BRDA:283,37,0,0 -BRDA:283,37,1,0 -BRDA:285,38,0,0 -BRDA:285,38,1,0 -BRDA:286,39,0,0 -BRDA:286,39,1,0 -BRDA:286,39,2,0 -BRDA:291,40,0,0 -BRDA:291,40,1,0 -BRDA:322,41,0,0 -BRDA:322,41,1,0 -BRDA:322,42,0,0 -BRDA:322,42,1,0 -BRDA:331,43,0,0 -BRDA:331,43,1,0 -BRDA:334,44,0,0 -BRDA:334,44,1,0 -BRDA:334,45,0,0 -BRDA:334,45,1,0 -BRDA:334,45,2,0 -BRDA:351,46,0,0 -BRDA:351,46,1,0 -BRDA:364,47,0,0 -BRDA:364,47,1,0 -BRDA:367,48,0,0 -BRDA:367,48,1,0 -BRDA:374,49,0,0 -BRDA:374,49,1,0 -BRDA:375,50,0,0 -BRDA:375,50,1,0 -BRDA:376,51,0,0 -BRDA:376,51,1,0 -BRDA:378,52,0,0 -BRDA:378,52,1,0 -BRDA:399,53,0,0 -BRDA:399,53,1,0 -BRDA:406,54,0,0 -BRDA:406,54,1,0 -BRDA:440,55,0,0 -BRDA:440,55,1,0 -BRDA:450,56,0,0 -BRDA:450,56,1,0 -BRDA:451,57,0,0 -BRDA:451,57,1,0 -BRDA:465,58,0,0 -BRDA:465,58,1,0 -BRDA:466,59,0,0 -BRDA:466,59,1,0 -BRDA:467,60,0,0 -BRDA:467,60,1,0 -BRDA:467,60,2,0 -BRDA:473,61,0,0 -BRDA:473,61,1,0 -BRDA:480,62,0,0 -BRDA:480,62,1,0 -BRDA:480,63,0,0 -BRDA:480,63,1,0 -BRDA:486,64,0,0 -BRDA:486,64,1,0 -BRDA:493,65,0,0 -BRDA:493,65,1,0 -BRDA:496,66,0,0 -BRDA:496,66,1,0 -BRDA:506,67,0,0 -BRDA:506,67,1,0 -BRDA:507,68,0,0 -BRDA:507,68,1,0 -BRDA:508,69,0,0 -BRDA:508,69,1,0 -BRDA:514,70,0,0 -BRDA:514,70,1,0 -BRDA:546,71,0,0 -BRDA:546,71,1,0 -BRDA:556,72,0,0 -BRDA:556,72,1,0 -BRDA:556,73,0,0 -BRDA:556,73,1,0 -BRDA:558,74,0,0 -BRDA:558,74,1,0 -BRDA:561,75,0,0 -BRDA:561,75,1,0 -BRDA:565,76,0,0 -BRDA:565,76,1,0 -BRDA:567,77,0,0 -BRDA:567,77,1,0 -BRDA:573,78,0,0 -BRDA:573,78,1,0 -BRDA:580,79,0,0 -BRDA:580,79,1,0 -BRDA:581,80,0,0 -BRDA:581,80,1,0 -BRDA:593,81,0,0 -BRDA:593,81,1,0 -BRDA:593,82,0,0 -BRDA:593,82,1,0 -BRDA:596,83,0,0 -BRDA:596,83,1,0 -BRDA:597,84,0,0 -BRDA:597,84,1,0 -BRDA:597,84,2,0 -BRDA:609,85,0,0 -BRDA:609,85,1,0 -BRDA:616,86,0,0 -BRDA:617,87,0,0 -BRDA:617,87,1,0 -BRDA:619,88,0,0 -BRDA:619,88,1,0 -BRDA:619,89,0,0 -BRDA:619,89,1,0 -BRDA:626,90,0,0 -BRDA:626,90,1,0 -BRDA:626,91,0,0 -BRDA:626,91,1,0 -BRDA:636,92,0,0 -BRDA:636,92,1,0 -BRDA:651,93,0,0 -BRDA:651,93,1,0 -BRDA:651,94,0,0 -BRDA:651,94,1,0 -BRDA:677,95,0,0 -BRDA:677,95,1,0 -BRDA:677,96,0,0 -BRDA:677,96,1,0 -BRDA:706,97,0,0 -BRDA:706,97,1,0 -BRDA:710,98,0,0 -BRDA:710,98,1,0 -BRDA:718,99,0,0 -BRDA:718,99,1,0 -BRDA:718,100,0,0 -BRDA:718,100,1,0 -BRDA:728,101,0,0 -BRDA:728,101,1,0 -BRDA:738,102,0,0 -BRDA:738,102,1,0 -BRDA:738,103,0,0 -BRDA:738,103,1,0 -BRDA:760,104,0,0 -BRDA:760,104,1,0 -BRDA:761,105,0,0 -BRDA:761,105,1,0 -BRDA:780,106,0,0 -BRDA:780,106,1,0 -BRDA:782,107,0,0 -BRDA:782,107,1,0 -BRDA:800,108,0,0 -BRDA:800,108,1,0 -BRDA:805,109,0,0 -BRDA:805,109,1,0 -BRDA:814,110,0,0 -BRDA:814,110,1,0 -BRDA:824,111,0,0 -BRDA:824,111,1,0 -BRDA:824,112,0,0 -BRDA:824,112,1,0 -BRDA:829,113,0,0 -BRDA:829,113,1,0 -BRDA:845,114,0,0 -BRDA:845,114,1,0 -BRDA:855,115,0,0 -BRDA:855,115,1,0 -BRDA:856,116,0,0 -BRDA:856,116,1,0 -BRDA:859,117,0,0 -BRDA:859,117,1,0 -BRDA:859,117,2,0 -BRDA:870,118,0,0 -BRDA:870,118,1,0 -BRDA:887,119,0,0 -BRDA:887,119,1,0 -BRDA:891,120,0,0 -BRDA:891,120,1,0 -BRDA:903,121,0,0 -BRDA:903,121,1,0 -BRDA:904,122,0,0 -BRDA:904,122,1,0 -BRDA:904,122,2,0 -BRDA:904,122,3,0 -BRDA:912,123,0,0 -BRDA:912,123,1,0 -BRDA:913,124,0,0 -BRDA:913,124,1,0 -BRDA:921,125,0,0 -BRDA:921,125,1,0 -BRDA:926,126,0,0 -BRDA:926,126,1,0 -BRDA:948,127,0,0 -BRDA:948,127,1,0 -BRDA:948,128,0,0 -BRDA:948,128,1,0 -BRDA:968,129,0,0 -BRDA:968,129,1,0 -BRDA:975,130,0,0 -BRDA:975,130,1,0 -BRDA:978,131,0,0 -BRDA:978,131,1,0 -BRDA:1009,132,0,0 -BRDA:1009,132,1,0 -BRDA:1013,133,0,0 -BRDA:1013,133,1,0 -BRDA:1022,134,0,0 -BRDA:1022,134,1,0 -BRDA:1040,135,0,0 -BRDA:1040,135,1,0 -BRF:283 -BRH:0 -end_of_record -TN: -SF:lib\FormulaEvaluator.js -FN:20,(anonymous_0) -FN:30,(anonymous_1) -FN:168,(anonymous_2) -FN:188,(anonymous_3) -FN:208,(anonymous_4) -FN:228,(anonymous_5) -FN:248,(anonymous_6) -FN:265,(anonymous_7) -FN:310,(anonymous_8) -FN:357,(anonymous_9) -FNF:10 -FNH:10 -FNDA:52,(anonymous_0) -FNDA:108,(anonymous_1) -FNDA:71,(anonymous_2) -FNDA:94,(anonymous_3) -FNDA:114,(anonymous_4) -FNDA:117,(anonymous_5) -FNDA:200,(anonymous_6) -FNDA:188,(anonymous_7) -FNDA:270,(anonymous_8) -FNDA:67,(anonymous_9) -DA:21,52 -DA:22,52 -DA:31,108 -DA:32,6 -DA:35,102 -DA:36,102 -DA:37,102 -DA:39,102 -DA:41,630 -DA:42,242 -DA:43,242 -DA:47,388 -DA:48,25 -DA:49,25 -DA:50,25 -DA:52,363 -DA:53,24 -DA:54,24 -DA:55,24 -DA:59,339 -DA:60,263 -DA:61,263 -DA:62,2 -DA:63,2 -DA:64,2 -DA:66,261 -DA:67,2 -DA:68,2 -DA:69,2 -DA:71,259 -DA:72,2 -DA:73,2 -DA:74,2 -DA:79,333 -DA:80,3 -DA:81,3 -DA:82,3 -DA:84,330 -DA:85,3 -DA:86,3 -DA:87,3 -DA:89,327 -DA:90,3 -DA:91,3 -DA:92,3 -DA:94,324 -DA:95,3 -DA:96,3 -DA:97,3 -DA:99,321 -DA:100,2 -DA:101,2 -DA:102,2 -DA:104,319 -DA:105,2 -DA:106,2 -DA:107,2 -DA:111,317 -DA:112,302 -DA:113,302 -DA:114,526 -DA:115,526 -DA:118,302 -DA:121,302 -DA:122,72 -DA:123,230 -DA:124,16 -DA:125,214 -DA:126,2 -DA:127,212 -DA:128,12 -DA:129,200 -DA:130,6 -DA:131,194 -DA:132,4 -DA:133,190 -DA:135,187 -DA:137,3 -DA:139,299 -DA:143,15 -DA:144,11 -DA:145,11 -DA:146,3 -DA:147,8 -DA:148,6 -DA:150,2 -DA:152,9 -DA:153,9 -DA:157,4 -DA:160,93 -DA:169,71 -DA:170,71 -DA:172,71 -DA:173,1 -DA:176,70 -DA:178,64 -DA:179,3 -DA:182,61 -DA:189,94 -DA:191,88 -DA:192,20 -DA:193,20 -DA:194,20 -DA:202,88 -DA:209,114 -DA:211,108 -DA:212,3 -DA:213,3 -DA:214,3 -DA:222,108 -DA:229,117 -DA:231,113 -DA:232,71 -DA:233,71 -DA:234,69 -DA:242,111 -DA:249,200 -DA:250,12 -DA:251,12 -DA:252,12 -DA:259,188 -DA:266,188 -DA:267,2 -DA:270,186 -DA:273,186 -DA:274,24 -DA:275,24 -DA:276,24 -DA:277,3 -DA:279,21 -DA:280,21 -DA:284,162 -DA:285,12 -DA:286,12 -DA:293,150 -DA:294,149 -DA:295,149 -DA:301,1 -DA:311,270 -DA:312,0 -DA:315,270 -DA:317,14 -DA:320,153 -DA:321,3 -DA:323,150 -DA:326,11 -DA:327,11 -DA:329,0 -DA:332,92 -DA:333,92 -DA:335,90 -DA:337,62 -DA:339,21 -DA:341,7 -DA:343,0 -DA:347,0 -DA:358,67 -DA:359,60 -DA:360,54 -DA:364,1 -LF:160 -LH:156 -BRDA:31,0,0,6 -BRDA:31,0,1,102 -BRDA:31,1,0,108 -BRDA:31,1,1,104 -BRDA:41,2,0,242 -BRDA:41,2,1,388 -BRDA:47,3,0,25 -BRDA:47,3,1,363 -BRDA:52,4,0,24 -BRDA:52,4,1,339 -BRDA:59,5,0,263 -BRDA:59,5,1,76 -BRDA:61,6,0,2 -BRDA:61,6,1,261 -BRDA:66,7,0,2 -BRDA:66,7,1,259 -BRDA:71,8,0,2 -BRDA:71,8,1,257 -BRDA:79,9,0,3 -BRDA:79,9,1,330 -BRDA:84,10,0,3 -BRDA:84,10,1,327 -BRDA:89,11,0,3 -BRDA:89,11,1,324 -BRDA:94,12,0,3 -BRDA:94,12,1,321 -BRDA:99,13,0,2 -BRDA:99,13,1,319 -BRDA:104,14,0,2 -BRDA:104,14,1,317 -BRDA:111,15,0,302 -BRDA:111,15,1,15 -BRDA:113,16,0,828 -BRDA:113,16,1,748 -BRDA:121,17,0,72 -BRDA:121,17,1,230 -BRDA:123,18,0,16 -BRDA:123,18,1,214 -BRDA:125,19,0,2 -BRDA:125,19,1,212 -BRDA:127,20,0,12 -BRDA:127,20,1,200 -BRDA:129,21,0,6 -BRDA:129,21,1,194 -BRDA:131,22,0,4 -BRDA:131,22,1,190 -BRDA:133,23,0,187 -BRDA:133,23,1,3 -BRDA:143,24,0,11 -BRDA:143,24,1,4 -BRDA:145,25,0,3 -BRDA:145,25,1,8 -BRDA:147,26,0,6 -BRDA:147,26,1,2 -BRDA:172,27,0,1 -BRDA:172,27,1,70 -BRDA:178,28,0,3 -BRDA:178,28,1,61 -BRDA:191,29,0,108 -BRDA:191,29,1,44 -BRDA:211,30,0,111 -BRDA:211,30,1,47 -BRDA:231,31,0,182 -BRDA:231,31,1,118 -BRDA:249,32,0,12 -BRDA:249,32,1,188 -BRDA:249,33,0,200 -BRDA:249,33,1,198 -BRDA:266,34,0,2 -BRDA:266,34,1,186 -BRDA:273,35,0,24 -BRDA:273,35,1,162 -BRDA:276,36,0,3 -BRDA:276,36,1,21 -BRDA:276,37,0,24 -BRDA:276,37,1,21 -BRDA:284,38,0,12 -BRDA:284,38,1,150 -BRDA:293,39,0,149 -BRDA:293,39,1,1 -BRDA:310,40,0,2 -BRDA:311,41,0,0 -BRDA:311,41,1,270 -BRDA:315,42,0,14 -BRDA:315,42,1,153 -BRDA:315,42,2,11 -BRDA:315,42,3,92 -BRDA:315,42,4,0 -BRDA:320,43,0,3 -BRDA:320,43,1,150 -BRDA:326,44,0,11 -BRDA:326,44,1,0 -BRDA:335,45,0,62 -BRDA:335,45,1,21 -BRDA:335,45,2,7 -BRDA:335,45,3,0 -BRDA:337,46,0,62 -BRDA:337,46,1,47 -BRDA:339,47,0,21 -BRDA:339,47,1,12 -BRDA:357,48,0,21 -BRF:101 -BRH:97 -end_of_record -TN: -SF:lib\Logger.js -FN:48,(anonymous_0) -FN:71,(anonymous_1) -FN:86,(anonymous_2) -FN:126,(anonymous_3) -FN:134,(anonymous_4) -FN:138,(anonymous_5) -FN:142,(anonymous_6) -FN:146,(anonymous_7) -FN:179,(anonymous_8) -FN:200,(anonymous_9) -FN:220,(anonymous_10) -FN:251,(anonymous_11) -FN:256,(anonymous_12) -FN:260,(anonymous_13) -FN:264,(anonymous_14) -FN:268,(anonymous_15) -FN:272,(anonymous_16) -FN:276,(anonymous_17) -FN:281,(anonymous_18) -FN:285,(anonymous_19) -FN:290,(anonymous_20) -FN:294,(anonymous_21) -FN:301,(anonymous_22) -FN:322,(anonymous_23) -FN:335,(anonymous_24) -FN:355,(anonymous_25) -FN:363,(anonymous_26) -FN:369,(anonymous_27) -FNF:28 -FNH:0 -FNDA:0,(anonymous_0) -FNDA:0,(anonymous_1) -FNDA:0,(anonymous_2) -FNDA:0,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:0,(anonymous_5) -FNDA:0,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:0,(anonymous_8) -FNDA:0,(anonymous_9) -FNDA:0,(anonymous_10) -FNDA:0,(anonymous_11) -FNDA:0,(anonymous_12) -FNDA:0,(anonymous_13) -FNDA:0,(anonymous_14) -FNDA:0,(anonymous_15) -FNDA:0,(anonymous_16) -FNDA:0,(anonymous_17) -FNDA:0,(anonymous_18) -FNDA:0,(anonymous_19) -FNDA:0,(anonymous_20) -FNDA:0,(anonymous_21) -FNDA:0,(anonymous_22) -FNDA:0,(anonymous_23) -FNDA:0,(anonymous_24) -FNDA:0,(anonymous_25) -FNDA:0,(anonymous_26) -FNDA:0,(anonymous_27) -DA:19,0 -DA:27,0 -DA:41,0 -DA:50,0 -DA:51,0 -DA:54,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:59,0 -DA:65,0 -DA:66,0 -DA:74,0 -DA:75,0 -DA:76,0 -DA:78,0 -DA:80,0 -DA:84,0 -DA:86,0 -DA:91,0 -DA:92,0 -DA:96,0 -DA:97,0 -DA:100,0 -DA:101,0 -DA:102,0 -DA:104,0 -DA:105,0 -DA:108,0 -DA:116,0 -DA:117,0 -DA:119,0 -DA:123,0 -DA:127,0 -DA:128,0 -DA:129,0 -DA:130,0 -DA:135,0 -DA:139,0 -DA:143,0 -DA:147,0 -DA:150,0 -DA:156,0 -DA:157,0 -DA:159,0 -DA:164,0 -DA:174,0 -DA:177,0 -DA:178,0 -DA:179,0 -DA:181,0 -DA:188,0 -DA:197,0 -DA:201,0 -DA:202,0 -DA:206,0 -DA:207,0 -DA:208,0 -DA:210,0 -DA:213,0 -DA:215,0 -DA:216,0 -DA:221,0 -DA:222,0 -DA:226,0 -DA:228,0 -DA:231,0 -DA:232,0 -DA:234,0 -DA:236,0 -DA:237,0 -DA:238,0 -DA:239,0 -DA:243,0 -DA:244,0 -DA:245,0 -DA:246,0 -DA:252,0 -DA:257,0 -DA:261,0 -DA:265,0 -DA:269,0 -DA:273,0 -DA:277,0 -DA:282,0 -DA:286,0 -DA:291,0 -DA:295,0 -DA:296,0 -DA:297,0 -DA:302,0 -DA:303,0 -DA:304,0 -DA:305,0 -DA:308,0 -DA:309,0 -DA:311,0 -DA:312,0 -DA:319,0 -DA:323,0 -DA:324,0 -DA:327,0 -DA:328,0 -DA:329,0 -DA:331,0 -DA:336,0 -DA:337,0 -DA:340,0 -DA:341,0 -DA:344,0 -DA:345,0 -DA:346,0 -DA:347,0 -DA:349,0 -DA:356,0 -DA:364,0 -DA:365,0 -DA:370,0 -DA:371,0 -DA:373,0 -DA:374,0 -DA:375,0 -DA:376,0 -DA:381,0 -DA:383,0 -DA:389,0 -DA:395,0 -DA:399,0 -DA:400,0 -DA:401,0 -DA:407,0 -LF:131 -LH:0 -BRDA:50,0,0,0 -BRDA:50,0,1,0 -BRDA:71,1,0,0 -BRDA:71,2,0,0 -BRDA:74,3,0,0 -BRDA:74,3,1,0 -BRDA:74,4,0,0 -BRDA:74,4,1,0 -BRDA:76,5,0,0 -BRDA:76,5,1,0 -BRDA:76,6,0,0 -BRDA:76,6,1,0 -BRDA:76,6,2,0 -BRDA:101,7,0,0 -BRDA:101,7,1,0 -BRDA:101,8,0,0 -BRDA:101,8,1,0 -BRDA:104,9,0,0 -BRDA:104,9,1,0 -BRDA:105,10,0,0 -BRDA:105,10,1,0 -BRDA:111,11,0,0 -BRDA:111,11,1,0 -BRDA:111,11,2,0 -BRDA:112,12,0,0 -BRDA:112,12,1,0 -BRDA:112,12,2,0 -BRDA:116,13,0,0 -BRDA:116,13,1,0 -BRDA:128,14,0,0 -BRDA:128,14,1,0 -BRDA:150,15,0,0 -BRDA:150,15,1,0 -BRDA:151,16,0,0 -BRDA:151,16,1,0 -BRDA:151,16,2,0 -BRDA:151,16,3,0 -BRDA:159,17,0,0 -BRDA:159,17,1,0 -BRDA:160,18,0,0 -BRDA:160,18,1,0 -BRDA:160,18,2,0 -BRDA:174,19,0,0 -BRDA:174,19,1,0 -BRDA:177,20,0,0 -BRDA:177,20,1,0 -BRDA:177,21,0,0 -BRDA:177,21,1,0 -BRDA:177,21,2,0 -BRDA:181,22,0,0 -BRDA:181,22,1,0 -BRDA:181,23,0,0 -BRDA:181,23,1,0 -BRDA:181,23,2,0 -BRDA:201,24,0,0 -BRDA:201,24,1,0 -BRDA:221,25,0,0 -BRDA:221,25,1,0 -BRDA:231,26,0,0 -BRDA:231,26,1,0 -BRDA:237,27,0,0 -BRDA:237,27,1,0 -BRDA:239,28,0,0 -BRDA:239,28,1,0 -BRDA:245,29,0,0 -BRDA:245,29,1,0 -BRDA:296,30,0,0 -BRDA:296,30,1,0 -BRDA:303,31,0,0 -BRDA:303,31,1,0 -BRDA:311,32,0,0 -BRDA:311,32,1,0 -BRDA:323,33,0,0 -BRDA:323,33,1,0 -BRDA:336,34,0,0 -BRDA:336,34,1,0 -BRDA:340,35,0,0 -BRDA:340,35,1,0 -BRDA:346,36,0,0 -BRDA:346,36,1,0 -BRDA:364,37,0,0 -BRDA:364,37,1,0 -BRDA:370,38,0,0 -BRDA:370,38,1,0 -BRDA:381,39,0,0 -BRDA:381,39,1,0 -BRDA:383,40,0,0 -BRDA:383,40,1,0 -BRF:88 -BRH:0 -end_of_record -TN: -SF:lib\loggerConfig.js -FNF:0 -FNH:0 -DA:14,0 -LF:1 -LH:0 -BRF:0 -BRH:0 -end_of_record diff --git a/no.tiwas.booleantoolbox/coverage/loggerConfig.js.html b/no.tiwas.booleantoolbox/coverage/loggerConfig.js.html deleted file mode 100644 index b87878a..0000000 --- a/no.tiwas.booleantoolbox/coverage/loggerConfig.js.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - Code coverage report for loggerConfig.js - - - - - - - - - -
-
-

All files loggerConfig.js

-
- -
- 0% - Statements - 0/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 0% - Lines - 0/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
'use strict';
- 
-/**
- * Global Logger Configuration for Boolean Toolbox
- *
- * This is the central configuration for all logging in the app.
- * Place this file in: lib/loggerConfig.js
- *
- * USAGE:
- * - Adjust `defaultLevel` to change the standard log level for all modules.
- * - Add specific class names to `categoryLevels` for finer control over individual modules.
- * - The Logger will automatically pick up this file if it exists.
- */
-module.exports = {
-  // Default log level for any logger not specified below.
-  // Options: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
-  defaultLevel: 'debug',
- 
-  // Override log levels for specific categories (class names).
-  // This helps in reducing noise or enabling detailed logs for specific parts of the app.
-  categoryLevels: {
-    // Example: Show detailed logging for a specific driver during debugging.
-    // 'MyDeviceName': 'DEBUG',
- 
-    // Reduce noise from base classes which are generally stable.
-    // 'BaseLogicDriver': 'INFO',
-    // 'BaseLogicUnit': 'INFO',
- 
-    // Always show important app-level messages.
-    // 'App': 'INFO',
-  },
- 
-  // Global options (rarely needed).
-  options: {
-    timestamps: false, // Homey already adds timestamps to its logs.
-    colors: false,     // Not supported in the Homey development console.
-  },
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/no.tiwas.booleantoolbox/coverage/prettify.css b/no.tiwas.booleantoolbox/coverage/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/no.tiwas.booleantoolbox/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/no.tiwas.booleantoolbox/coverage/prettify.js b/no.tiwas.booleantoolbox/coverage/prettify.js deleted file mode 100644 index b322523..0000000 --- a/no.tiwas.booleantoolbox/coverage/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/no.tiwas.booleantoolbox/coverage/sort-arrow-sprite.png b/no.tiwas.booleantoolbox/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/no.tiwas.booleantoolbox/coverage/sorter.js b/no.tiwas.booleantoolbox/coverage/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/no.tiwas.booleantoolbox/coverage/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); From 4afcbbe4c4c9d5ba68f11b7586249808fd3088c1 Mon Sep 17 00:00:00 2001 From: Tiwas Date: Mon, 5 Oct 2026 13:11:30 +0000 Subject: [PATCH 3/3] ci: add a message check for commits, branches and PR text New workflow .github/workflows/message-check.yml runs .github/scripts/message-check.js on pull requests, pushes, issues, comments and reviews. It removes signature footers from PR descriptions and comments, and fails on branch names, commit messages, identities and PR text that break the message rules in AI_RULES.md section 7. Tests run with node --test .github/scripts/message-check.test.js. AI_RULES.md names the check, PROJECT_DOCUMENTATION.md lists it and WORKLOG.md records it. --- .github/scripts/message-check.js | 307 ++++++++++++++++++++++++++ .github/scripts/message-check.test.js | 270 ++++++++++++++++++++++ .github/workflows/message-check.yml | 37 ++++ AI_RULES.md | 1 + PROJECT_DOCUMENTATION.md | 1 + WORKLOG.md | 15 +- 6 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/message-check.js create mode 100644 .github/scripts/message-check.test.js create mode 100644 .github/workflows/message-check.yml diff --git a/.github/scripts/message-check.js b/.github/scripts/message-check.js new file mode 100644 index 0000000..7f36c57 --- /dev/null +++ b/.github/scripts/message-check.js @@ -0,0 +1,307 @@ +// Message check: enforces the message rules in AI_RULES.md section 7. +// +// - Pull requests: removes signature footers from the description, then fails +// on tool names in the title or description, on a tool-named branch, and on +// commits with signature lines, tool names or a tool identity. +// - Pushes: fails on a tool-named branch and on such commits. +// - Issues, comments and reviews: removes signature footers and fails when the +// text names the conventions file. +// +// Runs from .github/workflows/message-check.yml. Tests: node --test .github/scripts/ + +import { execFileSync } from 'node:child_process'; +import { appendFileSync, readFileSync } from 'node:fs'; +import { pathToFileURL } from 'node:url'; + +const SIGNATURE_LINES = [ + // Co-authored-by trailers that name an assistant or its vendor. + /^\s*co-authored-by:.*\b(claude|anthropic|openai|codex|copilot|chatgpt|gemini|cursor|devin)/i, + // Tool trailers such as "Claude-Session: ". + /^\s*(claude|codex|copilot|chatgpt|cursor)-[\w-]+:\s*\S+\s*$/i, + // "Generated with/by " footers, with or without emoji and markup. + /^[\W_]*generated\s+(with|by)\s+\[?(claude|codex|copilot|chatgpt|gemini)\b/iu, + // A line that is only a link to an assistant session or app. + /^[\s>*_`(<[]*https?:\/\/(www\.)?(claude\.ai|claude\.com|chatgpt\.com|chat\.openai\.com)\/\S*[\s)>\]*_`]*$/i, + // Sign-offs such as "— Claude" or "-- Codex". + /^\s*[-–—~]+\s*(claude|codex|copilot|chatgpt)(\s+code)?\s*\.?\s*$/i, +]; + +// Tool names as words, also inside branch refs such as "Tiwas/codex/x". A +// preceding "@" (the "@codex review" command) or "." (dot folders such as +// ".claude/") does not count. Links and file names ending in ".md" are +// reported by ASSISTANT_LINK and CONVENTIONS_FILE instead. +const TOOL_NAME = /(? pattern.test(line)); +} + +/** + * Removes signature lines. When something was removed, a horizontal rule and + * blank lines left dangling at the end are removed too. + * @returns {{ text: string, removed: string[] }} + */ +export function stripSignatures(text) { + if (!text) return { text: text ?? '', removed: [] }; + const eol = text.includes('\r\n') ? '\r\n' : '\n'; + const removed = []; + const kept = text.split(/\r?\n/).filter((line) => { + if (!isSignatureLine(line)) return true; + removed.push(line.trim()); + return false; + }); + if (removed.length === 0) return { text, removed }; + const trimEnd = () => { + while (kept.length > 0 && kept[kept.length - 1].trim() === '') kept.pop(); + }; + trimEnd(); + if (kept.length > 0 && HORIZONTAL_RULE.test(kept[kept.length - 1])) { + kept.pop(); + trimEnd(); + } + return { text: kept.join(eol), removed }; +} + +function uniqueMatches(text, pattern) { + return [...new Set([...(text || '').matchAll(pattern)].map((match) => match[0]))]; +} + +/** Tool names, assistant links and conventions-file names in strict text (PRs, commits). */ +export function findToolNames(text) { + return [ + ...uniqueMatches(text, TOOL_NAME), + ...uniqueMatches(text, ASSISTANT_LINK), + ...uniqueMatches(text, CONVENTIONS_FILE), + ]; +} + +/** Conventions-file names and assistant links in comments and issues. */ +export function findReferences(text) { + return [...uniqueMatches(text, CONVENTIONS_FILE), ...uniqueMatches(text, ASSISTANT_LINK)]; +} + +export function isAssistantIdentity(name, email) { + return ASSISTANT_IDENTITY.test(`${name} <${email}>`); +} + +export function isAssistantBranch(branch) { + return ASSISTANT_BRANCH.test(branch || ''); +} + +/** Problems in one commit: signature lines, tool names and tool identities. */ +export function checkCommit(commit) { + const short = commit.sha.slice(0, 7); + const problems = []; + const signatures = commit.message.split(/\r?\n/).filter(isSignatureLine); + if (signatures.length > 0) problems.push(`commit ${short}: signature line "${signatures[0].trim()}"`); + const names = findToolNames(commit.message); + if (names.length > 0) problems.push(`commit ${short}: message names ${names.join(', ')}`); + if (isAssistantIdentity(commit.authorName, commit.authorEmail)) { + problems.push(`commit ${short}: author ${commit.authorName} <${commit.authorEmail}>`); + } + if (isAssistantIdentity(commit.committerName, commit.committerEmail)) { + problems.push(`commit ${short}: committer ${commit.committerName} <${commit.committerEmail}>`); + } + return problems; +} + +function git(args) { + return execFileSync('git', args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); +} + +function commitExists(sha) { + try { + git(['cat-file', '-e', `${sha}^{commit}`]); + return true; + } catch { + return false; + } +} + +/** Reads the commits of a git log range (for example ["base..head"]). */ +export function readCommits(range) { + const output = git(['log', '-z', '--format=%H%x1f%an%x1f%ae%x1f%cn%x1f%ce%x1f%B', ...range]); + return output.split('\0').filter(Boolean).map((record) => { + const [sha, authorName, authorEmail, committerName, committerEmail, message] = record.split('\x1f'); + return { sha, authorName, authorEmail, committerName, committerEmail, message: message || '' }; + }); +} + +function makeApi(token, apiUrl) { + return async (method, path, body) => { + const response = await fetch(`${apiUrl}${path}`, { + method, + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + body: JSON.stringify(body), + }); + if (!response.ok) throw new Error(`${method} ${path}: HTTP ${response.status}`); + return response.json(); + }; +} + +/** + * Removes signature footers from a text and saves it through `save`. + * Records a fix, or a problem when saving is impossible or fails. Returns the + * text without signatures, so later checks do not report them a second time. + */ +async function cleanText(label, text, save, result, { allowEmpty }) { + const { text: cleaned, removed } = stripSignatures(text); + if (removed.length === 0) return text; + if (!allowEmpty && cleaned.trim() === '') { + result.problems.push(`${label} contains only a signature; delete it`); + return cleaned; + } + try { + await save(cleaned); + result.fixes.push(`${label}: removed ${removed.map((line) => `"${line}"`).join(', ')}`); + } catch (error) { + result.problems.push(`${label}: signature found but not removed (${error.message})`); + } + return cleaned; +} + +function checkBranch(branch, result) { + if (isAssistantBranch(branch)) result.problems.push(`branch "${branch}" is named after a tool`); +} + +function checkCommits(commits, result) { + for (const commit of commits) result.problems.push(...checkCommit(commit)); +} + +async function handlePullRequest(context, result) { + const { event, repo, api, readCommits: read } = context; + const pr = event.pull_request; + const label = `PR #${pr.number} description`; + const body = await cleanText(label, pr.body || '', (text) => api('PATCH', `/repos/${repo}/pulls/${pr.number}`, { body: text }), result, { allowEmpty: true }); + const titleNames = findToolNames(pr.title); + if (titleNames.length > 0) result.problems.push(`PR #${pr.number} title names ${titleNames.join(', ')}`); + const bodyNames = findToolNames(body); + if (bodyNames.length > 0) result.problems.push(`${label} names ${bodyNames.join(', ')}`); + checkBranch(pr.head.ref, result); + checkCommits(read([`${pr.base.sha}..${pr.head.sha}`]), result); +} + +async function handlePush(context, result) { + const { event, readCommits: read, commitExists: exists } = context; + if (event.deleted || !event.ref.startsWith('refs/heads/')) return; + const branch = event.ref.slice('refs/heads/'.length); + const defaultBranch = event.repository.default_branch; + checkBranch(branch, result); + let range; + if (!/^0+$/.test(event.before) && exists(event.before)) { + range = [`${event.before}..${event.after}`]; + } else if (branch !== defaultBranch && exists(`origin/${defaultBranch}`)) { + range = [`origin/${defaultBranch}..${event.after}`]; + } else { + range = ['-1', event.after]; + } + checkCommits(read(range), result); +} + +async function handleText(context, result, { label, text, save, allowEmpty, author }) { + if (!text || author?.type === 'Bot') return; + const cleaned = await cleanText(label, text, save, result, { allowEmpty }); + const references = findReferences(cleaned); + if (references.length > 0) result.problems.push(`${label} names ${references.join(', ')}`); +} + +const HANDLERS = { + pull_request: handlePullRequest, + push: handlePush, + issues: (context, result) => { + const { issue } = context.event; + const path = `/repos/${context.repo}/issues/${issue.number}`; + return handleText(context, result, { + label: `issue #${issue.number}`, + text: issue.body, + save: (body) => context.api('PATCH', path, { body }), + allowEmpty: true, + author: issue.user, + }); + }, + issue_comment: (context, result) => { + const { comment, issue } = context.event; + return handleText(context, result, { + label: `comment ${comment.id} on #${issue.number}`, + text: comment.body, + save: (body) => context.api('PATCH', `/repos/${context.repo}/issues/comments/${comment.id}`, { body }), + allowEmpty: false, + author: comment.user, + }); + }, + pull_request_review_comment: (context, result) => { + const { comment, pull_request: pr } = context.event; + return handleText(context, result, { + label: `review comment ${comment.id} on #${pr.number}`, + text: comment.body, + save: (body) => context.api('PATCH', `/repos/${context.repo}/pulls/comments/${comment.id}`, { body }), + allowEmpty: false, + author: comment.user, + }); + }, + pull_request_review: (context, result) => { + const { review, pull_request: pr } = context.event; + return handleText(context, result, { + label: `review ${review.id} on #${pr.number}`, + text: review.body, + save: (body) => context.api('PUT', `/repos/${context.repo}/pulls/${pr.number}/reviews/${review.id}`, { body }), + allowEmpty: false, + author: review.user, + }); + }, +}; + +/** Runs the check for one event. Returns the fixes made and the problems found. */ +export async function run(context) { + const result = { fixes: [], problems: [] }; + const handler = HANDLERS[context.eventName]; + if (handler) await handler(context, result); + return result; +} + +// Escapes text for a workflow command so it cannot start a new command. +function escapeCommandData(text) { + return text.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); +} + +function report(result) { + for (const fix of result.fixes) console.log(`::notice::${escapeCommandData(fix)}`); + for (const problem of result.problems) console.log(`::error::${escapeCommandData(problem)}`); + const summaryFile = process.env.GITHUB_STEP_SUMMARY; + if (!summaryFile) return; + const lines = ['## Message check', '']; + if (result.fixes.length === 0 && result.problems.length === 0) lines.push('No findings.'); + for (const fix of result.fixes) lines.push(`- Fixed: ${fix}`); + for (const problem of result.problems) lines.push(`- Problem: ${problem}`); + appendFileSync(summaryFile, `${lines.join('\n')}\n`); +} + +async function main() { + const result = await run({ + eventName: process.env.GITHUB_EVENT_NAME, + event: JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8')), + repo: process.env.GITHUB_REPOSITORY, + api: makeApi(process.env.GITHUB_TOKEN, process.env.GITHUB_API_URL || 'https://api.github.com'), + readCommits, + commitExists, + }); + report(result); + if (result.problems.length > 0) process.exitCode = 1; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + console.log(`::error::${escapeCommandData(error.message)}`); + process.exitCode = 1; + }); +} diff --git a/.github/scripts/message-check.test.js b/.github/scripts/message-check.test.js new file mode 100644 index 0000000..b4a7073 --- /dev/null +++ b/.github/scripts/message-check.test.js @@ -0,0 +1,270 @@ +import assert from 'node:assert/strict'; +import { describe, test } from 'node:test'; +import { + checkCommit, + findReferences, + findToolNames, + isAssistantBranch, + isAssistantIdentity, + run, + stripSignatures, +} from './message-check.js'; + +const FOOTER_COMMENT = '@codex review\n\n---\n_Generated by [Claude Code](https://claude.ai/code)_'; +const PERSON = { authorName: 'Tiwas', authorEmail: 'lars.m@extreme.no', committerName: 'Tiwas', committerEmail: 'lars.m@extreme.no' }; + +function fakeApi({ fail = false } = {}) { + const calls = []; + const api = async (method, path, body) => { + calls.push({ method, path, body }); + if (fail) throw new Error('HTTP 403'); + return {}; + }; + return { api, calls }; +} + +describe('stripSignatures', () => { + test('removes a footer and the rule above it', () => { + assert.deepEqual(stripSignatures(FOOTER_COMMENT), { + text: '@codex review', + removed: ['_Generated by [Claude Code](https://claude.ai/code)_'], + }); + }); + + test('removes a generated-with line and a session link, keeping CRLF line ends', () => { + const body = 'Summary\r\n\r\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\r\n\r\nhttps://claude.ai/code/session_0123\r\n'; + assert.equal(stripSignatures(body).text, 'Summary'); + }); + + test('removes assistant trailers and keeps human co-authors', () => { + const message = 'fix: thing\n\nCo-authored-by: Kari \nCo-Authored-By: Claude \nClaude-Session: https://example.com/x'; + assert.equal(stripSignatures(message).text, 'fix: thing\n\nCo-authored-by: Kari '); + }); + + test('removes a sign-off line', () => { + assert.equal(stripSignatures('Fixed in abc123.\n\n— Claude').text, 'Fixed in abc123.'); + }); + + test('leaves text without signatures untouched, including rules and sentences', () => { + const text = 'Intro\n\n---\n\nThe table was generated by the export script.\n'; + assert.deepEqual(stripSignatures(text), { text, removed: [] }); + }); + + test('handles empty input', () => { + assert.deepEqual(stripSignatures(null), { text: '', removed: [] }); + }); +}); + +describe('findToolNames', () => { + test('finds names, links and conventions-file names', () => { + assert.deepEqual(findToolNames('Codex review found it, see CLAUDE.md and https://chatgpt.com/x. GPT-5 too.'), [ + 'Codex', + 'GPT-5', + 'https://chatgpt.com/x.', + 'CLAUDE.md', + ]); + }); + + test('finds names in branch refs and reports a link once', () => { + assert.deepEqual(findToolNames('Merge pull request #42 from Tiwas/codex/issue-16; see https://claude.ai/code'), [ + 'codex', + 'https://claude.ai/code', + ]); + }); + + test('ignores the review command, dot folders and other words', () => { + assert.deepEqual(findToolNames('@codex review; `.claude/` and `.codex-logs/`; Geminimaliseerd; AI_RULES.md'), []); + }); +}); + +describe('findReferences', () => { + test('finds conventions-file names and assistant links only', () => { + assert.deepEqual(findReferences('As per CLAUDE.md and AGENTS.md, see https://claude.ai/code. @codex review'), [ + 'CLAUDE.md', + 'AGENTS.md', + 'https://claude.ai/code.', + ]); + assert.deepEqual(findReferences('Codex found a bug'), []); + }); +}); + +describe('identities and branches', () => { + test('recognises assistant identities', () => { + assert.equal(isAssistantIdentity('Claude', 'noreply@anthropic.com'), true); + assert.equal(isAssistantIdentity('Tiwas', 'lars.m@extreme.no'), false); + assert.equal(isAssistantIdentity('GitHub', 'noreply@github.com'), false); + }); + + test('recognises tool-named branches', () => { + for (const branch of ['claude/busy-hamilton-iqufvp', 'codex/issue-16-fix', 'feature/copilot-x', 'gpt']) { + assert.equal(isAssistantBranch(branch), true, branch); + } + for (const branch of ['main', 'chore/remove-ai-attribution', 'fix/clg-keep-unknown-lux-sensor', 'ci/pages-actions-deploy']) { + assert.equal(isAssistantBranch(branch), false, branch); + } + }); +}); + +describe('checkCommit', () => { + test('reports trailers, tool names and identities', () => { + const problems = checkCommit({ + sha: 'abcdef1234', + message: 'fix: x\n\nCo-Authored-By: Claude ', + authorName: 'Claude', + authorEmail: 'noreply@anthropic.com', + committerName: 'Tiwas', + committerEmail: 'lars.m@extreme.no', + }); + assert.equal(problems.length, 3); + assert.match(problems[0], /^commit abcdef1: signature line/); + assert.match(problems[2], /^commit abcdef1: author Claude/); + }); + + test('accepts a clean commit', () => { + assert.deepEqual(checkCommit({ sha: 'abcdef1234', message: 'chore: tidy repo files', ...PERSON }), []); + }); +}); + +describe('run', () => { + const repo = 'owner/repo'; + + test('pull request: cleans the description and reports branch and commits', async () => { + const { api, calls } = fakeApi(); + const ranges = []; + const result = await run({ + eventName: 'pull_request', + repo, + api, + event: { + pull_request: { + number: 7, + title: 'chore: tidy', + body: 'Changes.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)', + head: { ref: 'claude/x', sha: 'head' }, + base: { sha: 'base' }, + }, + }, + readCommits: (range) => { + ranges.push(range); + return [{ sha: 'abcdef1234', message: 'fix\n\nClaude-Session: https://claude.ai/code/s', ...PERSON }]; + }, + }); + assert.deepEqual(calls, [{ method: 'PATCH', path: '/repos/owner/repo/pulls/7', body: { body: 'Changes.' } }]); + assert.deepEqual(ranges, [['base..head']]); + assert.equal(result.fixes.length, 1); + assert.deepEqual(result.problems.map((problem) => problem.split(':')[0]), [ + 'branch "claude/x" is named after a tool', + 'commit abcdef1', + 'commit abcdef1', + ]); + }); + + test('pull request: a clean PR passes without edits', async () => { + const { api, calls } = fakeApi(); + const result = await run({ + eventName: 'pull_request', + repo, + api, + event: { pull_request: { number: 8, title: 'chore: tidy', body: '', head: { ref: 'chore/tidy', sha: 'h' }, base: { sha: 'b' } } }, + readCommits: () => [{ sha: 'abcdef1234', message: 'chore: tidy', ...PERSON }], + }); + assert.deepEqual(calls, []); + assert.deepEqual(result, { fixes: [], problems: [] }); + }); + + test('comment: removes the footer and keeps the command', async () => { + const { api, calls } = fakeApi(); + const result = await run({ + eventName: 'issue_comment', + repo, + api, + event: { comment: { id: 5, body: FOOTER_COMMENT, user: { type: 'User' } }, issue: { number: 57 } }, + }); + assert.deepEqual(calls, [{ method: 'PATCH', path: '/repos/owner/repo/issues/comments/5', body: { body: '@codex review' } }]); + assert.deepEqual(result.problems, []); + }); + + test('comment: reports a conventions-file reference', async () => { + const { api } = fakeApi(); + const result = await run({ + eventName: 'pull_request_review_comment', + repo, + api, + event: { comment: { id: 6, body: 'Fixed, per CLAUDE.md.', user: { type: 'User' } }, pull_request: { number: 9 } }, + }); + assert.deepEqual(result.problems, ['review comment 6 on #9 names CLAUDE.md']); + }); + + test('comment: a signature-only comment is reported, not emptied', async () => { + const { api, calls } = fakeApi(); + const result = await run({ + eventName: 'issue_comment', + repo, + api, + event: { comment: { id: 7, body: '🤖 Generated with [Claude Code](https://claude.com/claude-code)', user: { type: 'User' } }, issue: { number: 1 } }, + }); + assert.deepEqual(calls, []); + assert.deepEqual(result.problems, ['comment 7 on #1 contains only a signature; delete it']); + }); + + test('comment: bot comments are skipped', async () => { + const { api, calls } = fakeApi(); + const result = await run({ + eventName: 'issue_comment', + repo, + api, + event: { comment: { id: 8, body: 'Codex Review: see https://chatgpt.com/codex', user: { type: 'Bot' } }, issue: { number: 1 } }, + }); + assert.deepEqual(calls, []); + assert.deepEqual(result, { fixes: [], problems: [] }); + }); + + test('review: a failed edit is reported', async () => { + const { api } = fakeApi({ fail: true }); + const result = await run({ + eventName: 'pull_request_review', + repo, + api, + event: { review: { id: 9, body: FOOTER_COMMENT, user: { type: 'User' } }, pull_request: { number: 2 } }, + }); + assert.equal(result.fixes.length, 0); + assert.match(result.problems[0], /^review 9 on #2: signature found but not removed \(HTTP 403\)$/); + }); + + test('push: picks the commit range', async () => { + const pushes = [ + [{ ref: 'refs/heads/main', before: 'aaa', after: 'bbb' }, ['aaa..bbb']], + [{ ref: 'refs/heads/chore/x', before: '0000000', after: 'bbb', created: true }, ['origin/main..bbb']], + [{ ref: 'refs/heads/main', before: 'gone', after: 'bbb', forced: true }, ['-1', 'bbb']], + ]; + for (const [push, expected] of pushes) { + const ranges = []; + await run({ + eventName: 'push', + event: { ...push, repository: { default_branch: 'main' } }, + readCommits: (range) => { + ranges.push(range); + return []; + }, + commitExists: (ref) => ref !== 'gone', + }); + assert.deepEqual(ranges, [expected], push.ref); + } + }); + + test('push: branch deletions and tags are skipped; tool-named branches are reported', async () => { + const read = () => { + throw new Error('must not read commits'); + }; + for (const event of [{ ref: 'refs/heads/x', deleted: true }, { ref: 'refs/tags/v1.0.0' }]) { + assert.deepEqual(await run({ eventName: 'push', event, readCommits: read }), { fixes: [], problems: [] }); + } + const result = await run({ + eventName: 'push', + event: { ref: 'refs/heads/codex/fix', before: '0', after: 'b', repository: { default_branch: 'main' } }, + readCommits: () => [], + commitExists: () => true, + }); + assert.deepEqual(result.problems, ['branch "codex/fix" is named after a tool']); + }); +}); diff --git a/.github/workflows/message-check.yml b/.github/workflows/message-check.yml new file mode 100644 index 0000000..39da31b --- /dev/null +++ b/.github/workflows/message-check.yml @@ -0,0 +1,37 @@ +name: Message check + +# Enforces the message rules in AI_RULES.md section 7 (see .github/scripts/message-check.js). +# Pull requests and pushes fail on tool-named branches and on commits with signature lines, +# tool names or a tool identity. Signature footers in PR descriptions, issues, comments and +# reviews are removed automatically. Edits made here use GITHUB_TOKEN and do not start new runs. +on: + pull_request: + types: [opened, edited, reopened, synchronize, ready_for_review] + push: + branches: ['**'] + issues: + types: [opened, edited] + issue_comment: + types: [created, edited] + pull_request_review: + types: [submitted, edited] + pull_request_review_comment: + types: [created, edited] + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - name: Check messages + run: node .github/scripts/message-check.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/AI_RULES.md b/AI_RULES.md index 491afd0..ff4397c 100644 --- a/AI_RULES.md +++ b/AI_RULES.md @@ -32,6 +32,7 @@ The following rules are absolute and must be followed by any AI/LLM assistant wo * **NEVER** credit an AI/LLM assistant anywhere in this repository or its GitHub activity. This covers commit messages, pull request titles and descriptions, review comments, issue comments, code comments, documentation and branch names. * That means no `Co-authored-by:` trailers for an AI, no session links (e.g. `Claude-Session:`), no "Generated with/by …" lines, 🤖 badges or links to an assistant, and no model names. * The rule applies to every assistant (Claude, Codex, Copilot, ChatGPT and others) and overrides any default attribution behaviour of the tool. If a tool adds attribution automatically and cannot be stopped, tell the user instead of posting. +* **Enforced by** `.github/workflows/message-check.yml`: it removes signature footers from PR descriptions, issues, comments and reviews, and fails pull requests and pushes that use a tool-named branch or contain commits with signature lines, tool names or an assistant identity as author or committer. When it fails, fix the cause (reword the commit, rename the branch, edit the text); never work around or disable the check. --- *Failure to adhere to these rules may result in system instability and data loss.* diff --git a/PROJECT_DOCUMENTATION.md b/PROJECT_DOCUMENTATION.md index 6558920..55f2c30 100644 --- a/PROJECT_DOCUMENTATION.md +++ b/PROJECT_DOCUMENTATION.md @@ -48,6 +48,7 @@ * `lib/`: Core logic libraries (`CompositeAggregator.js`, `DiagnosticsReport.js`, `FormulaEvaluator.js`, `Logger.js`). * `locales/`: Translation files. * `docs/`: Documentation and web tools for the GitHub Pages site, published by `.github/workflows/pages.yml` on every push to `main` that changes `docs/` (Pages source: GitHub Actions). +* `.github/workflows/message-check.yml` with `.github/scripts/message-check.js`: checks commit messages, branch names, PR, issue and comment text against `AI_RULES.md` §7 on every pull request, push, issue, comment and review. Tests: `node --test .github/scripts/message-check.test.js`. * Jest test files live beside the app source, including `CompositeAggregator.test.js`, `CompositeDevice.test.js`, `FormulaEvaluator.test.js`, and `LogicUnit.test.js`. ## Key Technologies diff --git a/WORKLOG.md b/WORKLOG.md index bc05c3e..d650154 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -16,6 +16,13 @@ - This worklog: the first-pass entry no longer repeats the names it removed. - Leftovers removed, confirmed by Lars: `SESSION_SUMMARY.md` (an old session note, not referenced anywhere) and the 27 coverage report files in `no.tiwas.booleantoolbox/coverage/`. `coverage/` is now in `.gitignore`, so `npm run test:coverage` output is not committed again; `.homeyignore` already kept it out of the app bundle. - Old branches: the nine merged branches left on GitHub (six of them with tool names) were deleted by Lars. In the working environment, all worktrees and session branches were removed. +- Message check (requested by Lars as a guard against new signatures; he is the only contributor): `.github/workflows/message-check.yml` runs `.github/scripts/message-check.js` on pull requests, pushes to any branch, issues, comments and reviews. + - Pull requests: signature footers are removed from the description; the check fails on tool names, assistant links or conventions-file names in the title or description, on a tool-named branch, and on commits with signature lines, tool names or an assistant identity. + - Pushes: the same branch and commit checks (new branches are checked against `main`, deleted branches and tags are skipped). + - Issues, comments, review comments and reviews: signature footers are removed; the run fails when the text names the conventions file or links to an assistant session. Bot accounts (such as the review bot) are skipped, and `@codex review` commands are allowed. + - Edits use `GITHUB_TOKEN`, so they do not start new runs. + - `AI_RULES.md` §7 names the check, and `PROJECT_DOCUMENTATION.md` lists it. +- PR #60: neutral title and description, at Lars's request. ### Not changed - Commit history on `main` (trailers and merge messages with tool branch names), for the reasons given in the first pass. @@ -24,7 +31,13 @@ ### Verification - `npx jest --runInBand`: 23 suites, 358 tests passed. -- `source.png`, `SESSION_SUMMARY.md` and `coverage/` are not referenced by any manifest, code or config. `npm run test:package` was not run (Homey CLI not available here). +- `npm run test:package` (run by Lars): publish validation passed; bundle 910 files, 8.00 MB, 17 manifest assets verified. +- `source.png`, `SESSION_SUMMARY.md` and `coverage/` are not referenced by any manifest, code or config. +- Message check: + - `node --test .github/scripts/message-check.test.js`: 23 tests passed. + - Run over all 478 commits on `main`: it flags all 126 commits with signature lines and all 28 with an assistant identity. The 28 flagged for names alone are all true hits (merge messages with tool-named branches and one subject naming a tool). No clean commit is flagged. Text from earlier PR descriptions (#47, #57, #59) passes. + - Local end-to-end run of the script with Actions-style event files and a fake API: a PR description footer and the #57 comment footer are removed with one PATCH each; a push of `claude/x` fails; pushes of this branch and of `main` pass. + - The workflow has not run on GitHub yet; the first real run is on this PR. ## 2026-10-05 — Remove assistant names and attribution