owlry 2.1.0: Lua config layer (Phase 3) #8
Merged
vikingowl
merged 21 commits from 2026-05-13 14:21:52 +02:00
feat/lua-config into main
21 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
02c3281afe |
feat(doctor): surface config source + migrate hint + legacy artifacts
User feedback after running `owlry doctor` against a TOML-only setup:
the report said "[config] OK" with no indication that owlry.lua is the
canonical format from 2.1+ — or that a `migrate-config` command
existed. Doctor is the natural place to nudge.
commands.rs:
- New `print_config_source_info()` runs after the "OK" line on a
successful Config::load(). Cases:
* owlry.lua present → "source: Lua (path)". When config.toml is
also present, an extra "note: …toml is present alongside —
ignored." line surfaces the precedence rule.
* owlry.lua absent, config.toml present → "source: TOML (path)" +
a 3-line hint pointing at `owlry migrate-config` (deterministic,
`--force` to overwrite, TOML works until 3.0).
* Neither file → "source: built-in defaults (no user config found)".
- Also pulls in the same `lua::migrate::scan_legacy_artifacts` used by
`migrate-config` and emits a compact "legacy: N pre-v2 artifact(s)
detected" block listing each plugin / scripts dir with paths. Closing
line points at migrate-config for paste-ready snippets.
- `#[cfg(feature = "lua")]` branch covers the rich output; the non-lua
build emits a simpler note that owlry.lua is ignored without the
feature compiled in.
- Pluralisation: "1 entry"/"2 entries" and "1 artifact"/"2 artifacts"
(no more `(s)` stutter). Same nit also fixed in migrate-config's
"Heads up: detected N pre-v2 artifact(s)..." line for consistency.
Verified against three real scenarios:
1. config.toml + pre-v2 plugins/hyprshutdown + scripts/test.sh:
"OK / source: TOML / hint: migrate-config / legacy: 2 detected"
2. config.toml only, empty tempdir:
"OK / source: TOML / hint: migrate-config" (no legacy section)
3. No user config:
"OK / source: built-in defaults (no user config found)"
Clippy silent across --features full and --no-default-features.
|
||
|
|
aeafce157b |
style(migrate): fix '1 file(s)' stutter in scripts/ artifact report
Trivial: pluralise 'file' based on entries.len() rather than the lazy '(s)' suffix. The current output says '1 file(s)' which reads awkwardly. |
||
|
|
3e374e70f4 |
fix(migrate): emit valid Lua in the scripts/ legacy artifact snippet
The skeleton I generated for the legacy scripts/ directory used `:map(function(name) ... end)`, which is JS/Rust idiom — Lua tables don't have a built-in :map method. Anyone who pasted that into their owlry.lua would have hit a runtime error on the first refresh. Rewrite the snippet to use a standard `ipairs` loop with `table.insert`. Verified by piping the exact rendered snippet (against /tmp as the script dir) through `owlry config validate`: exit 0, "OK (Lua, ...)". The hyprshutdown plugin snippet was already valid Lua and didn't need any change. |
||
|
|
2ba06a7d2b |
feat(migrate): detect pre-v2 plugins + scripts/ and surface to user
User feedback after migrate-config: TOML migration was correct but
silently ignored the legacy `plugins/<name>/` (Rune / C-ABI) and
`scripts/` directories that v2 removed support for. The files still
exist on disk but aren't loaded — easy to miss what got broken.
lua/migrate.rs:
- New LegacyArtifact { path, kind } + LegacyKind { Plugin {..} |
ScriptsDir { entries } } types. Plugin variant carries id, name,
prefix, icon, entry_point pulled from the original plugin.toml so
the user has paste-ready data for the rewrite.
- scan_legacy_artifacts(config_dir) walks `plugins/` and `scripts/`
in alphabetical order:
* plugins/ — recognise a subdir as a plugin ONLY if it has a
plugin.toml. Skip dotfile dirs (.claude, .git, .cache, etc.)
even when they accidentally contain a manifest. This avoids
false positives like the user's `plugins/.claude/` (editor
config) and `plugins/docs/` (spec drafts under superpowers/).
* scripts/ — surfaced as a single ScriptsDir entry with the file
listing so the user sees what's there at a glance.
- parse_plugin_dir does a best-effort toml parse: malformed manifests
fall back to the directory name as id with everything else None.
- MigrateOutcome grows `legacy_artifacts: Vec<LegacyArtifact>`.
Empty when nothing matches.
commands.rs:
- New report_legacy_artifacts() prints a heads-up block per artifact
after the success summary. Each block includes the original
metadata AND a ready-to-paste `owlry.provider {}` skeleton:
* Plugin block fills in id/prefix/icon from the manifest and points
at the Rune entry_point as "this is what you need to translate".
* ScriptsDir block proposes a wrapper provider that iterates the
dir via owlry.util.shell_lines.
- Closing line links to docs/lua-api.md §4.4 (user providers) and
§5.1 (owlry.util.*).
Tests: 7 new (24/24 migrate tests green)
- scan_returns_empty_when_no_legacy_dirs
- scan_detects_plugin_with_full_manifest (id/name/prefix/icon/entry_point)
- scan_skips_directories_without_plugin_toml (was: fallback to dir name)
- scan_skips_dotfile_directories (covers `.claude` false positive)
- scan_tolerates_malformed_manifest
- scan_detects_scripts_directory_with_listing
- scan_reports_both_plugins_and_scripts_when_present
- migrate_outcome_includes_legacy_artifacts_field (end-to-end)
Verified against the real config (~/.config/owlry/ with a Rune-based
hyprshutdown plugin + a scripts/test.sh): output names both, the
hyprshutdown block surfaces its original `:hs` prefix and
system-shutdown icon, the scripts block lists test.sh and proposes a
wrapper provider. The earlier false positives (`.claude`, `docs`)
no longer appear.
359/359 lib tests green with --features full. Clippy silent across
--features full, --no-default-features, --no-default-features --features lua.
|
||
|
|
30713f065f |
fix(tests): add missing prefix field to Request::Query call sites
|
||
|
|
ad4e538a7a |
fix(aur): export RUSTFLAGS in PKGBUILD to actually pin ld.bfd
|
||
|
|
8383746a72 |
fix(cargo): pin ld.bfd to dodge LLD's arg-order strict mode (chroot fix)
THIRD attempt at the chroot build. The real root cause: rustc's link arg ordering plus Arch's `extra/rust` defaulting to `-fuse-ld=lld`. lua-src emits `cargo:rustc-link-lib=static:-bundle=lua5.4` (negative `-bundle` = "static, don't pack into the rlib") with a separate `cargo:rustc-link-search=native=$OUT_DIR/lib`. rustc serialises these into the final linker command line with `-llua5.4` appearing BEFORE `-L .../mlua-sys-*/out/lib`. Single-pass linkers (LLD) honour args left-to-right and fail to locate the archive at the time `-l` is processed → 60+ undefined symbols out of mlua. GNU `ld.bfd` does multi-pass resolution and finds the archive regardless of arg order. rustup's stable toolchain (used locally) defaults to bfd, which is why the bug never reproduced outside the chroot despite identical Cargo.toml / Cargo.lock / cargo / rustc versions. Add `.cargo/config.toml` at the workspace root pinning bfd for x86_64-unknown-linux-gnu via `rustflags = ["-C", "link-arg=-fuse-ld=bfd"]`. binutils (which ships bfd) is already a build-essential dep of the chroot — no new install cost. Local builds gain identical behaviour to chroot, which is a nice side effect. Verified locally: clean + frozen + features full → 84s, no link errors. Sequence to reproduce the fix: cargo clean cargo build --frozen --release --features full Reverts the previous two attempts to fix this: - |
||
|
|
b3764b308e |
fix(cargo): drop optional on mlua/glob/notify to dodge chroot resolver bug
Second chroot test still failed with the same `undefined symbol:
lua_gettop` linker errors after hoisting features to the cargo
feature row in
|
||
|
|
a4d4f30d1e |
fix(cargo): hoist mlua features to lua cargo feature row
Cargo's clean-chroot resolver drops inline `features = [...]` on optional deps under `--frozen` (the same gotcha that bit rusqlite during v2). When mlua's `vendored` feature got stripped the chroot build tried to link a system `liblua5.4` that doesn't exist, with 60+ undefined-symbol errors from `lua_gettop`, `lua_pcallk`, etc. Move lua54, vendored, send, and serialize out of the inline `mlua` dep spec and into the `lua` cargo feature row using `mlua/<feat>` syntax. cargo's resolver propagates these reliably whether or not the lock-file is frozen. Verified locally with `cargo clean && cargo build --frozen --release --features full` — full from-scratch compile of every dep including mlua + lua-src, links cleanly in 42s. 352/352 lib tests still pass. Cross-references the same lesson captured in memory under "Cargo optional-dep features need explicit dep/feature wiring". |
||
|
|
35f6501de2 |
docs(phase 3.10): owlry.example.lua + refresh user-facing docs
Documentation, example config, and one validator-test fix. Version bump and AUR push intentionally deferred. data/owlry.example.lua (new): - Annotated reference config exercising every surface (set / providers / tabs / theme / profiles / provider / util). Active section is minimal and validates clean. aur/owlry/PKGBUILD: - Ships data/owlry.example.lua to /usr/share/doc/owlry/owlry.example.lua. pkgver kept at 2.0.1. README.md: - Config table puts owlry.lua first (preferred from 2.1), config.toml marked legacy/fallback with precedence note linking lua-api.md §2. - New "Quick Start (Lua config)" section with migrate-config blurb. - migrate-config row: [--force], deterministic. config validate row: exit 1 errors / exit 2 warnings. - Roadmap section flips Lua config from "lands in 2.1/3.0" to "shipped in 2.1"; lists 2.2 follow-ups (dynamic providers, owlry.bind, util.http_get). CLAUDE.md: - Project shape tree expands lua/ module with per-file descriptions. - Build section documents the `lua` cargo feature. - CLI shape line for migrate-config: [--force]. config validate: exit codes 1/2 mentioned. - New "Lua config layer (2.1+)" section covers precedence, the notify-based watcher, desktop-notification errors, and the Arc<Lua> / LoadedConfig invariants. data/owlry.1: - --profile mentions both owlry.profiles (lua) and [profiles.<NAME>] (toml). - config validate paragraph describes the categorised report and lists 0/1/2 exit codes. - migrate-config description no longer says "stub" — covers determinism, --force/-f, pre-v2 alias normalisation, links §9. - FILES adds ~/.config/owlry/owlry.lua and the example .lua; config.toml labelled legacy. ROADMAP.md: - "Lua-driven configuration" reworded as shipped in 2.1; example uses owlry.lua and the v2 API; 2.2 follow-ups listed. cli.rs help: migrate-config stub-era text → "TOML → owlry.lua (--force to overwrite)". lua/validate.rs: loosen pre_v2_aliases_are_known to assert only that aliases aren't flagged as unknown ids. is_clean() failed under --no-default-features --features lua because uuctl correctly triggered the compiled-out warning (uuctl → systemd, systemd feature off → silently dropped at runtime). Test matrix (all green): - --features full 352 lib tests - --no-default-features 182 lib tests - --no-default-features --features lua 305 lib tests Clippy silent in all three configurations. Smoke (release build, isolated XDG): 1. config validate on the shipped owlry.example.lua → OK exit 0 2. migrate-config: TOML → owlry.lua with the precedence notice 3. Daemon loads, watcher armed; appending owlry.provider triggers hot-reload within the debounce window 4. :phase310 prefix routes the empty query to the new provider 5. config validate against the live file still OK |
||
|
|
aa1a38bbcf |
feat(lua): owlry config validate covers the Lua surface (Phase 3.9)
Implements the validator per docs/lua-api.md §8. Replaces the
TOML-only "load and pass/fail" check from 2.0 with categorised
findings: errors (exit 1), warnings (exit 2), clean (exit 0).
lua/config.rs:
- LuaConfig grows `duplicate_user_provider_ids: Vec<String>`. The dedup
in apply_provider would otherwise erase duplicates by the time the
snapshot is read.
lua/api.rs:
- apply_provider records the id on the duplicate list when replacing
an existing entry. The original warn! log is unchanged.
lua/validate.rs (new):
- ValidationReport { errors, warnings } with exit_code() honouring the
§8 codes (0/1/2).
- validate(cfg) is a pure function over a LuaConfig snapshot. Surfaces:
* Unknown keys in owlry.set
* Unknown colour keys in owlry.theme
* Unknown ids in owlry.providers (built-in alias table consulted;
user-provider ids are recognised)
* Providers compiled out — Cargo features checked via cfg! macros;
always-on ids (app, cmd, calc, conv, power, dmenu) skip the check
* Unknown / not-in-providers ids in owlry.tabs (user providers
auto-join the enabled set, so they pass when listed in tabs)
* Duplicate provider ids
* Unknown ids inside owlry.profiles values (per-profile label)
- canonical_provider_id() mirrors the apply_providers_list alias table
in lua/config.rs so an id accepted by the merger is never flagged.
commands.rs:
- run_config_validate now branches: when owlry.lua exists, build the
LoadedConfig, run validate::validate on the snapshot, print the
report with `<n> warning(s):` / `<n> error(s):` headers. Eval
failures print the chained mlua error (file path + line/col).
- TOML fallback path unchanged when no owlry.lua is present.
- Whole feature path is `#[cfg(feature = "lua")]`-gated.
Tests: 13 new in lua::validate::tests
- clean config emits nothing, exit 0
- unknown set key warns
- unknown theme key warns
- unknown provider id warns (and `app` passes through unflagged)
- user_provider ids in owlry.providers list are recognised
- tabs with unknown id warn
- tabs with id not in owlry.providers warn ("dropped from tab bar")
- tabs with user provider id passes (auto-joins enabled set)
- duplicate provider id warns
- profile with unknown id warns and names the profile
- pre-v2 aliases (sys, uuctl) validate cleanly
- always-compiled-in providers never trigger compile-out warning
- multi-warning report keeps every finding
Live smoke (release build, isolated XDG) for each category:
- clean → "config: OK (Lua, ...)", exit 0
- unknown set key → "owlry.set: unknown key `mystery_key` — ignored
(forward-compat for 2.2+ keys)", exit 2
- unknown provider id → "owlry.providers: unknown id `fictional_provider`
— not a built-in and not registered by any owlry.provider call", exit 2
- tabs not in providers → "owlry.tabs: `cmd` is not in owlry.providers
— it will be dropped from the tab bar", exit 2
- duplicate provider id → "owlry.provider: id `hs` was registered more
than once — the last definition wins (earlier registrations are
dropped)", exit 2
- profile with unknown id → "owlry.profiles.dev: unknown id `phantom`",
exit 2
- syntax error → "config: ERROR — Lua evaluation error in /.../
owlry.lua: syntax error: [string]:5: '}' expected (to close '{' at
line 2) near <eof>", exit 1
- 5-warning combo → all five surfaced with stable ordering, exit 2
352/352 lib tests with --features full. Clippy silent across full,
--features lua, and --no-default-features.
|
||
|
|
c200b1447c |
feat(lua): migrate-config writes owlry.lua from config.toml (Phase 3.8)
Implements the TOML → owlry.lua migrator per docs/lua-api.md §9. The
stub from 2.0 is gone; `owlry migrate-config [--force]` now reads
$XDG_CONFIG_HOME/owlry/config.toml and writes an equivalent owlry.lua.
cli.rs:
- MigrateConfig grew a `--force` / `-f` flag.
lua/migrate.rs (new, 700+ lines):
- MigrateRequest { toml_path, lua_path, force } + MigrateOutcome.
- migrate() validates paths, parses TOML via the existing
Config::load_from_toml pipeline (so pre-v2 aliases like `system` and
`badge_sys` normalise to v2 names through serde's #[serde(alias)]),
then calls generate_lua() and writes the file. Refuses to overwrite
without --force; refuses if no config.toml exists.
- generate_lua(cfg, raw_toml, src) is a pure function emitting Lua
text section-by-section. Properties:
* Deterministic — every list and map is alphabetically sorted, so
running the migrator twice on the same TOML produces byte-identical
output (locked in tests::migration_is_deterministic).
* Minimal — values matching Config::default() are omitted. A
config.toml with no overrides produces a Lua stub of just the
header comment.
* Round-trippable — the generated Lua, evaluated through LuaContext
and merged onto a default Config, matches the source Config's
scalars (tests::round_trip_preserves_scalars).
* Preserves leading `#` comment block from the source TOML by
re-emitting as `--` Lua comments under "Preserved from your
config.toml:". Other comments are intentionally dropped (cleanly,
without DROPPED placeholders).
- emit_table_key() bracket-quotes profile names that aren't valid Lua
identifiers (`my-media`, `media center`) and Lua 5.4 reserved words
(`end`, `for`, etc.), so the generated file always parses.
- lua_string() escapes \", \\, \n, \r, \t. Non-ASCII passes through
(Lua 5.4 strings are 8-bit clean).
commands.rs:
- run_migrate_config(force: bool) lives behind `#[cfg(feature = "lua")]`
with a clean stub on the no-lua build that tells the user how to
rebuild. Exit codes: 0 success, 1 destination-exists or no-TOML, 2
unrecoverable.
- Success output names both paths and bytes written, plus a one-liner
reminder that owlry.lua now takes precedence over config.toml.
Tests: 16 new in lua::migrate::tests
- lua_string escaping (quotes, backslashes, newlines, UTF-8 passthrough).
- header comment extraction (stops at first non-`#` non-empty line).
- default config → header-only output (no spurious blocks).
- emit_set only writes modified scalars.
- providers section omitted when all 11 built-ins are enabled.
- providers section emits alphabetically and excludes disabled ids.
- theme colours emit alphabetically (accent, background, badge_app order).
- profiles emit alphabetically (dev before minimal).
- profile names with hyphens get `["my-dev"]` bracket form.
- Lua reserved word `"end"` as profile name → bracket-quoted.
- determinism: twice on same Config → identical bytes.
- refuses existing destination without --force; preserves existing file.
- overwrites with --force.
- refuses when source TOML is missing.
- pre-v2 aliases (`system`, `badge_sys`) normalise to v2 names.
- round-trip: generate_lua → eval through LuaContext → merge onto
default Config → matches scalar fields of original.
Live smoke (release build, isolated XDG, pre-v2-flavoured TOML with
header comments, `system = false`, `badge_sys = "#f38ba8"`, hyphenated
`profiles."my-media"`):
- migrate-config writes 1044 bytes; output has the header block,
owlry.set with the 4 modified scalars, owlry.tabs, owlry.providers
(alphabetical, excluding power/ssh), owlry.theme with v2-named
badge_power, and owlry.profiles with `["my-media"]` bracket-quoted.
- Re-running without --force reports "already exists. Pass --force..."
and exits 1.
- Daemon launched against the generated file logs "Loaded Lua config"
and `config show` reports the migrated theme/scalars/profiles.
339/339 lib tests with --features full. Clippy silent across full,
--features lua, and --no-default-features.
|
||
|
|
eba590bad8 |
feat(lua): owlry.lua hot reload with desktop-notification errors (Phase 3.7)
Implements the hot-reload pipeline per docs/lua-api.md §7: the daemon
watches the user's owlry.lua and, on save, re-evaluates in a fresh
LuaContext. On success the new state hot-swaps atomically; on failure
the previous state is preserved and the user is told what broke via
BOTH the daemon log AND a desktop notification — no need to tail the
journal to discover the config is dead.
Cargo.toml:
- New optional dep `notify = "6"` gated by the `lua` feature alongside
mlua and glob. inotify backend on Linux; no platform-specific feature
flags needed.
lua/watcher.rs (new):
- ConfigWatcher::spawn(lua_path, config, pm, lua_ctx) wires a
notify::RecommendedWatcher on the parent directory (atomic-rename
saves from vim/JetBrains/etc rebind to a new inode, so watching the
file directly misses them) and spawns the event-pump thread.
- Debounce: 200ms after the first event, drain follow-ups, then reload
once. Coalesces burst saves into a single re-eval.
- reload() builds a fresh state in isolation; on Err keeps the old
Config/PM/LuaContext untouched.
- Swap order: config → ProviderManager → LuaContext. Old user-provider
Boxes inside the dropped PM retain the old Arc<Lua> until they
themselves drop, so the OLD Lua state survives the swap until the
OLD PM finishes dropping. New LuaProviders in new_pm hold the new
Arc<Lua>.
- report_reload_failure() formats the full error chain and fires a
notify_rust notification with summary/body/icon/urgency so the user
sees "config reload failed — /path/to/owlry.lua — <exact line>" the
moment a save breaks the file.
- Success path also emits a low-noise notification ("config reloaded").
lua/error.rs:
- LuaConfigError::Eval and ::Read no longer embed `{source}` in their
Display strings — error_chain() walks `.source()` already, and the
doubled rendering produced duplicated text in notifications and
logs. Comment explains why for the next reader.
server.rs:
- _lua_ctx changed from `Option<LuaContext>` to
`Arc<Mutex<Option<LuaContext>>>` so the watcher thread can atomically
replace it without taking the daemon down.
- New `_lua_watcher: Option<ConfigWatcher>` field, populated only when
loaded.lua_path is Some. Failure to spawn the watcher logs at warn
but doesn't fail Server::bind — the daemon stays usable even if
inotify is exhausted or perms are wrong.
- Mutex import feature-gated so --no-default-features stays
warning-free.
Tests: 5 new in lua::watcher::tests
- reload_swaps_in_new_config_state: change max_results in the file,
call reload() directly, verify Config got the new value.
- reload_failure_preserves_previous_state: write broken Lua, verify
Config is untouched.
- reload_replaces_user_providers_with_new_definitions: replace user
provider v1 with v2, verify a search for the v1 item returns nothing
and v2 is found (the websearch dynamic provider initially false-
positived on substring "v1 unique" via "Search: v1 unique" — locked
the test to the exact name "v1 unique item").
- event_touches_matches_only_watched_path: paths outside the watched
file are ignored.
- error_chain_renders_nested_causes: confirms chain walking works.
Live smoke (release build, isolated XDG, file edits via shell):
- v1 → save v2 → daemon log "hot-reload: applied"; :test prefix now
returns 2 v2 items.
- v2 → save broken Lua → daemon log "hot-reload: re-eval of /.../
owlry.lua failed, keeping previous config — Lua evaluation error in
/.../owlry.lua: syntax error: [string]:2: '}' expected (to close
'{' at line 1) near <eof>". Notification body shows the same
detail (file path + precise error). :test prefix still returns v2
items.
- Invalid provider id ("BadId With Spaces"): "runtime error: owlry.
provider: id 'BadId With Spaces' invalid — must be lowercase
alphanumeric with `-`/`_`". Old state preserved.
322/322 lib tests with --features full. Clippy silent across full,
--features lua, and --no-default-features.
|
||
|
|
82133edade |
feat(lua): owlry.util.* host helpers (Phase 3.6)
Implements the six host helpers per docs/lua-api.md §5.1: owlry.util.shell(cmd) -> string (stdout, trim_end) owlry.util.shell_lines(cmd) -> table (split, no trailing empty) owlry.util.read_file(path) -> string|nil (nil on any I/O error) owlry.util.glob(pattern) -> table (tilde-expanded; PatternError → Lua err) owlry.util.env(name, def?) -> string|nil owlry.util.hostname() -> string Cargo.toml: - New optional dep `glob = "0.3"` gated by the `lua` feature alongside mlua. Pure-Rust, no system Lua/C deps. lua/util.rs: - `build(lua) -> mlua::Result<Table>` constructs the owlry.util sub-table with every helper registered. Stateless; no Arc<Mutex<LuaConfig>> needed. - shell uses `sh -c`; stderr is logged at warn on non-zero exit but stdout is still returned (don't crash user configs on missing commands). - shell_lines drops the trailing empty element so a terminating newline doesn't produce a phantom "" item. - read_file extends "nil if missing" (per spec) to "nil on any I/O failure" — saves users wrapping every read in pcall. - glob expands a leading `~/` via `dirs::home_dir()`. `~user/`, embedded `~`, and env-var refs are intentionally left as-is (predictable surface). - hostname uses libc::gethostname directly (libc is already a hard dep). lua/api.rs: - install() now attaches `owlry.util = util::build(lua)?` to the parent owlry table. Tests: 17 new - util.rs (15): shell trim, interior newlines preserved, shell_lines split/empty cases, read_file present/missing, glob match/empty/error, env present/missing/default, hostname non-empty, tilde expansion positive/negative. - runtime.rs (2): owlry.util surface exists with all 6 functions after api::install; util helpers feed into a user provider's items() end-to-end (hostname-as-launch-item, via LuaProvider::refresh). Smoke (release build, isolated XDG, user provider :host_info using hostname/shell/env): daemon loads 4 items per the user provider — "host: cn-arch", "kernel: 7.0.5-zen1-1.1-zen", "home: /home/...", "user: cnachtigall". 317/317 lib tests with --features full. Clippy silent across all three feature configurations. |
||
|
|
32c6972a9e |
feat(lua): owlry.theme(...) + owlry.profiles {} (Phase 3.5)
Implements the theme selection and named-profile surface per
docs/lua-api.md §4.5 and §6.5:
owlry.theme("catppuccin-mocha") -- string form: theme name
owlry.theme { background = "#1e1e2e", -- table form: colour overrides
accent = "#cba6f7",
badge_app = "#a6e3a1" }
owlry.profiles { dev = { "app", "cmd", "ssh" }, media = {...} }
LuaConfig (lua/config.rs):
- theme_name: Option<String> (set by string form, last-write-wins)
- theme_colors: ThemeColors (table form, per-key last-write-wins)
- unknown_theme_keys: Vec<String> (forward-compat for 2.2+ palette)
- profiles: Option<HashMap<String, Vec<String>>> (replace-on-call)
- New KNOWN_THEME_KEYS const lists every recognised colour name.
- merge_into() applies theme name + colour overlay (Some-only fields)
and replaces base profiles when Lua sets them. Omitting `owlry.profiles`
leaves existing TOML/default profiles intact.
api.rs:
- owlry.theme dispatches on mlua::Value: Value::String → set name,
Value::Table → read colour keys. Other types raise a clean Lua
runtime error.
- Pre-v2 `badge_sys` alias normalises to `badge_power` (mirrors the
serde alias on ThemeColors).
- owlry.profiles: every value must be a table of strings; non-table
values produce a clear "list of provider ids" error message.
Tests: 19 new
- lua::config (6): theme name override, colours overlay only Some
fields, name+colours compose, profiles replace base, empty profiles
clears, omitted profiles preserves base.
- lua::runtime (13): string form, full colour table, name + table
compose, per-key merge across calls, badge_sys alias, unknown
forward-compat keys, wrong-arg-type error, multi-profile capture,
profiles replace, non-list value error, numeric coercion docs.
Smoke test (release build, isolated XDG): `owlry config show` reports
`theme = "nord"`, the four colour overrides under [appearance.colors],
and all three named profiles. 300/300 lib tests with --features full,
no new clippy warnings.
|
||
|
|
bfbce42eab |
fix(ipc): thread active_prefix through Query so daemon honours it (Phase 3.4.5)
The smoke test in Phase 3.4 surfaced a long-standing gap: parse_query
already routes `:smoke foo` → Plugin("smoke") via the dynamic-prefix
fallback (filter.rs:319-347), but the UI never told the daemon. The
UI's filter.active_prefix was set locally yet build_modes_param only
serialised filter.enabled, so the daemon rebuilt a fresh filter with
no active_prefix and searched across every provider. Same gap also
affected hardcoded plugin prefixes (`:emoji heart` etc.) — they only
appeared to work because their items outranked others on fuzzy score.
ipc.rs:
- Request::Query gains optional `prefix: Option<String>`, default None,
skip_serializing_if = "Option::is_none". Wire-format omits the field
when None so existing 2.0.x clients keep deserialising on a 2.1
daemon and vice-versa.
filter.rs:
- New test parse_query_routes_unknown_prefix_to_plugin_type_id locks
in the existing dynamic-fallback behaviour for `:smoke item`.
- New test build_prefix_param_round_trips_through_provider_type
proves Display + FromStr agree on the wire format.
client.rs:
- `query(text, modes, prefix)` — third arg added; 2 existing tests
updated. Two new tests with a capturing mock_server assert the
prefix field is serialised when set and elided when None.
backend.rs:
- QueryParams grows a `prefix: Option<String>`. New helper
build_prefix_param(filter) -> Option<String> mirrors set_prefix's
active_prefix to the wire. All three client.query call sites updated
(search, search_with_tag, query_async).
server.rs:
- Query handler destructures the new prefix field and applies it via
filter.set_prefix(ProviderType::from_str(prefix)). Unknown ids fall
through to ProviderType::Plugin(_) per the existing FromStr impl —
symmetric with the UI's dynamic-fallback path.
Live smoke (release build, isolated XDG + socket):
- prefix=smoke + text="item" → 3 items, all smoke (was: 100, mixed)
- prefix=smoke + text="one" → 1 item: "smoke one item"
- prefix=smoke + text="" → 3 smoke items, frecency-sorted
- prefix=app + text="firefox" → 1 firefox app item (regression
check; hardcoded prefix path still works)
7 new tests (ipc:3, client:2, filter:2). 283/283 lib tests green with
--features full. Both clippy configurations silent. Closes task #28.
|
||
|
|
270f02dd69 |
feat(lua): wire owlry.lua into Config::load + daemon (Phase 3.4)
Resolution order per docs/lua-api.md §2:
1. $XDG_CONFIG_HOME/owlry/owlry.lua -- defaults + Lua overlay; TOML ignored
2. $XDG_CONFIG_HOME/owlry/config.toml -- back-compat
3. Built-in defaults
When owlry.lua is present alongside config.toml, an info log notes that
TOML is being ignored ("Lua takes precedence"). 2.2 will upgrade this to
a warning; 3.0 removes TOML entirely.
paths.rs:
- New lua_config_file() -> $XDG_CONFIG_HOME/owlry/owlry.lua.
config/mod.rs:
- New LoadedConfig { config, lua, lua_path, user_providers } gated by
feature = "lua". Keeps the LuaContext alive so user-provider items
closures remain callable. Drop the context and mlua::Function refs
inside user_providers go invalid — that was the lifetime bug 3.3
uncovered.
- LoadedConfig::load() does the full resolution; load_or_default()
falls back to LoadedConfig::defaults() on error.
- Config::load() now consults owlry.lua first when feature enabled,
then falls through to the existing TOML path. The Lua context is
dropped after merge (read-only callers don't need user providers).
- Extracted load_from_toml() and detect_and_apply_terminal() so all
three load paths share the same terminal-detection tail.
- Manual Debug impl on LoadedConfig that doesn't try to format the
LuaContext (mlua::Lua doesn't impl Debug).
providers/mod.rs:
- new_with_config now takes a user_providers: Vec<Box<dyn Provider>>
parameter (callers pre-build them). Appended after the built-in set;
info-logs the count when non-empty.
server.rs:
- Server::bind switches to LoadedConfig::load_or_default() under the
lua feature, builds LuaProvider boxes from the captured specs using
the context's lua_handle(), and stores the LuaContext in a new
_lua_ctx field to keep it alive for the daemon's lifetime. Hot
reload (Phase 3.7) will use this field.
- Non-lua build keeps the existing Config::load_or_default() path
with an empty user_providers vec.
Tests: 7 new in config::loaded_config_tests
- defaults_apply_when_no_files_exist
- lua_path_overrides_defaults_with_set_values
- lua_providers_list_disables_unlisted_built_ins
- lua_user_provider_is_captured
- lua_provider_remains_callable_after_load (end-to-end: load → snapshot
→ build LuaProvider → refresh → assert items, with the LuaContext
field outliving the helper scope)
- lua_syntax_error_is_surfaced
- toml_only_load_returns_defaults_when_path_missing
- toml_only_load_parses_existing_file
Live smoke test (release build, $XDG_CONFIG_HOME=tempdir, custom
owlry.lua with one user provider): daemon logs "Loaded Lua config",
"Registering 1 user-defined provider(s)", "Provider 'smoke' loaded 2
items". IPC Providers returns the smoke provider; auto-mode fuzzy
query against the user-defined items succeeds.
Known limitation surfaced during smoke (task #28): UI prefix routing
via ProviderFilter::parse_query uses a hardcoded prefix table, so
typing `:smoke foo` in the UI doesn't narrow to the user provider.
Auto-mode is unaffected. Separate task tracks the parse_query
generalisation; not in 3.4 scope.
276/276 lib tests green with --features full. 174/174 with
--no-default-features. Clippy silent in both configurations.
|
||
|
|
cfcd201c35 |
chore(providers): clear standing clippy warnings
Pre-existing nits surfaced by recent clippy runs. No behavior change: - application/command/systemd: sort_by(|a,b| a.name.to_lowercase().cmp(...)) → sort_by_key(|i| i.name.to_lowercase()). The key form computes to_lowercase() once per element instead of twice per comparison. - providers/mod.rs (6 sites): descending score sort_by(|a,b| b.1.cmp(&a.1)) → sort_by_key(|x| std::cmp::Reverse(x.1)). - dmenu: add impl Default for DmenuProvider delegating to ::new(). cargo clippy now silent under --features full, --features lua, and --no-default-features. 268/268 lib tests still green. |
||
|
|
76524739e9 |
feat(lua): owlry.provider {} registers a static LuaProvider (Phase 3.3)
Implements the user-defined provider surface per docs/lua-api.md §4.4:
owlry.provider {
id = "hs",
prefix = ":hs",
items = function() return { { name = "Lock", command = "hyprlock" } } end,
}
Spec capture (lua/config.rs):
- New LuaProviderSpec carrying id / name / prefix / tab_label / icon /
search_noun / priority / dynamic + the mlua::Function `items` callback.
- LuaConfig grows a `user_providers: Vec<LuaProviderSpec>` accumulator.
- is_valid_provider_id() enforces the §4.4 grammar (lowercase a-z 0-9 - _).
Registration validation (lua/api.rs):
- `id` and `items` are required; missing field → clean Lua runtime error.
- Invalid id format rejected with a precise message.
- `dynamic = true` rejected in 2.1 (per spec: "lands in 2.2"). dynamic=false
explicit is accepted.
- Duplicate id: second wins, prior entry replaced, warning logged (§6.4).
- Multiple distinct ids accumulate in registration order.
Provider bridge (lua/provider.rs, new):
- LuaProvider impl Provider (Send + Sync, verified by static assertion).
- refresh() calls the Lua function with an empty query, parses each row,
drops items missing required name/command (warning per drop), caches.
- Errors from the user's items() are logged and result in an empty cache
for that refresh — daemon never crashes (§6.4).
- LaunchItem.source = ItemSource::ScriptPlugin (already reserved for
exactly this case in v2 demolition).
Lua-state lifetime fix (lua/runtime.rs):
- LuaContext now holds `Arc<Lua>`. mlua::Function references don't bump
the Lua refcount on their own, so providers built from a dropped context
would panic at call time. lua_handle() exposes the Arc so LuaProvider
can keep the state alive independently.
Tests: 18 new
- lua::config: 2 (id-validation positive/negative)
- lua::provider::tests: 9 (defaults, full spec, refresh happy/sad paths,
item field parsing, error handling, Send+Sync compile-time check)
- lua::provider::registration_tests: 8 (accumulation, duplicate replace,
missing-required errors, invalid-id, dynamic gate)
46/46 lua tests green. 267/267 lib tests green with --features full. No
new clippy warnings. ProviderManager wiring is Phase 3.4.
|
||
|
|
fabe60a7e0 |
feat(lua): set / providers / tabs read into LuaConfig (Phase 3.2)
Implements the three core owlry.* config-collection functions per
docs/lua-api.md §4.1-§4.3:
owlry.set { theme = "owl", width = 900, ... }
owlry.providers { "app", "cmd", "calc", "conv", "power" }
owlry.tabs { "app", "cmd", "uuctl" }
Accumulator: Arc<Mutex<LuaConfig>> shared between the three registered
Lua closures and the host. LuaConfig is a strongly-typed Option-fielded
struct mirroring the spec table in §4.1.
- owlry.set merges across calls (last-write-wins per key); unknown keys
are recorded in unknown_settings for `owlry config validate` (3.9).
- owlry.providers replaces the list on each call; v1 aliases
(sys/system/uuctl) honoured at merge time.
- owlry.tabs replaces the tab order list verbatim.
LuaConfig::merge_into(&mut Config) overlays the captured state onto
the existing TOML-derived Config without touching unset fields. This
is the data layer; Phase 3.4 wires it into Config::load().
require("owlry") returns the same table as the global `owlry` (via a
tiny package.preload shim) so both styles in the spec quick-reference
work.
Tests: 28 new (config: 11, runtime: 17) covering happy path, multi-call
merge semantics, unknown-key tolerance, type-error propagation, alias
expansion, empty lists, file reads, and error path reporting. 249/249
lib tests green with --features full. No new clippy warnings.
|
||
|
|
5b8bc8bac9 |
feat(lua): scaffold mlua-based config module (Phase 3.1)
Adds the lua cargo feature and a stubbed crates/owlry/src/lua/ module in preparation for Phase 3 (Lua config layer per docs/lua-api.md). - mlua 0.11 (lua54, vendored, send, serialize) as an optional dep so AUR clean-chroot builds don't depend on system Lua. - New `lua` feature gates the module; included in the `full` feature set so the AUR build ships it. Disabled by default so minimal `cargo install` consumers don't pay for the C compile. - Stub submodules: runtime (LuaContext), api (owlry.* surface), error (LuaConfigError), util (owlry.util.*). Every function is a no-op placeholder for the sub-phases that will wire them. - pub mod lua gated behind #[cfg(feature = "lua")] in lib.rs. cargo check passes with --no-default-features, --features lua, and --features full. 221/221 lib tests still green. Zero clippy warnings from the new module. |