Compare commits

..

35 Commits

Author SHA1 Message Date
d5088c3d64 chore(aur): update owlry-plugin-weather to 1.0.3 2026-04-05 18:07:07 +02:00
f613dcb2d1 chore(owlry-plugin-weather): bump version to 1.0.3 2026-04-05 18:07:02 +02:00
da5085e0d2 fix: switch reqwest TLS backend from rustls to native-tls
reqwest 0.13 defaults to rustls -> aws-lc-rs which requires cmake/nasm
in minimal build environments. Switch owlry-plugin-weather to native-tls
(system OpenSSL). Also add scripts/aur-local-test for clean chroot
testing and .gitignore for build artifact exclusion.
2026-04-05 18:06:56 +02:00
d10fa3cdce chore(aur): bump all plugins to 1.0.2 2026-03-28 13:41:24 +01:00
d6df6ca96e chore: bump all plugins to 1.0.2 2026-03-28 13:40:34 +01:00
4a7693d50b docs: revise README — remove stale references, add dependency info
- Remove runtimes section (runtimes are in the core repo)
- Add external dependency column to plugin table
- Fix build examples (no deleted plugins)
- Add AUR install instructions
- Streamline development section
2026-03-28 13:28:45 +01:00
4068037a9a chore(aur): transitional packages for retired plugins 2026-03-28 12:30:24 +01:00
461a9c2249 docs: update README — calculator, converter, system moved to core 2026-03-28 12:29:29 +01:00
ecdcca93a4 chore: remove calculator, converter, system plugins
These providers are now built into owlry-core >= 1.2.0. The plugins
are retired — transitional AUR packages redirect to owlry-core.
2026-03-28 12:27:56 +01:00
7ffbd46358 fix: use git add -A in aur-publish-pkg 2026-03-28 11:21:03 +01:00
627cbcbf91 fix: aur-stage glob handling for packages without .install files 2026-03-28 10:51:53 +01:00
9e221b2328 chore: overhaul justfile for deployment pipeline
Key additions:
- bump-crate now updates Cargo.lock and commits (fixes --locked builds)
- bump-all updates lock file and commits
- tag-crate creates per-plugin tags ({crate}-v{version})
- Full AUR recipes: aur-update-pkg, aur-publish-pkg, aur-stage, aur-commit
- aur-stage handles embedded .git dirs in AUR subdirectories
- release-plugin does full pipeline: bump → push → tag → AUR → publish
- aur-status shows all plugin package versions
2026-03-28 10:31:44 +01:00
effbfa68e4 fix(aur): correct converter checksum after Cargo.lock fix 2026-03-28 10:28:31 +01:00
7d69105930 fix: update Cargo.lock for converter 1.0.2 2026-03-28 10:28:12 +01:00
2bd72c8af1 fix(aur): correct converter checksum after retag 2026-03-28 10:25:26 +01:00
b4de6a3791 chore(aur): add owlry-plugin-converter PKGBUILD (1.0.2) 2026-03-28 10:24:11 +01:00
c73f57578d fix(converter): fix double unit in description, broken icon, currency aliases
- Description showed "20 m = 0.02 km km" — display_value already
  includes the unit symbol, removed redundant r.target_symbol
- Icon changed from "edit-find-replace" to "edit-find-replace-symbolic"
  which exists in Adwaita
- Currency aliases (dollar, euro, etc.) now resolve in convert_to and
  convert_common — they were only handled by find_unit (parser validation)
  but not by lookup_unit (actual conversion)
2026-03-28 10:23:49 +01:00
b46477ae88 chore(aur): replace b2sums SKIP with real checksums
Fixed-version tarballs should use real B2 checksums per AUR
submission guidelines. SKIP is only appropriate for VCS packages.
2026-03-28 09:42:09 +01:00
9c3dec9d14 chore: remove meta packages (moved to owlry main repo)
The owlry-meta-* packages belong in the main owlry repo since they
bundle core + plugin packages. Having them in both repos caused
version drift. The main repo has the canonical versions.
2026-03-28 09:34:36 +01:00
41a00b188a docs: add per-crate tagging convention to CLAUDE.md 2026-03-26 18:57:25 +01:00
fe68af7b46 chore: bump changed plugins to 1.0.1 2026-03-26 18:52:00 +01:00
7e51425166 docs: update Rune plugin guide with working Item API, manifest providers, logging 2026-03-26 18:47:52 +01:00
5c93b8a280 docs: update plugin development guide for main defaults, register API, hot-reload
- entry_point → entry (canonical); note alias in manifest section
- Lua quick start and provider functions rewritten for owlry.provider.register() API
- owlry table is pre-registered globally; remove require("owlry") references
- Items documented as plain Lua tables, not method-chained owlry.item() objects
- owlry_version bumped to >=1.0.0 in manifest example
- Rune manifest entry_point → entry
- Add Hot Reload section documenting file-watcher behavior and caveats
2026-03-26 17:52:03 +01:00
3852245f74 fix: use mutex poisoning recovery in bookmarks plugin 2026-03-26 16:58:19 +01:00
9bcbacd9d7 fix: quality — config-based terminal/engine, emoji init perf, safer shell commands 2026-03-26 16:50:17 +01:00
37edb8e1df fix: critical — eliminate Box::leak in converter, secure temp files, fix background refresh 2026-03-26 16:46:32 +01:00
8d5a5e16b6 chore: update Cargo.lock for converter plugin 2026-03-26 15:48:00 +01:00
f3b9728f07 feat(converter): finalize integration and add integration tests 2026-03-26 15:28:54 +01:00
b2156dc0b2 feat(converter): implement currency rates from ECB with caching 2026-03-26 15:23:58 +01:00
5550a10048 feat(converter): implement natural speech query parser 2026-03-26 15:21:52 +01:00
9e6cedf159 feat(converter): implement unit definitions and conversion engine 2026-03-26 15:18:20 +01:00
c44502d0ab feat(converter): scaffold plugin crate with vtable 2026-03-26 15:15:33 +01:00
67a4791828 docs: add converter plugin implementation plan 2026-03-26 15:12:37 +01:00
268fd49741 docs: add converter plugin design spec 2026-03-26 15:07:04 +01:00
d8d26f4fd2 chore(aur): update all plugin PKGBUILDs for v1.0.0 with per-crate tags 2026-03-26 14:08:07 +01:00
66 changed files with 3418 additions and 1851 deletions

View File

@@ -26,6 +26,19 @@ cargo build -p owlry-plugin-calculator --release
# Bump versions
just bump-crate owlry-plugin-weather 0.5.0 # Bump specific plugin
just bump-all 0.5.0 # Bump all plugins
# Tagging convention: every changed crate gets its own tag
# Format: {crate-name}-v{version}
# Examples:
# owlry-plugin-bookmarks-v1.0.1
# owlry-plugin-calculator-v1.0.1
#
# IMPORTANT: After bumping, tag EVERY changed plugin crate individually.
# Unchanged plugins don't need new tags.
#
# Plugin API dependency uses a git tag from the core repo:
# owlry-plugin-api = { git = "...", tag = "plugin-api-v1.0.1" }
# Update this tag reference when the core repo bumps plugin-api.
```
## Workspace Structure

848
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
[workspace]
members = [
"crates/owlry-plugin-bookmarks",
"crates/owlry-plugin-calculator",
"crates/owlry-plugin-clipboard",
"crates/owlry-plugin-emoji",
"crates/owlry-plugin-filesearch",
@@ -9,7 +8,6 @@ members = [
"crates/owlry-plugin-pomodoro",
"crates/owlry-plugin-scripts",
"crates/owlry-plugin-ssh",
"crates/owlry-plugin-system",
"crates/owlry-plugin-systemd",
"crates/owlry-plugin-weather",
"crates/owlry-plugin-websearch",

View File

@@ -1,56 +1,66 @@
# owlry-plugins
Official plugins and script runtimes for [owlry](https://somegit.dev/Owlibou/owlry).
Official plugins for [owlry](https://somegit.dev/Owlibou/owlry).
> **Note:** Calculator, converter, and system actions are built into `owlry-core` (>= 1.2.0) and do not require separate packages.
## Plugins
| Plugin | Description |
|--------|-------------|
| calculator | Mathematical expression evaluation |
| bookmarks | Browser bookmark search (Firefox, Chrome) |
| clipboard | Clipboard history via cliphist |
| emoji | Emoji picker |
| filesearch | File search via fd/locate |
| media | MPRIS media player widget |
| pomodoro | Pomodoro timer widget |
| scripts | User script launcher |
| ssh | SSH host quick-connect |
| system | Power and session management |
| systemd | systemd user service control |
| weather | Weather widget |
| websearch | Web search with configurable engines |
## Runtimes
| Runtime | Description |
|---------|-------------|
| owlry-lua | Lua 5.4 scripting runtime for user plugins |
| owlry-rune | Rune scripting runtime for user plugins |
## Building
```bash
just build # Debug build
just release # Release build (optimized)
just plugin calc # Build a single plugin
just check # cargo check + clippy
just test # Run tests
```
| Plugin | Description | Dependencies |
|--------|-------------|-------------|
| bookmarks | Browser bookmark search (Firefox, Chrome, Brave, Edge) | — |
| clipboard | Clipboard history | `cliphist`, `wl-clipboard` |
| emoji | Emoji picker (400+) | `wl-clipboard`, `noto-fonts-emoji` |
| filesearch | File search (`/ filename`) | `fd` or `mlocate` |
| media | MPRIS media player widget | `playerctl` |
| pomodoro | Pomodoro timer widget | — |
| scripts | User script launcher | — |
| ssh | SSH host quick-connect | `openssh` |
| systemd | systemd user service control | `systemd` |
| weather | Weather widget | — |
| websearch | Web search with configurable engines | — |
## Installation
### Arch Linux (AUR)
```bash
just install-local # Install all plugins and runtimes to /usr/lib/owlry/
# Install individual plugins
yay -S owlry-plugin-bookmarks owlry-plugin-clipboard owlry-plugin-weather
# Or install several at once
yay -S owlry-plugin-{bookmarks,clipboard,emoji,ssh,websearch}
```
### Build from Source
Requires Rust 1.90+ and `owlry-core` installed.
```bash
git clone https://somegit.dev/Owlibou/owlry-plugins.git
cd owlry-plugins
just build # Debug build (all plugins)
just release # Release build (optimized)
just plugin bookmarks # Build a single plugin
just install-local # Install all plugins to /usr/lib/owlry/plugins/
```
Plugins are compiled as `.so` files and installed to `/usr/lib/owlry/plugins/`.
Runtimes are installed to `/usr/lib/owlry/runtimes/`.
## Development
See [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for plugin authoring guide.
Each plugin is a `cdylib` crate implementing the `owlry-plugin-api` ABI-stable interface from the [core repo](https://somegit.dev/Owlibou/owlry).
Plugins depend on `owlry-plugin-api` from the core repo for the ABI-stable interface.
```bash
just check # cargo check + clippy
just test # Run tests
just fmt # Format code
just show-versions # List all plugin versions
```
See [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for the plugin authoring guide.
## License

View File

@@ -1,17 +0,0 @@
pkgbase = owlry-meta-essentials
pkgdesc = Essential plugins for Owlry (calculator, system, ssh, scripts, bookmarks)
pkgver = 1.0.0
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
arch = any
license = GPL-3.0-or-later
depends = owlry
depends = owlry-plugin-bookmarks
depends = owlry-plugin-calculator
depends = owlry-plugin-scripts
depends = owlry-plugin-ssh
depends = owlry-plugin-system
conflicts = owlry-essentials
replaces = owlry-essentials
pkgname = owlry-meta-essentials

View File

@@ -1,18 +0,0 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-meta-essentials
pkgver=1.0.0
pkgrel=1
pkgdesc="Essential plugins for Owlry (calculator, system, ssh, scripts, bookmarks)"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=(
'owlry'
'owlry-plugin-bookmarks'
'owlry-plugin-calculator'
'owlry-plugin-scripts'
'owlry-plugin-ssh'
'owlry-plugin-system'
)
replaces=('owlry-essentials')
conflicts=('owlry-essentials')

View File

@@ -1,27 +0,0 @@
pkgbase = owlry-meta-full
pkgdesc = Complete Owlry installation with all plugins
pkgver = 1.0.0
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
arch = any
license = GPL-3.0-or-later
depends = owlry
depends = owlry-plugin-bookmarks
depends = owlry-plugin-calculator
depends = owlry-plugin-scripts
depends = owlry-plugin-ssh
depends = owlry-plugin-system
depends = owlry-plugin-clipboard
depends = owlry-plugin-emoji
depends = owlry-plugin-filesearch
depends = owlry-plugin-systemd
depends = owlry-plugin-websearch
depends = owlry-plugin-media
depends = owlry-plugin-pomodoro
depends = owlry-plugin-weather
optdepends = owlry-lua: Lua runtime for custom user plugins
optdepends = owlry-rune: Rune runtime for custom user plugins
conflicts = owlry-full
replaces = owlry-full
pkgname = owlry-meta-full

View File

@@ -1,33 +0,0 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-meta-full
pkgver=1.0.0
pkgrel=1
pkgdesc="Complete Owlry installation with all plugins"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=(
'owlry'
# Essential plugins
'owlry-plugin-bookmarks'
'owlry-plugin-calculator'
'owlry-plugin-scripts'
'owlry-plugin-ssh'
'owlry-plugin-system'
# Tool plugins
'owlry-plugin-clipboard'
'owlry-plugin-emoji'
'owlry-plugin-filesearch'
'owlry-plugin-systemd'
'owlry-plugin-websearch'
# Widget plugins
'owlry-plugin-media'
'owlry-plugin-pomodoro'
'owlry-plugin-weather'
)
optdepends=(
'owlry-lua: Lua runtime for custom user plugins'
'owlry-rune: Rune runtime for custom user plugins'
)
replaces=('owlry-full')
conflicts=('owlry-full')

View File

@@ -1,17 +0,0 @@
pkgbase = owlry-meta-tools
pkgdesc = Tool plugins for Owlry (clipboard, emoji, websearch, filesearch, systemd)
pkgver = 1.0.0
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
arch = any
license = GPL-3.0-or-later
depends = owlry
depends = owlry-plugin-clipboard
depends = owlry-plugin-emoji
depends = owlry-plugin-filesearch
depends = owlry-plugin-systemd
depends = owlry-plugin-websearch
conflicts = owlry-tools
replaces = owlry-tools
pkgname = owlry-meta-tools

View File

@@ -1,18 +0,0 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-meta-tools
pkgver=1.0.0
pkgrel=1
pkgdesc="Tool plugins for Owlry (clipboard, emoji, websearch, filesearch, systemd)"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=(
'owlry'
'owlry-plugin-clipboard'
'owlry-plugin-emoji'
'owlry-plugin-filesearch'
'owlry-plugin-systemd'
'owlry-plugin-websearch'
)
replaces=('owlry-tools')
conflicts=('owlry-tools')

View File

@@ -1,15 +0,0 @@
pkgbase = owlry-meta-widgets
pkgdesc = Widget plugins for Owlry (weather, media, pomodoro)
pkgver = 1.0.0
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
arch = any
license = GPL-3.0-or-later
depends = owlry
depends = owlry-plugin-media
depends = owlry-plugin-pomodoro
depends = owlry-plugin-weather
conflicts = owlry-widgets
replaces = owlry-widgets
pkgname = owlry-meta-widgets

View File

@@ -1,16 +0,0 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-meta-widgets
pkgver=1.0.0
pkgrel=1
pkgdesc="Widget plugins for Owlry (weather, media, pomodoro)"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=(
'owlry'
'owlry-plugin-media'
'owlry-plugin-pomodoro'
'owlry-plugin-weather'
)
replaces=('owlry-widgets')
conflicts=('owlry-widgets')

View File

@@ -1,17 +1,17 @@
pkgbase = owlry-plugin-bookmarks
pkgdesc = Browser bookmarks plugin for Owlry (Firefox, Chrome, Chromium)
pkgver = 0.4.10
pkgdesc = Bookmarks plugin for Owlry — search and launch browser bookmarks (Firefox, Chrome, Chromium)
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-bookmarks.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
optdepends = firefox: Firefox bookmarks support
optdepends = chromium: Chromium bookmarks support
optdepends = google-chrome: Chrome bookmarks support
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-bookmarks-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-bookmarks-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-bookmarks

View File

@@ -1,12 +1,12 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-bookmarks
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Browser bookmarks plugin for Owlry (Firefox, Chrome, Chromium)"
pkgdesc="Bookmarks plugin for Owlry — search and launch browser bookmarks (Firefox, Chrome, Chromium)"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry')
depends=('owlry-core')
makedepends=('cargo')
install=owlry-plugin-bookmarks.install
optdepends=(
@@ -14,33 +14,33 @@ optdepends=(
'chromium: Chromium bookmarks support'
'google-chrome: Chrome bookmarks support'
)
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-bookmarks
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,14 +1,11 @@
pkgbase = owlry-plugin-calculator
pkgdesc = Calculator plugin for Owlry - evaluate math expressions
pkgver = 0.4.10
pkgrel = 1
pkgdesc = Transitional package — calculator is now built into owlry-core
pkgver = 1.0.1
pkgrel = 99
url = https://somegit.dev/Owlibou/owlry
install = owlry-plugin-calculator.install
arch = x86_64
arch = any
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
depends = owlry-core>=1.2.0
replaces = owlry-plugin-calculator
pkgname = owlry-plugin-calculator

View File

@@ -1,41 +1,10 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-calculator
pkgver=0.4.10
pkgrel=1
pkgdesc="Calculator plugin for Owlry - evaluate math expressions"
arch=('x86_64')
pkgver=1.0.1
pkgrel=99
pkgdesc="Transitional package — calculator is now built into owlry-core"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=('owlry')
makedepends=('cargo')
install=owlry-plugin-calculator.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
_cratename=owlry-plugin-calculator
prepare() {
cd "owlry"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}
depends=('owlry-core>=1.2.0')
replaces=('owlry-plugin-calculator')

View File

@@ -1,16 +1,16 @@
pkgbase = owlry-plugin-clipboard
pkgdesc = Clipboard history plugin for Owlry
pkgver = 0.4.10
pkgdesc = Clipboard history plugin for Owlry — search and paste previous clipboard entries
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-clipboard.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
depends = cliphist
depends = wl-clipboard
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-clipboard-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-clipboard-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-clipboard

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-clipboard
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Clipboard history plugin for Owlry"
pkgdesc="Clipboard history plugin for Owlry — search and paste previous clipboard entries"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry' 'cliphist' 'wl-clipboard')
depends=('owlry-core' 'cliphist' 'wl-clipboard')
makedepends=('cargo')
install=owlry-plugin-clipboard.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-clipboard
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -0,0 +1,11 @@
pkgbase = owlry-plugin-converter
pkgdesc = Transitional package — converter is now built into owlry-core
pkgver = 1.0.2
pkgrel = 99
url = https://somegit.dev/Owlibou/owlry
arch = any
license = GPL-3.0-or-later
depends = owlry-core>=1.2.0
replaces = owlry-plugin-converter
pkgname = owlry-plugin-converter

View File

@@ -0,0 +1,10 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-converter
pkgver=1.0.2
pkgrel=99
pkgdesc="Transitional package — converter is now built into owlry-core"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=('owlry-core>=1.2.0')
replaces=('owlry-plugin-converter')

View File

@@ -0,0 +1,9 @@
pre_install() {
# Remove old plugin files that may have different names
rm -f /usr/lib/owlry/plugins/libconverter.so
rm -f /usr/lib/owlry/plugins/libowlry_plugin_converter.so
}
pre_upgrade() {
pre_install
}

View File

@@ -1,16 +1,16 @@
pkgbase = owlry-plugin-emoji
pkgdesc = Emoji picker plugin for Owlry
pkgver = 0.4.10
pkgdesc = Emoji picker plugin for Owlry — search and insert emoji characters
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-emoji.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
depends = wl-clipboard
depends = noto-fonts-emoji
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-emoji-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-emoji-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-emoji

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-emoji
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Emoji picker plugin for Owlry"
pkgdesc="Emoji picker plugin for Owlry — search and insert emoji characters"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry' 'wl-clipboard' 'noto-fonts-emoji')
depends=('owlry-core' 'wl-clipboard' 'noto-fonts-emoji')
makedepends=('cargo')
install=owlry-plugin-emoji.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-emoji
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,16 +1,16 @@
pkgbase = owlry-plugin-filesearch
pkgdesc = File search plugin for Owlry
pkgver = 0.4.10
pkgdesc = File search plugin for Owlry — find files using fd or mlocate
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-filesearch.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
optdepends = fd: fast file finding (recommended)
optdepends = mlocate: locate-based file search
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-filesearch-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-filesearch-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-filesearch

View File

@@ -1,45 +1,45 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-filesearch
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="File search plugin for Owlry"
pkgdesc="File search plugin for Owlry — find files using fd or mlocate"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry')
depends=('owlry-core')
makedepends=('cargo')
install=owlry-plugin-filesearch.install
optdepends=(
'fd: fast file finding (recommended)'
'mlocate: locate-based file search'
)
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-filesearch
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,15 +1,15 @@
pkgbase = owlry-plugin-media
pkgdesc = Media player controls plugin for Owlry (MPRIS)
pkgver = 0.4.10
pkgdesc = Media controls plugin for Owlry — control MPRIS-compatible media players
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-media.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
depends = playerctl
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-media-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-media-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-media

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-media
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Media player controls plugin for Owlry (MPRIS)"
pkgdesc="Media controls plugin for Owlry — control MPRIS-compatible media players"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry' 'playerctl')
depends=('owlry-core' 'playerctl')
makedepends=('cargo')
install=owlry-plugin-media.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-media
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,14 +1,14 @@
pkgbase = owlry-plugin-pomodoro
pkgdesc = Pomodoro timer widget plugin for Owlry
pkgver = 0.4.10
pkgdesc = Pomodoro timer widget for Owlry — track focus and break intervals
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-pomodoro.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
depends = owlry-core
source = owlry-plugin-pomodoro-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-pomodoro-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-pomodoro

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-pomodoro
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Pomodoro timer widget plugin for Owlry"
pkgdesc="Pomodoro timer widget for Owlry — track focus and break intervals"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry')
depends=('owlry-core')
makedepends=('cargo')
install=owlry-plugin-pomodoro.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-pomodoro
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,14 +1,14 @@
pkgbase = owlry-plugin-scripts
pkgdesc = Custom scripts provider plugin for Owlry
pkgver = 0.4.10
pkgdesc = Scripts plugin for Owlry — launch custom scripts from a configured directory
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-scripts.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
depends = owlry-core
source = owlry-plugin-scripts-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-scripts-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-scripts

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-scripts
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Custom scripts provider plugin for Owlry"
pkgdesc="Scripts plugin for Owlry — launch custom scripts from a configured directory"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry')
depends=('owlry-core')
makedepends=('cargo')
install=owlry-plugin-scripts.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-scripts
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,15 +1,15 @@
pkgbase = owlry-plugin-ssh
pkgdesc = SSH host launcher plugin for Owlry
pkgver = 0.4.10
pkgdesc = SSH plugin for Owlry — quickly connect to SSH hosts from config
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-ssh.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
depends = openssh
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-ssh-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-ssh-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-ssh

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-ssh
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="SSH host launcher plugin for Owlry"
pkgdesc="SSH plugin for Owlry — quickly connect to SSH hosts from config"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry' 'openssh')
depends=('owlry-core' 'openssh')
makedepends=('cargo')
install=owlry-plugin-ssh.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-ssh
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,15 +1,11 @@
pkgbase = owlry-plugin-system
pkgdesc = System actions plugin for Owlry (shutdown, reboot, logout, etc.)
pkgver = 0.4.10
pkgrel = 1
pkgdesc = Transitional package — system actions is now built into owlry-core
pkgver = 1.0.0
pkgrel = 99
url = https://somegit.dev/Owlibou/owlry
install = owlry-plugin-system.install
arch = x86_64
arch = any
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = systemd
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
depends = owlry-core>=1.2.0
replaces = owlry-plugin-system
pkgname = owlry-plugin-system

View File

@@ -1,41 +1,10 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-system
pkgver=0.4.10
pkgrel=1
pkgdesc="System actions plugin for Owlry (shutdown, reboot, logout, etc.)"
arch=('x86_64')
pkgver=1.0.0
pkgrel=99
pkgdesc="Transitional package — system actions is now built into owlry-core"
arch=('any')
url="https://somegit.dev/Owlibou/owlry"
license=('GPL-3.0-or-later')
depends=('owlry' 'systemd')
makedepends=('cargo')
install=owlry-plugin-system.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
_cratename=owlry-plugin-system
prepare() {
cd "owlry"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}
depends=('owlry-core>=1.2.0')
replaces=('owlry-plugin-system')

View File

@@ -1,15 +1,15 @@
pkgbase = owlry-plugin-systemd
pkgdesc = Systemd service management plugin for Owlry
pkgver = 0.4.10
pkgdesc = Systemd plugin for Owlry — manage systemd user services
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-systemd.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
depends = owlry-core
depends = systemd
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
source = owlry-plugin-systemd-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-systemd-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-systemd

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-systemd
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Systemd service management plugin for Owlry"
pkgdesc="Systemd plugin for Owlry — manage systemd user services"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry' 'systemd')
depends=('owlry-core' 'systemd')
makedepends=('cargo')
install=owlry-plugin-systemd.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-systemd
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,14 +1,15 @@
pkgbase = owlry-plugin-weather
pkgdesc = Weather widget plugin for Owlry
pkgver = 0.4.10
pkgdesc = Weather widget for Owlry — display current weather conditions
pkgver = 1.0.3
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-weather.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
depends = owlry-core
depends = openssl
source = owlry-plugin-weather-1.0.3.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-weather-v1.0.3.tar.gz
b2sums = a0988b7eb5496a1b9f0f5a9936b84990c79736b66da26de06e63d542bf4b0d9e2a382e0257c3237f67fdd41a278d0a8a38e683361f50cb0dcf0c6afe8d6ac7cd
pkgname = owlry-plugin-weather

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-weather
pkgver=0.4.10
pkgver=1.0.3
pkgrel=1
pkgdesc="Weather widget plugin for Owlry"
pkgdesc="Weather widget for Owlry — display current weather conditions"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry')
depends=('owlry-core' 'openssl')
makedepends=('cargo')
install=owlry-plugin-weather.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('a0988b7eb5496a1b9f0f5a9936b84990c79736b66da26de06e63d542bf4b0d9e2a382e0257c3237f67fdd41a278d0a8a38e683361f50cb0dcf0c6afe8d6ac7cd')
_cratename=owlry-plugin-weather
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,14 +1,14 @@
pkgbase = owlry-plugin-websearch
pkgdesc = Web search plugin for Owlry (DuckDuckGo, Google, etc.)
pkgver = 0.4.10
pkgdesc = Web search plugin for Owlry — search DuckDuckGo, Google, and custom engines
pkgver = 1.0.2
pkgrel = 1
url = https://somegit.dev/Owlibou/owlry
url = https://somegit.dev/Owlibou/owlry-plugins
install = owlry-plugin-websearch.install
arch = x86_64
license = GPL-3.0-or-later
makedepends = cargo
depends = owlry
source = owlry-0.4.10.tar.gz::https://somegit.dev/Owlibou/owlry/archive/v0.4.10.tar.gz
b2sums = 2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d
depends = owlry-core
source = owlry-plugin-websearch-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-websearch-v1.0.2.tar.gz
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
pkgname = owlry-plugin-websearch

View File

@@ -1,41 +1,41 @@
# Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-plugin-websearch
pkgver=0.4.10
pkgver=1.0.2
pkgrel=1
pkgdesc="Web search plugin for Owlry (DuckDuckGo, Google, etc.)"
pkgdesc="Web search plugin for Owlry — search DuckDuckGo, Google, and custom engines"
arch=('x86_64')
url="https://somegit.dev/Owlibou/owlry"
url="https://somegit.dev/Owlibou/owlry-plugins"
license=('GPL-3.0-or-later')
depends=('owlry')
depends=('owlry-core')
makedepends=('cargo')
install=owlry-plugin-websearch.install
source=("owlry-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('2d3761ee892d7f283a65fa58ca6206735b1b3b6e7f764f839e8a2cbd2fee2ce446c4b992e664790439393e6cb2f21fb21abacdeaf8de9eca4514da86c608216d')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
_cratename=owlry-plugin-websearch
prepare() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build -p $_cratename --frozen --release
}
check() {
cd "owlry"
cd "owlry-plugins"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo test -p $_cratename --frozen
}
package() {
cd "owlry"
cd "owlry-plugins"
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-bookmarks"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -20,6 +20,7 @@ use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
@@ -39,7 +40,7 @@ const PROVIDER_TYPE_ID: &str = "bookmarks";
/// Bookmarks provider state - holds cached items
struct BookmarksState {
/// Cached bookmark items (returned immediately on refresh)
items: Vec<PluginItem>,
items: Arc<Mutex<Vec<PluginItem>>>,
/// Flag to prevent concurrent background loads
loading: Arc<AtomicBool>,
}
@@ -47,7 +48,7 @@ struct BookmarksState {
impl BookmarksState {
fn new() -> Self {
Self {
items: Vec::new(),
items: Arc::new(Mutex::new(Vec::new())),
loading: Arc::new(AtomicBool::new(false)),
}
}
@@ -225,10 +226,13 @@ impl BookmarksState {
}
}
fn load_bookmarks(&mut self) {
fn load_bookmarks(&self) {
// Fast path: load from cache immediately
if self.items.is_empty() {
self.items = Self::load_cached_bookmarks();
{
let mut items = self.items.lock().unwrap_or_else(|e| e.into_inner());
if items.is_empty() {
*items = Self::load_cached_bookmarks();
}
}
// Don't start another background load if one is already running
@@ -238,6 +242,7 @@ impl BookmarksState {
// Spawn background thread to refresh bookmarks
let loading = self.loading.clone();
let shared_items = self.items.clone();
thread::spawn(move || {
let mut items = Vec::new();
@@ -256,6 +261,11 @@ impl BookmarksState {
// Save to cache for next startup
Self::save_cached_bookmarks(&items);
// Update shared state so subsequent refreshes see the new data
if let Ok(mut shared) = shared_items.lock() {
*shared = items;
}
loading.store(false, Ordering::SeqCst);
});
}
@@ -316,7 +326,8 @@ impl BookmarksState {
/// Read Firefox bookmarks using rusqlite (synchronous, bundled SQLite)
fn read_firefox_bookmarks(places_path: &PathBuf, items: &mut Vec<PluginItem>) {
let temp_dir = std::env::temp_dir();
let temp_db = temp_dir.join("owlry_places_temp.sqlite");
let pid = std::process::id();
let temp_db = temp_dir.join(format!("owlry_places_{}.sqlite", pid));
// Copy database to temp location to avoid locking issues
if fs::copy(places_path, &temp_db).is_err() {
@@ -332,7 +343,7 @@ impl BookmarksState {
// Copy favicons database if available
let favicons_path = Self::firefox_favicons_path(places_path);
let temp_favicons = temp_dir.join("owlry_favicons_temp.sqlite");
let temp_favicons = temp_dir.join(format!("owlry_favicons_{}.sqlite", pid));
if let Some(ref fp) = favicons_path {
let _ = fs::copy(fp, &temp_favicons);
let fav_wal = fp.with_extension("sqlite-wal");
@@ -534,13 +545,14 @@ extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec<PluginItem> {
}
// SAFETY: We created this handle from Box<BookmarksState>
let state = unsafe { &mut *(handle.ptr as *mut BookmarksState) };
let state = unsafe { &*(handle.ptr as *const BookmarksState) };
// Load bookmarks
state.load_bookmarks();
// Return items
state.items.to_vec().into()
let items = state.items.lock().unwrap_or_else(|e| e.into_inner());
items.to_vec().into()
}
extern "C" fn provider_query(_handle: ProviderHandle, _query: RStr<'_>) -> RVec<PluginItem> {
@@ -578,7 +590,7 @@ mod tests {
#[test]
fn test_bookmarks_state_new() {
let state = BookmarksState::new();
assert!(state.items.is_empty());
assert!(state.items.lock().unwrap().is_empty());
}
#[test]

View File

@@ -1,23 +0,0 @@
[package]
name = "owlry-plugin-calculator"
version = "1.0.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Calculator plugin for owlry - evaluates mathematical expressions"
keywords = ["owlry", "plugin", "calculator"]
categories = ["mathematics"]
[lib]
crate-type = ["cdylib"] # Compile as dynamic library (.so)
[dependencies]
# Plugin API for owlry
owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" }
# Math expression evaluation
meval = "0.2"
# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc)
abi_stable = "0.11"

View File

@@ -1,231 +0,0 @@
//! Calculator Plugin for Owlry
//!
//! A dynamic provider that evaluates mathematical expressions.
//! Supports queries prefixed with `=` or `calc `.
//!
//! Examples:
//! - `= 5 + 3` → 8
//! - `calc sqrt(16)` → 4
//! - `= pi * 2` → 6.283185...
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, owlry_plugin,
};
// Plugin metadata
const PLUGIN_ID: &str = "calculator";
const PLUGIN_NAME: &str = "Calculator";
const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION");
const PLUGIN_DESCRIPTION: &str = "Evaluate mathematical expressions";
// Provider metadata
const PROVIDER_ID: &str = "calculator";
const PROVIDER_NAME: &str = "Calculator";
const PROVIDER_PREFIX: &str = "=";
const PROVIDER_ICON: &str = "accessories-calculator";
const PROVIDER_TYPE_ID: &str = "calc";
/// Calculator provider state (empty for now, but could cache results)
struct CalculatorState;
// ============================================================================
// Plugin Interface Implementation
// ============================================================================
extern "C" fn plugin_info() -> PluginInfo {
PluginInfo {
id: RString::from(PLUGIN_ID),
name: RString::from(PLUGIN_NAME),
version: RString::from(PLUGIN_VERSION),
description: RString::from(PLUGIN_DESCRIPTION),
api_version: API_VERSION,
}
}
extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
vec![ProviderInfo {
id: RString::from(PROVIDER_ID),
name: RString::from(PROVIDER_NAME),
prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)),
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Dynamic,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 10000, // Dynamic: calculator results first
}]
.into()
}
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
// Create state and return handle
let state = Box::new(CalculatorState);
ProviderHandle::from_box(state)
}
extern "C" fn provider_refresh(_handle: ProviderHandle) -> RVec<PluginItem> {
// Dynamic provider - refresh does nothing
RVec::new()
}
extern "C" fn provider_query(_handle: ProviderHandle, query: RStr<'_>) -> RVec<PluginItem> {
let query_str = query.as_str();
// Extract expression from query
let expr = match extract_expression(query_str) {
Some(e) if !e.is_empty() => e,
_ => return RVec::new(),
};
// Evaluate the expression
match evaluate_expression(expr) {
Some(item) => vec![item].into(),
None => RVec::new(),
}
}
extern "C" fn provider_drop(handle: ProviderHandle) {
if !handle.ptr.is_null() {
// SAFETY: We created this handle from Box<CalculatorState>
unsafe {
handle.drop_as::<CalculatorState>();
}
}
}
// Register the plugin vtable
owlry_plugin! {
info: plugin_info,
providers: plugin_providers,
init: provider_init,
refresh: provider_refresh,
query: provider_query,
drop: provider_drop,
}
// ============================================================================
// Calculator Logic
// ============================================================================
/// Extract expression from query (handles `= expr` and `calc expr` formats)
fn extract_expression(query: &str) -> Option<&str> {
let trimmed = query.trim();
// Support both "= expr" and "=expr" (with or without space)
if let Some(expr) = trimmed.strip_prefix("= ") {
Some(expr.trim())
} else if let Some(expr) = trimmed.strip_prefix('=') {
Some(expr.trim())
} else if let Some(expr) = trimmed.strip_prefix("calc ") {
Some(expr.trim())
} else {
// For filter mode - accept raw expressions
Some(trimmed)
}
}
/// Evaluate a mathematical expression and return a PluginItem
fn evaluate_expression(expr: &str) -> Option<PluginItem> {
match meval::eval_str(expr) {
Ok(result) => {
// Format result nicely
let result_str = format_result(result);
Some(
PluginItem::new(
format!("calc:{}", expr),
result_str.clone(),
format!("sh -c 'echo -n \"{}\" | wl-copy'", result_str),
)
.with_description(format!("= {}", expr))
.with_icon(PROVIDER_ICON)
.with_keywords(vec!["math".to_string(), "calculator".to_string()]),
)
}
Err(_) => None,
}
}
/// Format a numeric result nicely
fn format_result(result: f64) -> String {
if result.fract() == 0.0 && result.abs() < 1e15 {
// Integer result
format!("{}", result as i64)
} else {
// Float result with reasonable precision, trimming trailing zeros
let formatted = format!("{:.10}", result);
formatted
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_expression() {
assert_eq!(extract_expression("= 5+3"), Some("5+3"));
assert_eq!(extract_expression("=5+3"), Some("5+3"));
assert_eq!(extract_expression("calc 5+3"), Some("5+3"));
assert_eq!(extract_expression(" = 5 + 3 "), Some("5 + 3"));
assert_eq!(extract_expression("5+3"), Some("5+3")); // Raw expression
}
#[test]
fn test_format_result() {
assert_eq!(format_result(8.0), "8");
assert_eq!(format_result(2.5), "2.5");
assert_eq!(format_result(3.14159265358979), "3.1415926536");
}
#[test]
fn test_evaluate_basic() {
let item = evaluate_expression("5+3").unwrap();
assert_eq!(item.name.as_str(), "8");
let item = evaluate_expression("10 * 2").unwrap();
assert_eq!(item.name.as_str(), "20");
let item = evaluate_expression("15 / 3").unwrap();
assert_eq!(item.name.as_str(), "5");
}
#[test]
fn test_evaluate_float() {
let item = evaluate_expression("5/2").unwrap();
assert_eq!(item.name.as_str(), "2.5");
}
#[test]
fn test_evaluate_functions() {
let item = evaluate_expression("sqrt(16)").unwrap();
assert_eq!(item.name.as_str(), "4");
let item = evaluate_expression("abs(-5)").unwrap();
assert_eq!(item.name.as_str(), "5");
}
#[test]
fn test_evaluate_constants() {
let item = evaluate_expression("pi").unwrap();
assert!(item.name.as_str().starts_with("3.14159"));
let item = evaluate_expression("e").unwrap();
assert!(item.name.as_str().starts_with("2.718"));
}
#[test]
fn test_evaluate_invalid() {
assert!(evaluate_expression("").is_none());
assert!(evaluate_expression("invalid").is_none());
assert!(evaluate_expression("5 +").is_none());
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-clipboard"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-emoji"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -33,7 +33,9 @@ struct EmojiState {
impl EmojiState {
fn new() -> Self {
Self { items: Vec::new() }
let mut state = Self { items: Vec::new() };
state.load_emojis();
state
}
fn load_emojis(&mut self) {
@@ -471,12 +473,9 @@ extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec<PluginItem> {
}
// SAFETY: We created this handle from Box<EmojiState>
let state = unsafe { &mut *(handle.ptr as *mut EmojiState) };
let state = unsafe { &*(handle.ptr as *const EmojiState) };
// Load emojis
state.load_emojis();
// Return items
// Return cached items (loaded once at init)
state.items.to_vec().into()
}
@@ -515,20 +514,15 @@ mod tests {
#[test]
fn test_emoji_state_new() {
let state = EmojiState::new();
assert!(state.items.is_empty());
}
#[test]
fn test_emoji_count() {
let mut state = EmojiState::new();
state.load_emojis();
assert!(state.items.len() > 100, "Should have more than 100 emojis");
assert!(
state.items.len() > 100,
"Should have more than 100 emojis loaded at init"
);
}
#[test]
fn test_emoji_has_grinning_face() {
let mut state = EmojiState::new();
state.load_emojis();
let state = EmojiState::new();
let grinning = state
.items
@@ -542,8 +536,7 @@ mod tests {
#[test]
fn test_emoji_command_format() {
let mut state = EmojiState::new();
state.load_emojis();
let state = EmojiState::new();
let item = &state.items[0];
assert!(item.command.as_str().contains("wl-copy"));
@@ -552,8 +545,7 @@ mod tests {
#[test]
fn test_emojis_have_keywords() {
let mut state = EmojiState::new();
state.load_emojis();
let state = EmojiState::new();
// Check that items have keywords for searching
let heart = state.items.iter().find(|i| i.name.as_str() == "red heart");

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-filesearch"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-media"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-pomodoro"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-scripts"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-ssh"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
@@ -21,3 +21,6 @@ abi_stable = "0.11"
# For finding ~/.ssh/config
dirs = "5.0"
# For reading owlry config.toml
toml = "0.8"

View File

@@ -28,9 +28,6 @@ const PROVIDER_PREFIX: &str = ":ssh";
const PROVIDER_ICON: &str = "utilities-terminal";
const PROVIDER_TYPE_ID: &str = "ssh";
// Default terminal command (TODO: make configurable via plugin config)
const DEFAULT_TERMINAL: &str = "kitty";
/// SSH provider state - holds cached items
struct SshState {
items: Vec<PluginItem>,
@@ -39,15 +36,36 @@ struct SshState {
impl SshState {
fn new() -> Self {
// Try to detect terminal from environment, fall back to default
let terminal = std::env::var("TERMINAL").unwrap_or_else(|_| DEFAULT_TERMINAL.to_string());
let terminal = Self::load_terminal_from_config();
Self {
items: Vec::new(),
terminal_command: terminal,
}
}
fn load_terminal_from_config() -> String {
// Try [plugins.ssh] in config.toml
let config_path = dirs::config_dir().map(|d| d.join("owlry").join("config.toml"));
if let Some(content) = config_path.and_then(|p| fs::read_to_string(p).ok())
&& let Ok(toml) = content.parse::<toml::Table>()
{
if let Some(plugins) = toml.get("plugins").and_then(|v| v.as_table())
&& let Some(ssh) = plugins.get("ssh").and_then(|v| v.as_table())
&& let Some(terminal) = ssh.get("terminal").and_then(|v| v.as_str())
{
return terminal.to_string();
}
}
// Fall back to $TERMINAL env var
if let Ok(terminal) = std::env::var("TERMINAL") {
return terminal;
}
// Last resort
"xdg-terminal-exec".to_string()
}
fn ssh_config_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".ssh").join("config"))
}

View File

@@ -1,20 +0,0 @@
[package]
name = "owlry-plugin-system"
version = "1.0.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "System plugin for owlry - power and session management commands"
keywords = ["owlry", "plugin", "system", "power"]
categories = ["os"]
[lib]
crate-type = ["cdylib"] # Compile as dynamic library (.so)
[dependencies]
# Plugin API for owlry
owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" }
# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc)
abi_stable = "0.11"

View File

@@ -1,257 +0,0 @@
//! System Plugin for Owlry
//!
//! A static provider that provides system power and session management commands.
//!
//! Commands:
//! - Shutdown - Power off the system
//! - Reboot - Restart the system
//! - Reboot into BIOS - Restart into UEFI/BIOS setup
//! - Suspend - Suspend to RAM
//! - Hibernate - Suspend to disk
//! - Lock Screen - Lock the session
//! - Log Out - End the current session
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, owlry_plugin,
};
// Plugin metadata
const PLUGIN_ID: &str = "system";
const PLUGIN_NAME: &str = "System";
const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION");
const PLUGIN_DESCRIPTION: &str = "Power and session management commands";
// Provider metadata
const PROVIDER_ID: &str = "system";
const PROVIDER_NAME: &str = "System";
const PROVIDER_PREFIX: &str = ":sys";
const PROVIDER_ICON: &str = "system-shutdown";
const PROVIDER_TYPE_ID: &str = "system";
/// System provider state - holds cached items
struct SystemState {
items: Vec<PluginItem>,
}
impl SystemState {
fn new() -> Self {
Self { items: Vec::new() }
}
fn load_commands(&mut self) {
self.items.clear();
// Define system commands
// Format: (id, name, description, icon, command)
let commands: &[(&str, &str, &str, &str, &str)] = &[
(
"system:shutdown",
"Shutdown",
"Power off the system",
"system-shutdown",
"systemctl poweroff",
),
(
"system:reboot",
"Reboot",
"Restart the system",
"system-reboot",
"systemctl reboot",
),
(
"system:reboot-bios",
"Reboot into BIOS",
"Restart into UEFI/BIOS setup",
"system-reboot",
"systemctl reboot --firmware-setup",
),
(
"system:suspend",
"Suspend",
"Suspend to RAM",
"system-suspend",
"systemctl suspend",
),
(
"system:hibernate",
"Hibernate",
"Suspend to disk",
"system-suspend-hibernate",
"systemctl hibernate",
),
(
"system:lock",
"Lock Screen",
"Lock the session",
"system-lock-screen",
"loginctl lock-session",
),
(
"system:logout",
"Log Out",
"End the current session",
"system-log-out",
"loginctl terminate-session self",
),
];
for (id, name, description, icon, command) in commands {
self.items.push(
PluginItem::new(*id, *name, *command)
.with_description(*description)
.with_icon(*icon)
.with_keywords(vec!["power".to_string(), "system".to_string()]),
);
}
}
}
// ============================================================================
// Plugin Interface Implementation
// ============================================================================
extern "C" fn plugin_info() -> PluginInfo {
PluginInfo {
id: RString::from(PLUGIN_ID),
name: RString::from(PLUGIN_NAME),
version: RString::from(PLUGIN_VERSION),
description: RString::from(PLUGIN_DESCRIPTION),
api_version: API_VERSION,
}
}
extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
vec![ProviderInfo {
id: RString::from(PROVIDER_ID),
name: RString::from(PROVIDER_NAME),
prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)),
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
let state = Box::new(SystemState::new());
ProviderHandle::from_box(state)
}
extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec<PluginItem> {
if handle.ptr.is_null() {
return RVec::new();
}
// SAFETY: We created this handle from Box<SystemState>
let state = unsafe { &mut *(handle.ptr as *mut SystemState) };
// Load/reload commands
state.load_commands();
// Return items
state.items.to_vec().into()
}
extern "C" fn provider_query(_handle: ProviderHandle, _query: RStr<'_>) -> RVec<PluginItem> {
// Static provider - query is handled by the core using cached items
RVec::new()
}
extern "C" fn provider_drop(handle: ProviderHandle) {
if !handle.ptr.is_null() {
// SAFETY: We created this handle from Box<SystemState>
unsafe {
handle.drop_as::<SystemState>();
}
}
}
// Register the plugin vtable
owlry_plugin! {
info: plugin_info,
providers: plugin_providers,
init: provider_init,
refresh: provider_refresh,
query: provider_query,
drop: provider_drop,
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_state_new() {
let state = SystemState::new();
assert!(state.items.is_empty());
}
#[test]
fn test_system_commands_loaded() {
let mut state = SystemState::new();
state.load_commands();
assert!(state.items.len() >= 6);
// Check for specific commands
let names: Vec<&str> = state.items.iter().map(|i| i.name.as_str()).collect();
assert!(names.contains(&"Shutdown"));
assert!(names.contains(&"Reboot"));
assert!(names.contains(&"Suspend"));
assert!(names.contains(&"Lock Screen"));
assert!(names.contains(&"Log Out"));
}
#[test]
fn test_reboot_bios_command() {
let mut state = SystemState::new();
state.load_commands();
let bios_cmd = state
.items
.iter()
.find(|i| i.name.as_str() == "Reboot into BIOS")
.expect("Reboot into BIOS should exist");
assert_eq!(
bios_cmd.command.as_str(),
"systemctl reboot --firmware-setup"
);
}
#[test]
fn test_commands_have_icons() {
let mut state = SystemState::new();
state.load_commands();
for item in &state.items {
assert!(
item.icon.is_some(),
"Item '{}' should have an icon",
item.name.as_str()
);
}
}
#[test]
fn test_commands_have_descriptions() {
let mut state = SystemState::new();
state.load_commands();
for item in &state.items {
assert!(
item.description.is_some(),
"Item '{}' should have a description",
item.name.as_str()
);
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-systemd"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-weather"
version = "1.0.0"
version = "1.0.3"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
@@ -20,7 +20,7 @@ owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugi
abi_stable = "0.11"
# HTTP client for weather API requests
reqwest = { version = "0.13", features = ["blocking", "json"] }
reqwest = { version = "0.13", default-features = false, features = ["native-tls", "blocking", "json"] }
# JSON parsing for API responses
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-websearch"
version = "1.0.0"
version = "1.0.2"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
@@ -18,3 +18,7 @@ owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugi
# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc)
abi_stable = "0.11"
# For reading owlry config.toml
toml = "0.8"
dirs = "5.0"

View File

@@ -13,6 +13,7 @@ use owlry_plugin_api::{
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, owlry_plugin,
};
use std::fs;
// Plugin metadata
const PLUGIN_ID: &str = "websearch";
@@ -143,6 +144,21 @@ impl WebSearchState {
}
}
fn load_engine_from_config() -> String {
let config_path = dirs::config_dir().map(|d| d.join("owlry").join("config.toml"));
if let Some(content) = config_path.and_then(|p| fs::read_to_string(p).ok())
&& let Ok(toml) = content.parse::<toml::Table>()
{
if let Some(plugins) = toml.get("plugins").and_then(|v| v.as_table())
&& let Some(websearch) = plugins.get("websearch").and_then(|v| v.as_table())
&& let Some(engine) = websearch.get("engine").and_then(|v| v.as_str())
{
return engine.to_string();
}
}
DEFAULT_ENGINE.to_string()
}
// ============================================================================
// Plugin Interface Implementation
// ============================================================================
@@ -172,8 +188,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
}
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
// TODO: Read search engine from config when plugin config is available
let state = Box::new(WebSearchState::new());
let engine = load_engine_from_config();
let state = Box::new(WebSearchState::with_engine(&engine));
ProviderHandle::from_box(state)
}

View File

@@ -115,7 +115,7 @@ id = "my-lua-plugin"
name = "My Lua Plugin"
version = "0.1.0"
description = "A custom Lua plugin"
entry_point = "init.lua"
entry = "main.lua"
[[providers]]
id = "myluaprovider"
@@ -126,23 +126,27 @@ type = "static"
type_id = "mylua"
```
Create `~/.config/owlry/plugins/my-lua-plugin/init.lua`:
Create `~/.config/owlry/plugins/my-lua-plugin/main.lua`:
```lua
local owlry = require("owlry")
-- Called once at startup for static providers
function refresh()
return {
owlry.item("item-1", "Hello from Lua", "echo 'Hello Lua!'")
:description("A Lua greeting")
:icon("face-smile"),
}
end
-- Called per-keystroke for dynamic providers
function query(q)
return {}
end
-- owlry table is pre-registered globally (no require needed)
owlry.provider.register({
name = "myluaprovider",
display_name = "My Lua Provider",
type_id = "mylua",
default_icon = "application-x-executable",
prefix = ":mylua",
refresh = function()
return {
{
id = "item-1",
name = "Hello from Lua",
command = "echo 'Hello Lua!'",
description = "A Lua greeting",
icon = "face-smile",
},
}
end,
})
```
---
@@ -330,8 +334,8 @@ id = "my-plugin"
name = "My Plugin"
version = "1.0.0"
description = "Plugin description"
entry_point = "init.lua"
owlry_version = ">=0.4.0" # Optional version constraint
entry = "main.lua" # Canonical field; entry_point is accepted as an alias
owlry_version = ">=1.0.0" # Optional version constraint
[permissions]
fs = ["read"] # File system access
@@ -350,14 +354,10 @@ type_id = "shortid"
### Lua API
```lua
local owlry = require("owlry")
-- owlry table is pre-registered globally (no require needed)
-- Create items
local item = owlry.item(id, name, command)
:description("Description")
:icon("icon-name")
:terminal(false)
:keywords({"tag1", "tag2"})
-- Items are plain Lua tables returned from refresh/query callbacks:
-- { id = "...", name = "...", command = "...", description = "...", icon = "...", terminal = false, tags = {"...", "..."} }
-- Notifications
owlry.notify("Title", "Body")
@@ -388,73 +388,123 @@ local value = owlry.cache.get("key")
### Provider Functions
```lua
-- Static provider: called once at startup
function refresh()
return {
owlry.item("id1", "Item 1", "command1"),
owlry.item("id2", "Item 2", "command2"),
}
end
-- Static provider: called once at startup and on reload
owlry.provider.register({
name = "my-provider",
display_name = "My Provider",
prefix = ":my",
refresh = function()
return {
{ id = "id1", name = "Item 1", command = "command1" },
{ id = "id2", name = "Item 2", command = "command2" },
}
end,
})
-- Dynamic provider: called on each keystroke
function query(q)
if q == "" then
return {}
end
return {
owlry.item("result", "Result for: " .. q, "echo " .. q),
}
end
owlry.provider.register({
name = "my-search",
display_name = "My Search",
prefix = "?my",
query = function(q)
if q == "" then return {} end
return {
{ id = "result", name = "Result for: " .. q, command = "echo " .. q },
}
end,
})
```
---
## Rune Plugin API
Rune plugins use a Rust-like syntax with memory safety.
Rune plugins use a Rust-like syntax with memory safety. Requires `owlry-rune` runtime.
```bash
# Install Rune runtime
yay -S owlry-rune
# Create plugin directory
mkdir -p ~/.config/owlry/plugins/my-rune-plugin
```
### Plugin Manifest
Rune plugins declare providers in `[[providers]]` sections of `plugin.toml`. The runtime reads these declarations and maps them to the plugin's `refresh()` and `query()` functions.
```toml
[plugin]
id = "my-rune-plugin"
name = "My Rune Plugin"
version = "1.0.0"
entry_point = "main.rn"
description = "A custom Rune plugin"
entry = "main.rn" # Default: main.rn; entry_point also accepted
[[providers]]
id = "runeprovider"
name = "Rune Provider"
type = "static"
id = "myrune"
name = "My Rune Provider"
prefix = ":myrune" # Activates with :myrune prefix in search
icon = "application-x-executable"
type = "static" # "static" (refresh at startup) or "dynamic" (query per keystroke)
type_id = "myrune" # Short ID shown as badge in UI
```
### Rune API
```rune
use owlry::{Item, log, notify};
use owlry::Item;
/// Called once at startup and on each hot-reload for static providers
pub fn refresh() {
let items = [];
items.push(Item::new("id", "Name", "command")
.description("Description")
.icon("icon-name"));
items.push(Item::new("item-1", "Hello from Rune", "echo 'Hello!'")
.description("A Rune greeting")
.icon("face-smile")
.keywords(["hello", "rune"]));
items.push(Item::new("item-2", "Another Item", "notify-send 'Hi'")
.description("Send a notification")
.icon("dialog-information"));
items
}
/// Called per keystroke for dynamic providers
pub fn query(q) {
if q.is_empty() {
return [];
}
log::info(`Query: {q}`);
[Item::new("result", `Result: {q}`, `echo {q}`)]
[Item::new("result", `Result: {q}`, `echo {q}`)
.description("Search result")]
}
```
### Item Builder
The `Item` type is provided by the `owlry` module:
```rune
// Create an item with required fields
let item = Item::new(id, name, command);
// Optional builder methods (all return Item for chaining)
item = item.description("Description text");
item = item.icon("icon-name"); // Freedesktop icon name
item = item.keywords(["tag1", "tag2"]); // Search keywords
```
### Logging
```rune
owlry::log_info("Info message");
owlry::log_warn("Warning message");
owlry::log_error("Error message");
owlry::log_debug("Debug message");
```
---
## Best Practices
@@ -482,19 +532,22 @@ extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec<PluginItem> {
```
```lua
-- Lua: Wrap in pcall for safety
function refresh()
local ok, result = pcall(function()
return load_items()
end)
-- Lua: Wrap refresh callback in pcall for safety
owlry.provider.register({
name = "safe-provider",
refresh = function()
local ok, result = pcall(function()
return load_items()
end)
if not ok then
owlry.log.error("Failed: " .. result)
return {}
end
if not ok then
owlry.log.error("Failed: " .. result)
return {}
end
return result
end
return result
end,
})
```
### Icons
@@ -524,6 +577,29 @@ RUST_LOG=debug owlry
---
## Hot Reload
User plugins in `~/.config/owlry/plugins/` are automatically reloaded when files change.
The daemon watches the plugins directory and reloads all script runtimes when any file
is created, modified, or deleted. No daemon restart is needed.
**What triggers a reload:**
- Creating a new plugin directory with `plugin.toml`
- Editing a plugin's script files (`main.lua`, `main.rn`, etc.)
- Editing a plugin's `plugin.toml`
- Deleting a plugin directory
**What does NOT trigger a reload:**
- Changes to native plugins (`.so` files) — these require a daemon restart
- Changes to runtime libraries in `/usr/lib/owlry/runtimes/` — daemon restart needed
**Reload behavior:**
- All script runtimes (Lua, Rune) are fully reloaded
- Existing search results may briefly show stale data during reload
- Errors in plugins are logged but don't affect other plugins
---
## Publishing to AUR
### PKGBUILD Template

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,419 @@
# Converter Plugin — Design Spec
**Date:** 2026-03-26
**Status:** Draft
**Goal:** Create an owlry plugin for unit and currency conversion with natural speech input.
---
## 1. Overview
A dynamic plugin that converts between units of measurement and currencies. Users type natural expressions like `102F to C` or `> 50 kg in lb` and see conversion results inline.
**Key UX decisions:**
- Trigger prefix `>` for explicit conversion, plus auto-detection
- Accepts `to` and `in` as connector words
- No space required between number and unit (`102F` works)
- When no target unit specified, shows the most common conversions for that category
- Currency rates from ECB (free, no API key, ~30 fiat currencies, daily)
- Case-insensitive matching with generous aliases
---
## 2. Plugin Identity
| Field | Value |
|-------|-------|
| Plugin ID | `converter` |
| Provider ID | `converter` |
| Type ID | `conv` |
| Provider Kind | Dynamic |
| Trigger prefix | `>` |
| Search prefix | `:conv` |
| Position | Normal |
| Priority | 9000 |
| Icon | `accessories-text-editor` (or `edit-find-replace`) |
| Crate | `owlry-plugin-converter` |
| Repo | `owlry-plugins` |
---
## 3. Query Parsing
### Input patterns
All of these are valid and must parse correctly:
```
102F to C → 102 Fahrenheit → Celsius
102 F to C → same
102 fahrenheit in celsius → same (full names)
102°F to °C → same (symbols)
> 102 F → prefix, no target → show all temperature conversions
102 F → auto-detect, no target → show all
102F → auto-detect, no space, no target → show all
> 50 kg in lb → prefix, specific target
50 kg → auto-detect, show common weight conversions
100 eur to usd → currency
100€ to $ → currency with symbols
```
### Parser rules
1. Strip prefix `>` if present (and trim whitespace)
2. Extract number: integer or decimal at start of input (`102`, `3.5`, `.5`, `0.25`)
3. Extract source unit: immediately adjacent to number OR space-separated. Match against known unit aliases (case-insensitive, longest match first)
4. If remaining input contains `to` or `in` (case-insensitive, word boundary), extract target unit from what follows
5. If no connector word and remaining text after source unit is non-empty, try to parse it as a target unit anyway (handles `100 km miles`)
### Auto-detection
The plugin's `query()` is called for every keystroke. To avoid false positives:
- Only match if the input starts with a number
- Only match if the text after the number resolves to a known unit alias
- Minimum input length: 2 characters (number + unit)
- Don't match if the text after the number is not a unit alias (e.g., `100x`, `102nd`)
### Ambiguity resolution
| Input | Resolves to | Why |
|-------|-------------|-----|
| `oz` | Ounce (weight) | More common than fluid ounce |
| `fl oz`, `floz` | Fluid ounce | Explicit |
| `ton` | Metric ton (1000 kg) | International default |
| `short_ton`, `ton_us` | US short ton (907 kg) | Explicit |
| `gal` | US gallon | More common globally in software |
| `gal_uk`, `imp_gal` | Imperial gallon | Explicit |
---
## 4. Unit System Architecture
### Conversion model
Each category has a **base unit**. Every unit defines a conversion factor relative to the base:
```
value_in_base = input_value * from_unit.to_base(input_value)
result = target_unit.from_base(value_in_base)
```
For most units this is a simple multiply/divide by factor. Temperature uses offset formulas.
### Unit definition
```rust
struct UnitDef {
id: &'static str, // canonical name: "kilometer"
symbol: &'static str, // display symbol: "km"
aliases: &'static [&'static str], // match aliases: ["km", "kms", "kilometers", "kilometre"]
category: Category,
conversion: Conversion,
}
enum Conversion {
Factor(f64), // multiply by factor to get base unit
Custom { // for temperature
to_base: fn(f64) -> f64,
from_base: fn(f64) -> f64,
},
}
enum Category {
Temperature,
Length,
Weight,
Volume,
Speed,
Area,
Data,
Time,
Pressure,
Energy,
Currency,
}
```
### Base units per category
| Category | Base unit | Rationale |
|----------|-----------|-----------|
| Temperature | Kelvin | Standard SI, no negative zero issues |
| Length | Meter | SI |
| Weight | Kilogram | SI |
| Volume | Liter | Practical |
| Speed | m/s | SI |
| Area | m² | SI |
| Data | Byte | Fundamental |
| Time | Second | SI |
| Pressure | Pascal | SI |
| Energy | Joule | SI |
| Currency | EUR | ECB reports rates relative to EUR |
### Temperature conversions (special case)
```
Celsius → Kelvin: K = C + 273.15
Fahrenheit → Kelvin: K = (F - 32) * 5/9 + 273.15
Kelvin → Celsius: C = K - 273.15
Kelvin → Fahrenheit: F = (K - 273.15) * 9/5 + 32
```
---
## 5. Unit Table
### Temperature
| Unit | Symbol | Aliases | Factor/Conversion |
|------|--------|---------|-------------------|
| Celsius | °C | `c`, `°c`, `celsius`, `degc`, `centigrade` | Custom |
| Fahrenheit | °F | `f`, `°f`, `fahrenheit`, `degf` | Custom |
| Kelvin | K | `k`, `kelvin` | Base |
### Length
| Unit | Symbol | Aliases | Factor (to meters) |
|------|--------|---------|-------------------|
| Millimeter | mm | `mm`, `millimeter`, `millimeters`, `millimetre` | 0.001 |
| Centimeter | cm | `cm`, `centimeter`, `centimeters`, `centimetre` | 0.01 |
| Meter | m | `m`, `meter`, `meters`, `metre`, `metres` | 1.0 (base) |
| Kilometer | km | `km`, `kms`, `kilometer`, `kilometers`, `kilometre` | 1000.0 |
| Inch | in | `in`, `inch`, `inches`, `"` | 0.0254 |
| Foot | ft | `ft`, `foot`, `feet`, `'` | 0.3048 |
| Yard | yd | `yd`, `yard`, `yards` | 0.9144 |
| Mile | mi | `mi`, `mile`, `miles` | 1609.344 |
| Nautical mile | nmi | `nmi`, `nautical_mile`, `nautical_miles` | 1852.0 |
### Weight/Mass
| Unit | Symbol | Aliases | Factor (to kg) |
|------|--------|---------|----------------|
| Milligram | mg | `mg`, `milligram`, `milligrams` | 0.000001 |
| Gram | g | `g`, `gram`, `grams` | 0.001 |
| Kilogram | kg | `kg`, `kilogram`, `kilograms`, `kilo`, `kilos` | 1.0 (base) |
| Metric ton | t | `t`, `ton`, `tons`, `tonne`, `tonnes`, `metric_ton` | 1000.0 |
| US short ton | short_ton | `short_ton`, `ton_us` | 907.185 |
| Ounce | oz | `oz`, `ounce`, `ounces` | 0.0283495 |
| Pound | lb | `lb`, `lbs`, `pound`, `pounds` | 0.453592 |
| Stone | st | `st`, `stone`, `stones` | 6.35029 |
### Volume
| Unit | Symbol | Aliases | Factor (to liters) |
|------|--------|---------|-------------------|
| Milliliter | ml | `ml`, `milliliter`, `milliliters`, `millilitre` | 0.001 |
| Liter | l | `l`, `liter`, `liters`, `litre`, `litres` | 1.0 (base) |
| US gallon | gal | `gal`, `gallon`, `gallons` | 3.78541 |
| Imperial gallon | imp_gal | `imp_gal`, `gal_uk`, `imperial_gallon` | 4.54609 |
| Quart | qt | `qt`, `quart`, `quarts` | 0.946353 |
| Pint | pt | `pt`, `pint`, `pints` | 0.473176 |
| Cup | cup | `cup`, `cups` | 0.236588 |
| Fluid ounce | fl oz | `floz`, `fl_oz`, `fluid_ounce`, `fluid_ounces` | 0.0295735 |
| Tablespoon | tbsp | `tbsp`, `tablespoon`, `tablespoons` | 0.0147868 |
| Teaspoon | tsp | `tsp`, `teaspoon`, `teaspoons` | 0.00492892 |
### Speed
| Unit | Symbol | Aliases | Factor (to m/s) |
|------|--------|---------|-----------------|
| m/s | m/s | `m/s`, `mps`, `meters_per_second` | 1.0 (base) |
| km/h | km/h | `km/h`, `kmh`, `kph`, `kilometers_per_hour` | 0.277778 |
| mph | mph | `mph`, `miles_per_hour` | 0.44704 |
| Knot | kn | `kn`, `kt`, `knot`, `knots` | 0.514444 |
| ft/s | ft/s | `ft/s`, `fps`, `feet_per_second` | 0.3048 |
### Area
| Unit | Symbol | Aliases | Factor (to m²) |
|------|--------|---------|----------------|
| mm² | mm² | `mm2`, `sqmm`, `square_millimeter` | 0.000001 |
| cm² | cm² | `cm2`, `sqcm`, `square_centimeter` | 0.0001 |
| m² | m² | `m2`, `sqm`, `square_meter`, `square_meters` | 1.0 (base) |
| km² | km² | `km2`, `sqkm`, `square_kilometer` | 1000000.0 |
| ft² | ft² | `ft2`, `sqft`, `square_foot`, `square_feet` | 0.092903 |
| Acre | ac | `ac`, `acre`, `acres` | 4046.86 |
| Hectare | ha | `ha`, `hectare`, `hectares` | 10000.0 |
### Data
| Unit | Symbol | Aliases | Factor (to bytes) |
|------|--------|---------|-------------------|
| Byte | B | `b`, `byte`, `bytes` | 1.0 (base) |
| Kilobyte | KB | `kb`, `kilobyte`, `kilobytes` | 1000.0 |
| Megabyte | MB | `mb`, `megabyte`, `megabytes` | 1000000.0 |
| Gigabyte | GB | `gb`, `gigabyte`, `gigabytes` | 1000000000.0 |
| Terabyte | TB | `tb`, `terabyte`, `terabytes` | 1000000000000.0 |
| Kibibyte | KiB | `kib`, `kibibyte`, `kibibytes` | 1024.0 |
| Mebibyte | MiB | `mib`, `mebibyte`, `mebibytes` | 1048576.0 |
| Gibibyte | GiB | `gib`, `gibibyte`, `gibibytes` | 1073741824.0 |
| Tebibyte | TiB | `tib`, `tebibyte`, `tebibytes` | 1099511627776.0 |
### Time
| Unit | Symbol | Aliases | Factor (to seconds) |
|------|--------|---------|---------------------|
| Second | s | `s`, `sec`, `second`, `seconds` | 1.0 (base) |
| Minute | min | `min`, `minute`, `minutes` | 60.0 |
| Hour | h | `h`, `hr`, `hour`, `hours` | 3600.0 |
| Day | d | `d`, `day`, `days` | 86400.0 |
| Week | wk | `wk`, `week`, `weeks` | 604800.0 |
| Month | mo | `mo`, `month`, `months` | 2592000.0 (30 days) |
| Year | yr | `yr`, `year`, `years` | 31536000.0 (365 days) |
### Pressure
| Unit | Symbol | Aliases | Factor (to Pa) |
|------|--------|---------|----------------|
| Pascal | Pa | `pa`, `pascal`, `pascals` | 1.0 (base) |
| Hectopascal | hPa | `hpa`, `hectopascal` | 100.0 |
| Kilopascal | kPa | `kpa`, `kilopascal` | 1000.0 |
| Bar | bar | `bar`, `bars` | 100000.0 |
| Millibar | mbar | `mbar`, `millibar` | 100.0 |
| PSI | psi | `psi`, `pounds_per_square_inch` | 6894.76 |
| Atmosphere | atm | `atm`, `atmosphere`, `atmospheres` | 101325.0 |
| mmHg | mmHg | `mmhg`, `torr` | 133.322 |
### Energy
| Unit | Symbol | Aliases | Factor (to Joules) |
|------|--------|---------|-------------------|
| Joule | J | `j`, `joule`, `joules` | 1.0 (base) |
| Kilojoule | kJ | `kj`, `kilojoule`, `kilojoules` | 1000.0 |
| Calorie | cal | `cal`, `calorie`, `calories` | 4.184 |
| Kilocalorie | kcal | `kcal`, `kilocalorie`, `kilocalories` | 4184.0 |
| Watt-hour | Wh | `wh`, `watt_hour` | 3600.0 |
| Kilowatt-hour | kWh | `kwh`, `kilowatt_hour` | 3600000.0 |
| BTU | BTU | `btu`, `british_thermal_unit` | 1055.06 |
---
## 6. Currency
### Data source
ECB daily reference rates: `https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml`
Returns ~30 currencies with rates relative to EUR.
### Caching
- Cache file: `~/.cache/owlry/ecb_rates.json`
- Format: `{ "date": "2026-03-26", "rates": { "USD": 1.0832, "GBP": 0.8345, ... } }`
- Refresh when cache is older than 24 hours
- If fetch fails, use cached rates silently (stale rates > no rates)
- If no cache exists and fetch fails, currency conversions return no results
### Currency aliases
Each currency accepts ISO code, lowercase, full name, and common symbol:
| Currency | Aliases |
|----------|---------|
| EUR | `eur`, `euro`, `euros`, `€` |
| USD | `usd`, `dollar`, `dollars`, `$`, `us_dollar` |
| GBP | `gbp`, `pound_sterling`, `£`, `british_pound` |
| JPY | `jpy`, `yen`, `¥`, `japanese_yen` |
| CHF | `chf`, `swiss_franc`, `francs` |
| CAD | `cad`, `canadian_dollar`, `c$` |
| AUD | `aud`, `australian_dollar`, `a$` |
| CNY | `cny`, `yuan`, `renminbi`, `rmb` |
| SEK | `sek`, `swedish_krona`, `kronor` |
| NOK | `nok`, `norwegian_krone` |
| DKK | `dkk`, `danish_krone` |
| PLN | `pln`, `zloty`, `złoty` |
| CZK | `czk`, `czech_koruna` |
| HUF | `huf`, `forint` |
| TRY | `try`, `turkish_lira`, `lira` |
| (others) | ISO code + lowercase |
### Conversion
EUR is the base. To convert A→B: `result = value / rate_A * rate_B`
(EUR rate is implicitly 1.0 since ECB reports everything relative to EUR.)
---
## 7. Result Display
### With target unit specified
One result:
```
Name: 37.78 °C
Description: 100 °F = 37.78 °C
Icon: edit-find-replace
Command: (copy "37.78" to clipboard via wl-copy)
```
### Without target unit
Up to 5 results showing common conversions for that category:
```
Query: "100 F"
Result 1: 37.78 °C │ 100 °F = 37.78 °C
Result 2: 310.93 K │ 100 °F = 310.93 K
```
```
Query: "100 km"
Result 1: 62.14 mi │ 100 km = 62.14 mi
Result 2: 100000 m │ 100 km = 100,000 m
Result 3: 328084 ft │ 100 km = 328,084 ft
Result 4: 109361 yd │ 100 km = 109,361 yd
```
### Common conversions per category
| Category | Show (excluding source unit) |
|----------|------------------------------|
| Temperature | °C, °F, K |
| Length | m, km, ft, mi, in |
| Weight | kg, lb, oz, g, st |
| Volume | l, gal, ml, cup, fl oz |
| Speed | km/h, mph, m/s, kn |
| Area | m², ft², acres, ha, km² |
| Data | MB, GB, MiB, GiB, TB |
| Time | s, min, h, days, weeks |
| Pressure | bar, psi, atm, hPa, mmHg |
| Energy | kJ, kcal, kWh, BTU, Wh |
| Currency | USD, EUR, GBP, JPY, CNY |
### Number formatting
- Up to 4 decimal places, trailing zeros stripped
- Large numbers get thousand separators: `1,000,000`
- Very small numbers use scientific notation if < 0.0001
- Currency always shows 2 decimal places
---
## 8. File Structure
```
owlry-plugins/crates/owlry-plugin-converter/
├── Cargo.toml
└── src/
├── lib.rs # Plugin interface (vtable, init, query dispatch)
├── parser.rs # Query parsing (number + unit extraction)
├── units.rs # Unit definitions, conversion logic, category tables
└── currency.rs # ECB fetch, cache, currency-specific handling
```
### Dependencies
```toml
[dependencies]
owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" }
abi_stable = "0.11"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.13", features = ["blocking"] }
dirs = "5"
```
---
## 9. Out of Scope
- Cryptocurrency (future addition)
- Compound units (`km/h² to m/s²`)
- Math expressions in conversion (`2 * 50 kg to lb`)
- Configurable default targets per category
- Locale-aware number formatting (use `.` as decimal separator always)
- Offline currency rate bundling (rely on cache)

263
justfile
View File

@@ -1,12 +1,24 @@
# Owlry Plugins build and release automation
default:
@just --list
# === Build ===
build:
cargo build --workspace
release:
cargo build --workspace --release
plugin name:
cargo build -p owlry-plugin-{{name}} --release
plugins:
cargo build --workspace --release
# === Quality ===
check:
cargo check --workspace
cargo clippy --workspace
@@ -17,29 +29,7 @@ test:
fmt:
cargo fmt --all
plugin name:
cargo build -p owlry-plugin-{{name}} --release
plugins:
cargo build --workspace --release
show-versions:
@for dir in crates/owlry-plugin-*; do \
name=$$(basename $$dir); \
version=$$(grep '^version' $$dir/Cargo.toml | head -1 | cut -d'"' -f2); \
printf "%-35s %s\n" "$$name" "$$version"; \
done
bump-crate crate new_version:
@cd crates/{{crate}} && \
sed -i 's/^version = ".*"/version = "{{new_version}}"/' Cargo.toml
@echo "Bumped {{crate}} to {{new_version}}"
bump-all new_version:
@for dir in crates/owlry-plugin-*; do \
sed -i 's/^version = ".*"/version = "{{new_version}}"/' $$dir/Cargo.toml; \
done
@echo "Bumped all crates to {{new_version}}"
# === Install ===
install-local:
just plugins
@@ -47,3 +37,230 @@ install-local:
sudo install -Dm755 "$$f" /usr/lib/owlry/plugins/$$(basename "$$f"); \
done
@echo "Installed all plugins"
# === Version Management ===
show-versions:
#!/usr/bin/env bash
for dir in crates/owlry-plugin-*; do
name=$(basename "$dir")
ver=$(grep '^version' "$dir/Cargo.toml" | head -1 | cut -d'"' -f2)
printf " %-35s %s\n" "$name" "$ver"
done
# Bump a single plugin version, update Cargo.lock, commit
bump-crate crate new_version:
#!/usr/bin/env bash
set -euo pipefail
toml="crates/{{crate}}/Cargo.toml"
[ -f "$toml" ] || { echo "Error: $toml not found"; exit 1; }
old=$(grep '^version' "$toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
[ "$old" = "{{new_version}}" ] && { echo "{{crate}} already at {{new_version}}"; exit 0; }
echo "Bumping {{crate}} from $old to {{new_version}}"
sed -i 's/^version = ".*"/version = "{{new_version}}"/' "$toml"
cargo check -p {{crate}}
git add "$toml" Cargo.lock
git commit -m "chore({{crate}}): bump version to {{new_version}}"
echo "{{crate}} bumped to {{new_version}}"
# Bump all plugin crates to same version
bump-all new_version:
#!/usr/bin/env bash
set -euo pipefail
for dir in crates/owlry-plugin-*; do
crate=$(basename "$dir")
old=$(grep '^version' "$dir/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
[ "$old" = "{{new_version}}" ] && continue
echo "Bumping $crate from $old to {{new_version}}"
sed -i 's/^version = ".*"/version = "{{new_version}}"/' "$dir/Cargo.toml"
done
cargo check --workspace
git add crates/*/Cargo.toml Cargo.lock
git commit -m "chore: bump all plugins to {{new_version}}"
echo "All plugins bumped to {{new_version}}"
# === Tagging ===
# Tag a specific plugin (format: {crate}-v{version})
tag-crate crate:
#!/usr/bin/env bash
set -euo pipefail
ver=$(grep '^version' "crates/{{crate}}/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
tag="{{crate}}-v$ver"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo "Tag $tag already exists"
exit 0
fi
git tag -a "$tag" -m "{{crate}} v$ver"
echo "Created tag $tag"
push-tags:
git push --tags
# === AUR Package Management ===
# Stage AUR files into git index (handles embedded .git dirs)
aur-stage pkg:
#!/usr/bin/env bash
set -euo pipefail
dir="aur/{{pkg}}"
[ -d "$dir" ] || { echo "Error: $dir not found"; exit 1; }
# Build list of files to stage
files=("$dir/PKGBUILD" "$dir/.SRCINFO")
for f in "$dir"/*.install; do
[ -f "$f" ] && files+=("$f")
done
if [ -d "$dir/.git" ]; then
mv "$dir/.git" "$dir/.git.bak"
git add "${files[@]}"
mv "$dir/.git.bak" "$dir/.git"
else
git add "${files[@]}"
fi
# Update a specific plugin AUR package PKGBUILD
aur-update-pkg pkg:
#!/usr/bin/env bash
set -euo pipefail
aur_dir="aur/{{pkg}}"
[ -d "$aur_dir" ] || { echo "Error: $aur_dir not found"; exit 1; }
crate_dir="crates/{{pkg}}"
[ -d "$crate_dir" ] || { echo "Error: $crate_dir not found"; exit 1; }
ver=$(grep '^version' "$crate_dir/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
tag="{{pkg}}-v$ver"
url="https://somegit.dev/Owlibou/owlry-plugins/archive/$tag.tar.gz"
echo "Updating {{pkg}} to $ver (tag: $tag)"
sed -i "s/^pkgver=.*/pkgver=$ver/" "$aur_dir/PKGBUILD"
sed -i 's/^pkgrel=.*/pkgrel=1/' "$aur_dir/PKGBUILD"
echo "Downloading tarball and computing checksum..."
hash=$(curl -sL "$url" | b2sum | cut -d' ' -f1)
if [ -z "$hash" ] || [ ${#hash} -lt 64 ]; then
echo "Error: failed to download or hash $url"
exit 1
fi
sed -i "s|^b2sums=.*|b2sums=('$hash')|" "$aur_dir/PKGBUILD"
(cd "$aur_dir" && makepkg --printsrcinfo > .SRCINFO)
echo "{{pkg}} PKGBUILD updated to $ver"
# Publish a specific plugin to aur.archlinux.org
aur-publish-pkg pkg:
#!/usr/bin/env bash
set -euo pipefail
aur_dir="aur/{{pkg}}"
[ -d "$aur_dir/.git" ] || { echo "Error: $aur_dir has no AUR git repo"; exit 1; }
cd "$aur_dir"
ver=$(grep '^pkgver=' PKGBUILD | sed 's/pkgver=//')
git add -A
git commit -m "Update to v$ver" || { echo "Nothing to commit"; exit 0; }
git push origin master
echo "{{pkg}} v$ver published to AUR!"
# Update ALL plugin AUR packages
aur-update-all:
#!/usr/bin/env bash
set -euo pipefail
for dir in aur/owlry-plugin-*/; do
pkg=$(basename "$dir")
[ -f "$dir/PKGBUILD" ] || continue
echo "=== $pkg ==="
just aur-update-pkg "$pkg"
echo ""
done
echo "All updated. Run 'just aur-publish-all' to publish."
# Publish ALL plugin AUR packages
aur-publish-all:
#!/usr/bin/env bash
set -euo pipefail
for dir in aur/owlry-plugin-*/; do
pkg=$(basename "$dir")
[ -d "$dir/.git" ] || continue
echo "=== $pkg ==="
just aur-publish-pkg "$pkg"
echo ""
done
echo "All published!"
# Commit AUR file changes to the plugins repo
aur-commit msg="chore(aur): update PKGBUILDs":
#!/usr/bin/env bash
set -euo pipefail
for dir in aur/*/; do
pkg=$(basename "$dir")
[ -f "$dir/PKGBUILD" ] || continue
just aur-stage "$pkg"
done
git diff --cached --quiet && { echo "No AUR changes to commit"; exit 0; }
git commit -m "{{msg}}"
# Show AUR package status
aur-status:
#!/usr/bin/env bash
echo "=== AUR Package Status ==="
for dir in aur/*/; do
pkg=$(basename "$dir")
[ -f "$dir/PKGBUILD" ] || continue
ver=$(grep '^pkgver=' "$dir/PKGBUILD" | sed 's/pkgver=//')
if [ -d "$dir/.git" ]; then
printf " ✓ %-35s %s\n" "$pkg" "$ver"
else
printf " ✗ %-35s %s (no AUR repo)\n" "$pkg" "$ver"
fi
done
# === Release Workflows ===
# Release a single plugin: bump → push → tag → update AUR → publish AUR
release-plugin crate new_version:
#!/usr/bin/env bash
set -euo pipefail
just bump-crate {{crate}} {{new_version}}
git push
just tag-crate {{crate}}
just push-tags
echo "Waiting for tag to propagate..."
sleep 3
just aur-update-pkg {{crate}}
just aur-commit "chore(aur): update {{crate}} to {{new_version}}"
git push
just aur-publish-pkg {{crate}}
echo ""
echo "{{crate}} v{{new_version}} released and published to AUR!"
# === Testing ===
# Quick local build test (no chroot, uses host deps)
aur-test-pkg pkg:
#!/usr/bin/env bash
set -euo pipefail
cd "aur/{{pkg}}"
echo "Testing {{pkg}} PKGBUILD..."
makepkg -sf
echo "Package built successfully!"
ls -lh *.pkg.tar.zst
# Build AUR packages from the local working tree in a clean chroot.
# Packages current source (incl. uncommitted changes), patches PKGBUILD,
# builds in dep order, injects local artifacts, restores PKGBUILD on exit.
# owlry-core is not in the official repos — inject it with -I:
#
# Examples:
# just aur-local-test -I ../owlry/aur/owlry-core/owlry-core-*.pkg.tar.zst owlry-plugin-weather
# just aur-local-test -I ../owlry/aur/owlry-core/owlry-core-*.pkg.tar.zst --all
aur-local-test *args:
scripts/aur-local-test {{args}}

329
scripts/aur-local-test Executable file
View File

@@ -0,0 +1,329 @@
#!/usr/bin/env bash
# scripts/aur-local-test
#
# Build AUR packages from the local working tree in a clean extra chroot.
#
# Packages the current working tree (including uncommitted changes) into a
# tarball, temporarily patches each PKGBUILD to use it, runs
# extra-x86_64-build, then restores the PKGBUILD on exit regardless of
# success or failure.
#
# Packages with local AUR deps (e.g. owlry-rune depends on owlry-core) are
# built in topological order and their artifacts injected automatically.
#
# Usage: scripts/aur-local-test [OPTIONS] [PKG...]
# See --help for details.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
REPO_NAME="$(basename "$REPO_ROOT")"
AUR_DIR="$REPO_ROOT/aur"
# State tracked for cleanup
TMP_TARBALL=""
declare -a PKGBUILD_BACKUPS=()
declare -a PLACED_FILES=()
# Build config
RESET_CHROOT=0
declare -a INPUT_PKGS=()
declare -a EXTRA_INJECT=() # --inject paths (external AUR deps)
# ─── Output helpers ──────────────────────────────────────────────────────────
die() { echo "error: $*" >&2; exit 1; }
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m ->\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m !\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31mFAIL\033[0m %s\n' "$*" >&2; }
# ─── Cleanup ─────────────────────────────────────────────────────────────────
cleanup() {
local code=$?
local f pkgbuild
# Remove tarballs placed in aur/ dirs
for f in "${PLACED_FILES[@]+"${PLACED_FILES[@]}"}"; do
[[ -f "$f" ]] && rm -f "$f"
done
# Restore patched PKGBUILDs from backups
for f in "${PKGBUILD_BACKUPS[@]+"${PKGBUILD_BACKUPS[@]}"}"; do
pkgbuild="${f%.bak}"
[[ -f "$f" ]] && mv "$f" "$pkgbuild"
done
[[ -n "$TMP_TARBALL" && -f "$TMP_TARBALL" ]] && rm -f "$TMP_TARBALL"
exit "$code"
}
trap cleanup EXIT INT TERM
# ─── Usage ───────────────────────────────────────────────────────────────────
usage() {
cat >&2 <<EOF
Usage: $(basename "$0") [OPTIONS] [PKG...]
Build AUR packages from the local working tree in a clean chroot.
Packages current working tree (incl. uncommitted changes), patches PKGBUILD
source + checksum, runs extra-x86_64-build, then restores on exit.
Packages with local AUR deps are built in topological order and their
.pkg.tar.zst artifacts are injected into dependent builds automatically.
OPTIONS
-c, --reset Reset chroot matrix (passes -c to extra-x86_64-build).
Only applied to the first package; subsequent builds
reuse the already-fresh chroot.
-a, --all Build all packages in aur/ (respects dep order).
-I, --inject FILE Inject FILE (.pkg.tar.zst) into the chroot before
building. For AUR deps not in the official repos
(e.g. owlry-core when testing owlry-plugins).
Can be repeated.
-h, --help Show this help.
EXAMPLES
# Single package
$(basename "$0") owlry-core
# Multiple packages with chroot reset
$(basename "$0") -c owlry-core owlry-rune
# All packages in dependency order
$(basename "$0") --all --reset
# owlry-plugins: inject owlry-core from sibling repo
$(basename "$0") -I ../owlry/aur/owlry-core/owlry-core-*.pkg.tar.zst --all
EOF
exit 1
}
# ─── Argument parsing ────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--reset)
RESET_CHROOT=1
shift ;;
-a|--all)
for dir in "$AUR_DIR"/*/; do
pkg=$(basename "$dir")
[[ -f "$dir/PKGBUILD" ]] && INPUT_PKGS+=("$pkg")
done
shift ;;
-I|--inject)
[[ $# -ge 2 ]] || die "--inject requires an argument"
[[ -f "$2" ]] || die "inject file not found: $2"
EXTRA_INJECT+=("$(realpath "$2")")
shift 2 ;;
-h|--help) usage ;;
-*) die "unknown option: $1" ;;
*)
if [[ "$1" == *.pkg.tar.zst ]]; then
[[ -f "$1" ]] || die "inject file not found: $1"
EXTRA_INJECT+=("$(realpath "$1")")
else
INPUT_PKGS+=("$1")
fi
shift ;;
esac
done
[[ ${#INPUT_PKGS[@]} -eq 0 ]] && usage
# ─── Dependency resolution ───────────────────────────────────────────────────
# Return the names of local AUR packages that PKG depends on.
local_deps_of() {
local pkg="$1"
local pkgbuild="$AUR_DIR/$pkg/PKGBUILD"
[[ -f "$pkgbuild" ]] || return 0
local dep_line bare
dep_line=$(grep '^depends=' "$pkgbuild" 2>/dev/null | head -1 || true)
[[ -z "$dep_line" ]] && return 0
# Strip depends=, parens, and quotes; split on whitespace
echo "$dep_line" \
| sed "s/^depends=//; s/[()\"']/ /g" \
| tr ' ' '\n' \
| while IFS= read -r dep; do
[[ -z "$dep" ]] && continue
bare="${dep%%[><=]*}" # strip version constraints
[[ -d "$AUR_DIR/$bare" ]] && echo "$bare"
done
}
# Topological sort (DFS) — deps before dependents.
declare -A TOPO_VISITED=()
declare -a TOPO_ORDER=()
topo_visit() {
local pkg="$1"
[[ -v "TOPO_VISITED[$pkg]" ]] && return 0
TOPO_VISITED[$pkg]=1
local dep
while IFS= read -r dep; do
topo_visit "$dep"
done < <(local_deps_of "$pkg")
TOPO_ORDER+=("$pkg")
}
resolve_order() {
TOPO_VISITED=()
TOPO_ORDER=()
local pkg
for pkg in "$@"; do
topo_visit "$pkg"
done
}
# ─── Tarball creation ────────────────────────────────────────────────────────
make_tarball() {
TMP_TARBALL=$(mktemp /tmp/aur-local-XXXXXX.tar.gz)
info "Packaging ${REPO_NAME} working tree (incl. uncommitted changes)..."
tar czf "$TMP_TARBALL" \
--exclude='.git' \
--exclude='target' \
--transform "s|^\.|${REPO_NAME}|" \
-C "$REPO_ROOT" .
ok "Tarball ready: $(du -b "$TMP_TARBALL" | cut -f1 | numfmt --to=iec 2>/dev/null || wc -c < "$TMP_TARBALL") bytes"
}
# ─── PKGBUILD patching ───────────────────────────────────────────────────────
# Patch a package's PKGBUILD to use the local tarball.
# Backs up the original; cleanup() restores it on exit.
patch_pkgbuild() {
local pkg="$1"
local pkgbuild="$AUR_DIR/$pkg/PKGBUILD"
local pkgdir="$AUR_DIR/$pkg"
# Skip packages with no remote source (meta/group packages)
if ! grep -q '^source=' "$pkgbuild" || grep -qE '^source=\(\s*\)' "$pkgbuild"; then
ok "No source URL to patch — skipping tarball injection for $pkg"
return 0
fi
local pkgname pkgver filename hash
pkgname=$(grep '^pkgname=' "$pkgbuild" | cut -d= -f2- | tr -d "\"'")
pkgver=$(grep '^pkgver=' "$pkgbuild" | cut -d= -f2- | tr -d "\"'")
filename="${pkgname}-${pkgver}.tar.gz"
hash=$(b2sum "$TMP_TARBALL" | cut -d' ' -f1)
# Backup original PKGBUILD
cp "$pkgbuild" "${pkgbuild}.bak"
PKGBUILD_BACKUPS+=("${pkgbuild}.bak")
# Place local tarball where makepkg looks for it
cp "$TMP_TARBALL" "$pkgdir/$filename"
PLACED_FILES+=("$pkgdir/$filename")
# Patch source and checksum lines in-place
sed -i "s|^source=.*|source=(\"${filename}\")|" "$pkgbuild"
sed -i "s|^b2sums=.*|b2sums=('${hash}')|" "$pkgbuild"
ok "Patched PKGBUILD: source=${filename}, b2sum=${hash:0:12}…"
}
# ─── Build ───────────────────────────────────────────────────────────────────
# built_artifacts[pkg] = path to the .pkg.tar.zst produced in this run.
# Used to inject pkg artifacts into dependent builds.
declare -A BUILT_ARTIFACTS=()
find_artifact() {
local pkg="$1"
local pkgver
# pkgver is the same in patched and original PKGBUILD
pkgver=$(grep '^pkgver=' "$AUR_DIR/$pkg/PKGBUILD" | cut -d= -f2- | tr -d "\"'" \
|| grep '^pkgver=' "$AUR_DIR/$pkg/PKGBUILD.bak" | cut -d= -f2- | tr -d "\"'")
ls "$AUR_DIR/$pkg/${pkg}-${pkgver}-"*".pkg.tar.zst" 2>/dev/null \
| grep -v -- '-debug-' | sort -V | tail -1 || true
}
build_one() {
local pkg="$1"
local pkgdir="$AUR_DIR/$pkg"
info "[$pkg] Patching PKGBUILD..."
patch_pkgbuild "$pkg"
# Collect inject args: extra (external) + artifacts of local deps built earlier
local inject=()
for f in "${EXTRA_INJECT[@]+"${EXTRA_INJECT[@]}"}"; do
inject+=("-I" "$f")
done
while IFS= read -r dep; do
if [[ -v "BUILT_ARTIFACTS[$dep]" ]]; then
inject+=("-I" "${BUILT_ARTIFACTS[$dep]}")
else
warn "$pkg depends on $dep (local AUR) which was not built in this run"
warn " → Build $dep first or pass: -I path/to/${dep}-*.pkg.tar.zst"
fi
done < <(local_deps_of "$pkg")
# Build args: -c only on the first package, then cleared
local build_args=()
if [[ $RESET_CHROOT -eq 1 ]]; then
build_args+=("-c")
RESET_CHROOT=0
fi
info "[$pkg] Running extra-x86_64-build..."
(
cd "$pkgdir"
if [[ ${#inject[@]} -gt 0 ]]; then
extra-x86_64-build "${build_args[@]+"${build_args[@]}"}" -- "${inject[@]}"
else
extra-x86_64-build "${build_args[@]+"${build_args[@]}"}"
fi
)
# Record artifact for potential injection into dependents
local artifact
artifact=$(find_artifact "$pkg")
if [[ -n "$artifact" ]]; then
BUILT_ARTIFACTS[$pkg]="$artifact"
ok "[$pkg] artifact: $(basename "$artifact")"
fi
}
# ─── Main ────────────────────────────────────────────────────────────────────
# Validate all requested packages exist
for pkg in "${INPUT_PKGS[@]}"; do
[[ -d "$AUR_DIR/$pkg" && -f "$AUR_DIR/$pkg/PKGBUILD" ]] \
|| die "package not found: aur/$pkg/PKGBUILD"
done
# Sort into build order (deps before dependents)
resolve_order "${INPUT_PKGS[@]}"
# Create one tarball, reused for all packages in this run
make_tarball
declare -a FAILED=()
for pkg in "${TOPO_ORDER[@]}"; do
echo ""
if build_one "$pkg"; then
:
else
fail "[$pkg]"
FAILED+=("$pkg")
fi
done
echo ""
if [[ ${#FAILED[@]} -gt 0 ]]; then
fail "packages failed: ${FAILED[*]}"
exit 1
fi
info "All packages built successfully!"