Compare commits
22 Commits
owlry-plug
...
owlry-plug
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d69105930 | |||
| 2bd72c8af1 | |||
| b4de6a3791 | |||
| c73f57578d | |||
| b46477ae88 | |||
| 9c3dec9d14 | |||
| 41a00b188a | |||
| fe68af7b46 | |||
| 7e51425166 | |||
| 5c93b8a280 | |||
| 3852245f74 | |||
| 9bcbacd9d7 | |||
| 37edb8e1df | |||
| 8d5a5e16b6 | |||
| f3b9728f07 | |||
| b2156dc0b2 | |||
| 5550a10048 | |||
| 9e6cedf159 | |||
| c44502d0ab | |||
| 67a4791828 | |||
| 268fd49741 | |||
| d8d26f4fd2 |
13
CLAUDE.md
13
CLAUDE.md
@@ -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
|
||||
|
||||
25
Cargo.lock
generated
25
Cargo.lock
generated
@@ -879,7 +879,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-bookmarks"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -891,7 +891,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-calculator"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"meval",
|
||||
@@ -906,9 +906,21 @@ dependencies = [
|
||||
"owlry-plugin-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-converter"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
"owlry-plugin-api",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-emoji"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -954,11 +966,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-ssh"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
"owlry-plugin-api",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -992,10 +1005,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-websearch"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
"owlry-plugin-api",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"crates/owlry-plugin-bookmarks",
|
||||
"crates/owlry-plugin-calculator",
|
||||
"crates/owlry-plugin-converter",
|
||||
"crates/owlry-plugin-clipboard",
|
||||
"crates/owlry-plugin-emoji",
|
||||
"crates/owlry-plugin-filesearch",
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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.1
|
||||
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.1.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-bookmarks-v1.0.1.tar.gz
|
||||
b2sums = 1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9
|
||||
|
||||
pkgname = owlry-plugin-bookmarks
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-bookmarks
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.1
|
||||
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=('1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
pkgbase = owlry-plugin-calculator
|
||||
pkgdesc = Calculator plugin for Owlry - evaluate math expressions
|
||||
pkgver = 0.4.10
|
||||
pkgdesc = Calculator plugin for Owlry — evaluate math expressions inline
|
||||
pkgver = 1.0.1
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-calculator.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-calculator-1.0.1.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-calculator-v1.0.1.tar.gz
|
||||
b2sums = 1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9
|
||||
|
||||
pkgname = owlry-plugin-calculator
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-calculator
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.1
|
||||
pkgrel=1
|
||||
pkgdesc="Calculator plugin for Owlry - evaluate math expressions"
|
||||
pkgdesc="Calculator plugin for Owlry — evaluate math expressions inline"
|
||||
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-calculator.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=('1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9')
|
||||
|
||||
_cratename=owlry-plugin-calculator
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -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.0
|
||||
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.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-clipboard-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-clipboard
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-clipboard
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
14
aur/owlry-plugin-converter/.SRCINFO
Normal file
14
aur/owlry-plugin-converter/.SRCINFO
Normal file
@@ -0,0 +1,14 @@
|
||||
pkgbase = owlry-plugin-converter
|
||||
pkgdesc = Unit and currency conversion plugin for Owlry — convert temperature, weight, length, currency, and more
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-converter.install
|
||||
arch = x86_64
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
source = owlry-plugin-converter-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-converter-v1.0.2.tar.gz
|
||||
b2sums = a89bff286559b1e9984545bf8655c0d7230f0cd139134498041fad71ac90e144c52b550b5d9540a8458c96f1fb05b23aa28e557d1bb0d6a08ee46563ae6da188
|
||||
|
||||
pkgname = owlry-plugin-converter
|
||||
41
aur/owlry-plugin-converter/PKGBUILD
Normal file
41
aur/owlry-plugin-converter/PKGBUILD
Normal file
@@ -0,0 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-converter
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Unit and currency conversion plugin for Owlry — convert temperature, weight, length, currency, and more"
|
||||
arch=('x86_64')
|
||||
url="https://somegit.dev/Owlibou/owlry-plugins"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('owlry-core')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-converter.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('a89bff286559b1e9984545bf8655c0d7230f0cd139134498041fad71ac90e144c52b550b5d9540a8458c96f1fb05b23aa28e557d1bb0d6a08ee46563ae6da188')
|
||||
|
||||
_cratename=owlry-plugin-converter
|
||||
|
||||
prepare() {
|
||||
cd "owlry-plugins"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "owlry-plugins"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
export CARGO_TARGET_DIR=target
|
||||
cargo build -p $_cratename --frozen --release
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "owlry-plugins"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
export CARGO_TARGET_DIR=target
|
||||
cargo test -p $_cratename --frozen
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "owlry-plugins"
|
||||
install -Dm755 "target/release/lib${_cratename//-/_}.so" \
|
||||
"$pkgdir/usr/lib/owlry/plugins/lib${_cratename//-/_}.so"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.1
|
||||
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.1.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-emoji-v1.0.1.tar.gz
|
||||
b2sums = 1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9
|
||||
|
||||
pkgname = owlry-plugin-emoji
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-emoji
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.1
|
||||
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=('1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -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.0
|
||||
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.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-filesearch-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-filesearch
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-filesearch
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -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.0
|
||||
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.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-media-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-media
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-media
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -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.0
|
||||
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.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-pomodoro-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-pomodoro
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-pomodoro
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -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.0
|
||||
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.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-scripts-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-scripts
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-scripts
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -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.1
|
||||
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.1.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-ssh-v1.0.1.tar.gz
|
||||
b2sums = 1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9
|
||||
|
||||
pkgname = owlry-plugin-ssh
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-ssh
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.1
|
||||
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=('1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
pkgbase = owlry-plugin-system
|
||||
pkgdesc = System actions plugin for Owlry (shutdown, reboot, logout, etc.)
|
||||
pkgver = 0.4.10
|
||||
pkgdesc = System actions plugin for Owlry — shutdown, reboot, logout, lock, and suspend
|
||||
pkgver = 1.0.0
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-system.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-system-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-system-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-system
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-system
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="System actions plugin for Owlry (shutdown, reboot, logout, etc.)"
|
||||
pkgdesc="System actions plugin for Owlry — shutdown, reboot, logout, lock, and suspend"
|
||||
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-system.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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_cratename=owlry-plugin-system
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -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.0
|
||||
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.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-systemd-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-systemd
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-systemd
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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.0
|
||||
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
|
||||
source = owlry-plugin-weather-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-weather-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
|
||||
pkgname = owlry-plugin-weather
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-weather
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.0
|
||||
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')
|
||||
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=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -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.1
|
||||
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.1.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-websearch-v1.0.1.tar.gz
|
||||
b2sums = 1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9
|
||||
|
||||
pkgname = owlry-plugin-websearch
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-websearch
|
||||
pkgver=0.4.10
|
||||
pkgver=1.0.1
|
||||
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=('1ae495d6dc9dce479f9676b4bfddc410bfc9be0f3f6b99f0626f007e15de55a52c4630a3facdb9671d0aaef61d30ab1fc27401476c6934371d68da6000e7e1a9')
|
||||
|
||||
_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"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-bookmarks"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-calculator"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -136,7 +136,7 @@ fn evaluate_expression(expr: &str) -> Option<PluginItem> {
|
||||
PluginItem::new(
|
||||
format!("calc:{}", expr),
|
||||
result_str.clone(),
|
||||
format!("sh -c 'echo -n \"{}\" | wl-copy'", result_str),
|
||||
format!("printf '%s' '{}' | wl-copy", result_str.replace('\'', "'\\''")),
|
||||
)
|
||||
.with_description(format!("= {}", expr))
|
||||
.with_icon(PROVIDER_ICON)
|
||||
|
||||
21
crates/owlry-plugin-converter/Cargo.toml
Normal file
21
crates/owlry-plugin-converter/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "owlry-plugin-converter"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Unit and currency conversion plugin for owlry"
|
||||
keywords = ["owlry", "plugin", "converter", "units", "currency"]
|
||||
categories = ["science"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[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"
|
||||
313
crates/owlry-plugin-converter/src/currency.rs
Normal file
313
crates/owlry-plugin-converter/src/currency.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const ECB_URL: &str = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
|
||||
const CACHE_MAX_AGE_SECS: u64 = 86400; // 24 hours
|
||||
|
||||
static CACHED_RATES: Mutex<Option<CurrencyRates>> = Mutex::new(None);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CurrencyRates {
|
||||
pub date: String,
|
||||
pub rates: HashMap<String, f64>,
|
||||
}
|
||||
|
||||
struct CurrencyAlias {
|
||||
code: &'static str,
|
||||
aliases: &'static [&'static str],
|
||||
}
|
||||
|
||||
static CURRENCY_ALIASES: &[CurrencyAlias] = &[
|
||||
CurrencyAlias {
|
||||
code: "EUR",
|
||||
aliases: &["eur", "euro", "euros", "€"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "USD",
|
||||
aliases: &["usd", "dollar", "dollars", "$", "us_dollar"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "GBP",
|
||||
aliases: &["gbp", "pound_sterling", "£", "british_pound", "pounds"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "JPY",
|
||||
aliases: &["jpy", "yen", "¥", "japanese_yen"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "CHF",
|
||||
aliases: &["chf", "swiss_franc", "francs"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "CAD",
|
||||
aliases: &["cad", "canadian_dollar", "c$"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "AUD",
|
||||
aliases: &["aud", "australian_dollar", "a$"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "CNY",
|
||||
aliases: &["cny", "yuan", "renminbi", "rmb"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "SEK",
|
||||
aliases: &["sek", "swedish_krona", "kronor"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "NOK",
|
||||
aliases: &["nok", "norwegian_krone"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "DKK",
|
||||
aliases: &["dkk", "danish_krone"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "PLN",
|
||||
aliases: &["pln", "zloty", "złoty"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "CZK",
|
||||
aliases: &["czk", "czech_koruna"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "HUF",
|
||||
aliases: &["huf", "forint"],
|
||||
},
|
||||
CurrencyAlias {
|
||||
code: "TRY",
|
||||
aliases: &["try", "turkish_lira", "lira"],
|
||||
},
|
||||
];
|
||||
|
||||
pub fn resolve_currency_code(alias: &str) -> Option<&'static str> {
|
||||
let lower = alias.to_lowercase();
|
||||
|
||||
// Check aliases
|
||||
for ca in CURRENCY_ALIASES {
|
||||
if ca.aliases.contains(&lower.as_str()) {
|
||||
return Some(ca.code); // ca.code is already &'static str
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a raw 3-letter ISO code we know about
|
||||
let upper = alias.to_uppercase();
|
||||
if upper.len() == 3 {
|
||||
if upper == "EUR" {
|
||||
return Some("EUR");
|
||||
}
|
||||
if let Some(rates) = get_rates()
|
||||
&& rates.rates.contains_key(&upper)
|
||||
{
|
||||
for ca in CURRENCY_ALIASES {
|
||||
if ca.code == upper {
|
||||
return Some(ca.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_currency_alias(alias: &str) -> bool {
|
||||
resolve_currency_code(alias).is_some()
|
||||
}
|
||||
|
||||
pub fn get_rates() -> Option<CurrencyRates> {
|
||||
// Check memory cache first
|
||||
{
|
||||
let cache = CACHED_RATES.lock().ok()?;
|
||||
if let Some(ref rates) = *cache {
|
||||
return Some(rates.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Try disk cache
|
||||
if let Some(rates) = load_cache()
|
||||
&& !is_stale(&rates)
|
||||
{
|
||||
let mut cache = CACHED_RATES.lock().ok()?;
|
||||
*cache = Some(rates.clone());
|
||||
return Some(rates);
|
||||
}
|
||||
|
||||
// Fetch fresh rates
|
||||
if let Some(rates) = fetch_rates() {
|
||||
save_cache(&rates);
|
||||
let mut cache = CACHED_RATES.lock().ok()?;
|
||||
*cache = Some(rates.clone());
|
||||
return Some(rates);
|
||||
}
|
||||
|
||||
// Fall back to stale cache
|
||||
load_cache()
|
||||
}
|
||||
|
||||
fn cache_path() -> Option<PathBuf> {
|
||||
let cache_dir = dirs::cache_dir()?.join("owlry");
|
||||
Some(cache_dir.join("ecb_rates.json"))
|
||||
}
|
||||
|
||||
fn load_cache() -> Option<CurrencyRates> {
|
||||
let path = cache_path()?;
|
||||
let content = fs::read_to_string(path).ok()?;
|
||||
serde_json::from_str(&content).ok()
|
||||
}
|
||||
|
||||
fn save_cache(rates: &CurrencyRates) {
|
||||
if let Some(path) = cache_path() {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).ok();
|
||||
}
|
||||
if let Ok(json) = serde_json::to_string_pretty(rates) {
|
||||
fs::write(path, json).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_stale(_rates: &CurrencyRates) -> bool {
|
||||
let path = match cache_path() {
|
||||
Some(p) => p,
|
||||
None => return true,
|
||||
};
|
||||
let metadata = match fs::metadata(path) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return true,
|
||||
};
|
||||
let modified = match metadata.modified() {
|
||||
Ok(t) => t,
|
||||
Err(_) => return true,
|
||||
};
|
||||
match SystemTime::now().duration_since(modified) {
|
||||
Ok(age) => age.as_secs() > CACHE_MAX_AGE_SECS,
|
||||
Err(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_rates() -> Option<CurrencyRates> {
|
||||
let response = reqwest::blocking::get(ECB_URL).ok()?;
|
||||
let body = response.text().ok()?;
|
||||
parse_ecb_xml(&body)
|
||||
}
|
||||
|
||||
fn parse_ecb_xml(xml: &str) -> Option<CurrencyRates> {
|
||||
let mut rates = HashMap::new();
|
||||
let mut date = String::new();
|
||||
|
||||
for line in xml.lines() {
|
||||
let trimmed = line.trim();
|
||||
|
||||
// Extract date: <Cube time='2026-03-26'>
|
||||
if trimmed.contains("time=")
|
||||
&& let Some(start) = trimmed.find("time='")
|
||||
{
|
||||
let rest = &trimmed[start + 6..];
|
||||
if let Some(end) = rest.find('\'') {
|
||||
date = rest[..end].to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// Extract rate: <Cube currency='USD' rate='1.0832'/>
|
||||
if trimmed.contains("currency=") && trimmed.contains("rate=") {
|
||||
let currency = extract_attr(trimmed, "currency")?;
|
||||
let rate_str = extract_attr(trimmed, "rate")?;
|
||||
let rate: f64 = rate_str.parse().ok()?;
|
||||
rates.insert(currency, rate);
|
||||
}
|
||||
}
|
||||
|
||||
if rates.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(CurrencyRates { date, rates })
|
||||
}
|
||||
|
||||
fn extract_attr(line: &str, attr: &str) -> Option<String> {
|
||||
let needle = format!("{}='", attr);
|
||||
let start = line.find(&needle)? + needle.len();
|
||||
let rest = &line[start..];
|
||||
let end = rest.find('\'')?;
|
||||
Some(rest[..end].to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_resolve_currency_code_iso() {
|
||||
assert_eq!(resolve_currency_code("usd"), Some("USD"));
|
||||
assert_eq!(resolve_currency_code("EUR"), Some("EUR"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_currency_code_name() {
|
||||
assert_eq!(resolve_currency_code("dollar"), Some("USD"));
|
||||
assert_eq!(resolve_currency_code("euro"), Some("EUR"));
|
||||
assert_eq!(resolve_currency_code("pounds"), Some("GBP"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_currency_code_symbol() {
|
||||
assert_eq!(resolve_currency_code("$"), Some("USD"));
|
||||
assert_eq!(resolve_currency_code("€"), Some("EUR"));
|
||||
assert_eq!(resolve_currency_code("£"), Some("GBP"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_currency_unknown() {
|
||||
assert_eq!(resolve_currency_code("xyz"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_currency_alias() {
|
||||
assert!(is_currency_alias("usd"));
|
||||
assert!(is_currency_alias("euro"));
|
||||
assert!(is_currency_alias("$"));
|
||||
assert!(!is_currency_alias("km"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ecb_xml() {
|
||||
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
|
||||
<gesmes:subject>Reference rates</gesmes:subject>
|
||||
<Cube>
|
||||
<Cube time='2026-03-26'>
|
||||
<Cube currency='USD' rate='1.0832'/>
|
||||
<Cube currency='JPY' rate='161.94'/>
|
||||
<Cube currency='GBP' rate='0.83450'/>
|
||||
</Cube>
|
||||
</Cube>
|
||||
</gesmes:Envelope>"#;
|
||||
|
||||
let rates = parse_ecb_xml(xml).unwrap();
|
||||
assert!((rates.rates["USD"] - 1.0832).abs() < 0.001);
|
||||
assert!((rates.rates["GBP"] - 0.8345).abs() < 0.001);
|
||||
assert!((rates.rates["JPY"] - 161.94).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_roundtrip() {
|
||||
let rates = CurrencyRates {
|
||||
date: "2026-03-26".to_string(),
|
||||
rates: {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("USD".to_string(), 1.0832);
|
||||
m.insert("GBP".to_string(), 0.8345);
|
||||
m
|
||||
},
|
||||
};
|
||||
let json = serde_json::to_string(&rates).unwrap();
|
||||
let parsed: CurrencyRates = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.rates["USD"], 1.0832);
|
||||
}
|
||||
}
|
||||
237
crates/owlry-plugin-converter/src/lib.rs
Normal file
237
crates/owlry-plugin-converter/src/lib.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
//! Converter Plugin for Owlry
|
||||
//!
|
||||
//! A dynamic provider that converts between units and currencies.
|
||||
//! Supports queries prefixed with `>` or auto-detected.
|
||||
//!
|
||||
//! Examples:
|
||||
//! - `> 100 F to C` → 37.78 °C
|
||||
//! - `50 kg in lb` → 110.23 lb
|
||||
//! - `100 eur to usd` → 108.32 USD
|
||||
|
||||
mod currency;
|
||||
mod parser;
|
||||
mod units;
|
||||
|
||||
use abi_stable::std_types::{ROption, RStr, RString, RVec};
|
||||
use owlry_plugin_api::{
|
||||
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
|
||||
ProviderPosition, owlry_plugin,
|
||||
};
|
||||
|
||||
const PLUGIN_ID: &str = "converter";
|
||||
const PLUGIN_NAME: &str = "Converter";
|
||||
const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const PLUGIN_DESCRIPTION: &str = "Convert between units and currencies";
|
||||
|
||||
const PROVIDER_ID: &str = "converter";
|
||||
const PROVIDER_NAME: &str = "Converter";
|
||||
const PROVIDER_PREFIX: &str = ">";
|
||||
const PROVIDER_ICON: &str = "edit-find-replace-symbolic";
|
||||
const PROVIDER_TYPE_ID: &str = "conv";
|
||||
|
||||
struct ConverterState;
|
||||
|
||||
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: 9000,
|
||||
}]
|
||||
.into()
|
||||
}
|
||||
|
||||
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
|
||||
let state = Box::new(ConverterState);
|
||||
ProviderHandle::from_box(state)
|
||||
}
|
||||
|
||||
extern "C" fn provider_refresh(_handle: ProviderHandle) -> RVec<PluginItem> {
|
||||
RVec::new()
|
||||
}
|
||||
|
||||
extern "C" fn provider_query(_handle: ProviderHandle, query: RStr<'_>) -> RVec<PluginItem> {
|
||||
let query_str = query.as_str().trim();
|
||||
// Strip prefix
|
||||
let input = if let Some(rest) = query_str.strip_prefix('>') {
|
||||
rest.trim()
|
||||
} else {
|
||||
query_str
|
||||
};
|
||||
|
||||
let parsed = match parser::parse_conversion(input) {
|
||||
Some(p) => p,
|
||||
None => return RVec::new(),
|
||||
};
|
||||
|
||||
let results = if let Some(ref target) = parsed.target_unit {
|
||||
units::convert_to(&parsed.value, &parsed.from_unit, target)
|
||||
.into_iter()
|
||||
.collect()
|
||||
} else {
|
||||
units::convert_common(&parsed.value, &parsed.from_unit)
|
||||
};
|
||||
|
||||
results
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
PluginItem::new(
|
||||
format!("conv:{}:{}:{}", parsed.from_unit, r.target_symbol, r.value),
|
||||
r.display_value.clone(),
|
||||
format!("printf '%s' '{}' | wl-copy", r.raw_value.replace('\'', "'\\''")),
|
||||
)
|
||||
.with_description(format!(
|
||||
"{} {} = {}",
|
||||
format_number(parsed.value),
|
||||
parsed.from_symbol,
|
||||
r.display_value,
|
||||
))
|
||||
.with_icon(PROVIDER_ICON)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
}
|
||||
|
||||
extern "C" fn provider_drop(handle: ProviderHandle) {
|
||||
if !handle.ptr.is_null() {
|
||||
unsafe {
|
||||
handle.drop_as::<ConverterState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
owlry_plugin! {
|
||||
info: plugin_info,
|
||||
providers: plugin_providers,
|
||||
init: provider_init,
|
||||
refresh: provider_refresh,
|
||||
query: provider_query,
|
||||
drop: provider_drop,
|
||||
}
|
||||
|
||||
fn format_number(n: f64) -> String {
|
||||
if n.fract() == 0.0 && n.abs() < 1e15 {
|
||||
let i = n as i64;
|
||||
if i.abs() >= 1000 {
|
||||
format_with_separators(i)
|
||||
} else {
|
||||
format!("{}", i)
|
||||
}
|
||||
} else {
|
||||
format!("{:.4}", n)
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.')
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_separators(n: i64) -> String {
|
||||
let s = n.abs().to_string();
|
||||
let mut result = String::new();
|
||||
for (i, c) in s.chars().rev().enumerate() {
|
||||
if i > 0 && i % 3 == 0 {
|
||||
result.push(',');
|
||||
}
|
||||
result.push(c);
|
||||
}
|
||||
if n < 0 {
|
||||
result.push('-');
|
||||
}
|
||||
result.chars().rev().collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_format_number_integer() {
|
||||
assert_eq!(format_number(42.0), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_number_large_integer() {
|
||||
assert_eq!(format_number(1000000.0), "1,000,000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_number_decimal() {
|
||||
assert_eq!(format_number(3.14), "3.14");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_with_separators() {
|
||||
assert_eq!(format_with_separators(1234567), "1,234,567");
|
||||
assert_eq!(format_with_separators(999), "999");
|
||||
assert_eq!(format_with_separators(-1234), "-1,234");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_provider_query_with_prefix() {
|
||||
let result = provider_query(
|
||||
ProviderHandle {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
RStr::from("> 100 km to mi"),
|
||||
);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_provider_query_auto_detect() {
|
||||
let result = provider_query(
|
||||
ProviderHandle {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
RStr::from("100 km to mi"),
|
||||
);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_provider_query_no_target() {
|
||||
let result = provider_query(
|
||||
ProviderHandle {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
RStr::from("> 100 km"),
|
||||
);
|
||||
assert!(result.len() > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_provider_query_nonsense() {
|
||||
let result = provider_query(
|
||||
ProviderHandle {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
RStr::from("hello world"),
|
||||
);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_provider_query_temperature() {
|
||||
let result = provider_query(
|
||||
ProviderHandle {
|
||||
ptr: std::ptr::null_mut(),
|
||||
},
|
||||
RStr::from("102F to C"),
|
||||
);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
}
|
||||
235
crates/owlry-plugin-converter/src/parser.rs
Normal file
235
crates/owlry-plugin-converter/src/parser.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use crate::units;
|
||||
|
||||
pub struct ParsedQuery {
|
||||
pub value: f64,
|
||||
pub from_unit: String,
|
||||
pub from_symbol: String,
|
||||
pub target_unit: Option<String>,
|
||||
}
|
||||
|
||||
pub fn parse_conversion(input: &str) -> Option<ParsedQuery> {
|
||||
let input = input.trim();
|
||||
if input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Extract leading number
|
||||
let (value, rest) = extract_number(input)?;
|
||||
let rest = rest.trim();
|
||||
|
||||
if rest.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Split on " to " or " in " (case-insensitive)
|
||||
let (from_str, target_str) = split_on_connector(rest);
|
||||
|
||||
// Resolve from unit
|
||||
let from_lower = from_str.trim().to_lowercase();
|
||||
let from_symbol = units::find_unit(&from_lower)?;
|
||||
|
||||
let from_symbol_str = from_symbol.to_string();
|
||||
|
||||
// Resolve target unit if present
|
||||
let target_unit = target_str.and_then(|t| {
|
||||
let t_lower = t.trim().to_lowercase();
|
||||
if t_lower.is_empty() {
|
||||
None
|
||||
} else {
|
||||
units::find_unit(&t_lower).map(|_| t_lower)
|
||||
}
|
||||
});
|
||||
|
||||
Some(ParsedQuery {
|
||||
value,
|
||||
from_unit: from_lower,
|
||||
from_symbol: from_symbol_str,
|
||||
target_unit,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_number(input: &str) -> Option<(f64, &str)> {
|
||||
let bytes = input.as_bytes();
|
||||
let mut i = 0;
|
||||
|
||||
// Optional negative sign
|
||||
if i < bytes.len() && bytes[i] == b'-' {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Must have at least one digit or start with .
|
||||
if i >= bytes.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start_digits = i;
|
||||
|
||||
// Integer part
|
||||
while i < bytes.len() && bytes[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Decimal part
|
||||
if i < bytes.len() && bytes[i] == b'.' {
|
||||
i += 1;
|
||||
while i < bytes.len() && bytes[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if i == start_digits && !(i > 0 && bytes[0] == b'-') {
|
||||
// No digits found (and not just a negative sign before a dot)
|
||||
// Handle ".5" case
|
||||
if bytes[start_digits] == b'.' {
|
||||
// already advanced past dot above
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 || (i == 1 && bytes[0] == b'-') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let num_str = &input[..i];
|
||||
let value: f64 = num_str.parse().ok()?;
|
||||
let rest = &input[i..];
|
||||
|
||||
Some((value, rest))
|
||||
}
|
||||
|
||||
fn split_on_connector(input: &str) -> (&str, Option<&str>) {
|
||||
let lower = input.to_lowercase();
|
||||
|
||||
// Try " to " first
|
||||
if let Some(pos) = lower.find(" to ") {
|
||||
let from = &input[..pos];
|
||||
let target = &input[pos + 4..];
|
||||
return (from, Some(target));
|
||||
}
|
||||
|
||||
// Try " in "
|
||||
if let Some(pos) = lower.find(" in ") {
|
||||
let from = &input[..pos];
|
||||
let target = &input[pos + 4..];
|
||||
return (from, Some(target));
|
||||
}
|
||||
|
||||
(input, None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_number_and_unit_with_space() {
|
||||
let p = parse_conversion("100 km").unwrap();
|
||||
assert!((p.value - 100.0).abs() < 0.001);
|
||||
assert_eq!(p.from_unit, "km");
|
||||
assert!(p.target_unit.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_and_unit_no_space() {
|
||||
let p = parse_conversion("100km").unwrap();
|
||||
assert!((p.value - 100.0).abs() < 0.001);
|
||||
assert_eq!(p.from_unit, "km");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_target_to() {
|
||||
let p = parse_conversion("100 km to mi").unwrap();
|
||||
assert!((p.value - 100.0).abs() < 0.001);
|
||||
assert_eq!(p.from_unit, "km");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("mi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_target_in() {
|
||||
let p = parse_conversion("100 km in mi").unwrap();
|
||||
assert_eq!(p.target_unit.as_deref(), Some("mi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_no_space() {
|
||||
let p = parse_conversion("102F to C").unwrap();
|
||||
assert!((p.value - 102.0).abs() < 0.001);
|
||||
assert_eq!(p.from_unit, "f");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temperature_with_space() {
|
||||
let p = parse_conversion("102 F in K").unwrap();
|
||||
assert!((p.value - 102.0).abs() < 0.001);
|
||||
assert_eq!(p.from_unit, "f");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("k"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_number() {
|
||||
let p = parse_conversion("3.5 kg to lb").unwrap();
|
||||
assert!((p.value - 3.5).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_starting_with_dot() {
|
||||
let p = parse_conversion(".5 kg").unwrap();
|
||||
assert!((p.value - 0.5).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_unit_names() {
|
||||
let p = parse_conversion("100 kilometers to miles").unwrap();
|
||||
assert_eq!(p.from_unit, "kilometers");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("miles"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_case_insensitive() {
|
||||
let p = parse_conversion("100 KM TO MI").unwrap();
|
||||
assert_eq!(p.from_unit, "km");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("mi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_currency() {
|
||||
let p = parse_conversion("100 eur to usd").unwrap();
|
||||
assert_eq!(p.from_unit, "eur");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("usd"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_number_returns_none() {
|
||||
assert!(parse_conversion("km to mi").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_unit_returns_none() {
|
||||
assert!(parse_conversion("100 xyz to abc").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_returns_none() {
|
||||
assert!(parse_conversion("").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_only_returns_none() {
|
||||
assert!(parse_conversion("100").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compound_unit_alias() {
|
||||
let p = parse_conversion("100 km/h to mph").unwrap();
|
||||
assert_eq!(p.from_unit, "km/h");
|
||||
assert_eq!(p.target_unit.as_deref(), Some("mph"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_word_unit() {
|
||||
let p = parse_conversion("100 fl_oz to ml").unwrap();
|
||||
assert_eq!(p.from_unit, "fl_oz");
|
||||
}
|
||||
}
|
||||
944
crates/owlry-plugin-converter/src/units.rs
Normal file
944
crates/owlry-plugin-converter/src/units.rs
Normal file
@@ -0,0 +1,944 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::currency;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Category {
|
||||
Temperature,
|
||||
Length,
|
||||
Weight,
|
||||
Volume,
|
||||
Speed,
|
||||
Area,
|
||||
Data,
|
||||
Time,
|
||||
Pressure,
|
||||
Energy,
|
||||
Currency,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Conversion {
|
||||
Factor(f64),
|
||||
Custom {
|
||||
to_base: fn(f64) -> f64,
|
||||
from_base: fn(f64) -> f64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct UnitDef {
|
||||
_id: &'static str,
|
||||
symbol: &'static str,
|
||||
aliases: &'static [&'static str],
|
||||
category: Category,
|
||||
conversion: Conversion,
|
||||
}
|
||||
|
||||
impl UnitDef {
|
||||
fn to_base(&self, value: f64) -> f64 {
|
||||
match &self.conversion {
|
||||
Conversion::Factor(f) => value * f,
|
||||
Conversion::Custom { to_base, .. } => to_base(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_from_base(&self, value: f64) -> f64 {
|
||||
match &self.conversion {
|
||||
Conversion::Factor(f) => value / f,
|
||||
Conversion::Custom { from_base, .. } => from_base(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConversionResult {
|
||||
pub value: f64,
|
||||
pub raw_value: String,
|
||||
pub display_value: String,
|
||||
pub target_symbol: String,
|
||||
}
|
||||
|
||||
static UNITS: LazyLock<Vec<UnitDef>> = LazyLock::new(build_unit_table);
|
||||
static ALIAS_MAP: LazyLock<HashMap<String, usize>> = LazyLock::new(|| {
|
||||
let mut map = HashMap::new();
|
||||
for (i, unit) in UNITS.iter().enumerate() {
|
||||
for alias in unit.aliases {
|
||||
map.insert(alias.to_lowercase(), i);
|
||||
}
|
||||
}
|
||||
map
|
||||
});
|
||||
|
||||
// Common conversions per category (symbols to show when no target specified)
|
||||
static COMMON_TARGETS: LazyLock<HashMap<Category, Vec<&'static str>>> = LazyLock::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(Category::Temperature, vec!["°C", "°F", "K"]);
|
||||
m.insert(Category::Length, vec!["m", "km", "ft", "mi", "in"]);
|
||||
m.insert(Category::Weight, vec!["kg", "lb", "oz", "g", "st"]);
|
||||
m.insert(Category::Volume, vec!["l", "gal", "ml", "cup", "fl oz"]);
|
||||
m.insert(Category::Speed, vec!["km/h", "mph", "m/s", "kn"]);
|
||||
m.insert(Category::Area, vec!["m²", "ft²", "ac", "ha", "km²"]);
|
||||
m.insert(Category::Data, vec!["MB", "GB", "MiB", "GiB", "TB"]);
|
||||
m.insert(Category::Time, vec!["s", "min", "h", "d", "wk"]);
|
||||
m.insert(Category::Pressure, vec!["bar", "psi", "atm", "hPa", "mmHg"]);
|
||||
m.insert(Category::Energy, vec!["kJ", "kcal", "kWh", "BTU", "Wh"]);
|
||||
m.insert(Category::Currency, vec!["USD", "EUR", "GBP", "JPY", "CNY"]);
|
||||
m
|
||||
});
|
||||
|
||||
pub fn find_unit(alias: &str) -> Option<&'static str> {
|
||||
let lower = alias.to_lowercase();
|
||||
if let Some(&i) = ALIAS_MAP.get(&lower) {
|
||||
return Some(UNITS[i].symbol);
|
||||
}
|
||||
currency::resolve_currency_code(&lower)
|
||||
}
|
||||
|
||||
pub fn lookup_unit(alias: &str) -> Option<(usize, &UnitDef)> {
|
||||
let lower = alias.to_lowercase();
|
||||
ALIAS_MAP.get(&lower).map(|&i| (i, &UNITS[i]))
|
||||
}
|
||||
|
||||
pub fn convert_to(value: &f64, from: &str, to: &str) -> Option<ConversionResult> {
|
||||
// Try currency first — currency aliases (dollar, euro, etc.) aren't in the UNITS table
|
||||
if currency::is_currency_alias(from) || currency::is_currency_alias(to) {
|
||||
return convert_currency(*value, from, to);
|
||||
}
|
||||
|
||||
let (_, from_def) = lookup_unit(from)?;
|
||||
let (_, to_def) = lookup_unit(to)?;
|
||||
|
||||
// Currency via UNITS table (shouldn't reach here, but just in case)
|
||||
if from_def.category == Category::Currency || to_def.category == Category::Currency {
|
||||
return convert_currency(*value, from, to);
|
||||
}
|
||||
|
||||
// Must be same category
|
||||
if from_def.category != to_def.category {
|
||||
return None;
|
||||
}
|
||||
|
||||
let base_value = from_def.to_base(*value);
|
||||
let result = to_def.convert_from_base(base_value);
|
||||
|
||||
Some(format_result(result, to_def.symbol))
|
||||
}
|
||||
|
||||
pub fn convert_common(value: &f64, from: &str) -> Vec<ConversionResult> {
|
||||
// Try currency first — currency aliases (dollar, euro, etc.) aren't in the UNITS table
|
||||
if currency::is_currency_alias(from) {
|
||||
return convert_currency_common(*value, from);
|
||||
}
|
||||
|
||||
let (_, from_def) = match lookup_unit(from) {
|
||||
Some(u) => u,
|
||||
None => return vec![],
|
||||
};
|
||||
|
||||
let category = from_def.category;
|
||||
let from_symbol = from_def.symbol;
|
||||
|
||||
if category == Category::Currency {
|
||||
return convert_currency_common(*value, from);
|
||||
}
|
||||
|
||||
let targets = match COMMON_TARGETS.get(&category) {
|
||||
Some(t) => t,
|
||||
None => return vec![],
|
||||
};
|
||||
|
||||
let base_value = from_def.to_base(*value);
|
||||
|
||||
targets
|
||||
.iter()
|
||||
.filter(|&&sym| sym != from_symbol)
|
||||
.filter_map(|&sym| {
|
||||
let (_, to_def) = lookup_unit(sym)?;
|
||||
let result = to_def.convert_from_base(base_value);
|
||||
Some(format_result(result, to_def.symbol))
|
||||
})
|
||||
.take(5)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn convert_currency(value: f64, from: &str, to: &str) -> Option<ConversionResult> {
|
||||
let rates = currency::get_rates()?;
|
||||
let from_code = currency::resolve_currency_code(from)?;
|
||||
let to_code = currency::resolve_currency_code(to)?;
|
||||
|
||||
let from_rate = if from_code == "EUR" { 1.0 } else { *rates.rates.get(from_code)? };
|
||||
let to_rate = if to_code == "EUR" { 1.0 } else { *rates.rates.get(to_code)? };
|
||||
|
||||
let result = value / from_rate * to_rate;
|
||||
Some(format_currency_result(result, to_code))
|
||||
}
|
||||
|
||||
fn convert_currency_common(value: f64, from: &str) -> Vec<ConversionResult> {
|
||||
let rates = match currency::get_rates() {
|
||||
Some(r) => r,
|
||||
None => return vec![],
|
||||
};
|
||||
let from_code = match currency::resolve_currency_code(from) {
|
||||
Some(c) => c,
|
||||
None => return vec![],
|
||||
};
|
||||
|
||||
let targets = COMMON_TARGETS.get(&Category::Currency).unwrap();
|
||||
let from_rate = if from_code == "EUR" {
|
||||
1.0
|
||||
} else {
|
||||
match rates.rates.get(from_code) {
|
||||
Some(&r) => r,
|
||||
None => return vec![],
|
||||
}
|
||||
};
|
||||
|
||||
targets
|
||||
.iter()
|
||||
.filter(|&&sym| sym != from_code)
|
||||
.filter_map(|&sym| {
|
||||
let to_rate = if sym == "EUR" { 1.0 } else { *rates.rates.get(sym)? };
|
||||
let result = value / from_rate * to_rate;
|
||||
Some(format_currency_result(result, sym))
|
||||
})
|
||||
.take(5)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn format_result(value: f64, symbol: &str) -> ConversionResult {
|
||||
let raw = if value.fract() == 0.0 && value.abs() < 1e15 {
|
||||
format!("{}", value as i64)
|
||||
} else {
|
||||
format!("{:.4}", value)
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.')
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let display = if value.abs() >= 1000.0 && value.fract() == 0.0 && value.abs() < 1e15 {
|
||||
crate::format_with_separators(value as i64)
|
||||
} else {
|
||||
raw.clone()
|
||||
};
|
||||
|
||||
ConversionResult {
|
||||
value,
|
||||
raw_value: raw,
|
||||
display_value: format!("{} {}", display, symbol),
|
||||
target_symbol: symbol.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_currency_result(value: f64, code: &str) -> ConversionResult {
|
||||
let raw = format!("{:.2}", value);
|
||||
let display = raw.clone();
|
||||
|
||||
ConversionResult {
|
||||
value,
|
||||
raw_value: raw,
|
||||
display_value: format!("{} {}", display, code),
|
||||
target_symbol: code.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_unit_table() -> Vec<UnitDef> {
|
||||
vec![
|
||||
// Temperature (base: Kelvin)
|
||||
UnitDef {
|
||||
_id: "celsius",
|
||||
symbol: "°C",
|
||||
aliases: &["c", "°c", "celsius", "degc", "centigrade"],
|
||||
category: Category::Temperature,
|
||||
conversion: Conversion::Custom {
|
||||
to_base: |v| v + 273.15,
|
||||
from_base: |v| v - 273.15,
|
||||
},
|
||||
},
|
||||
UnitDef {
|
||||
_id: "fahrenheit",
|
||||
symbol: "°F",
|
||||
aliases: &["f", "°f", "fahrenheit", "degf"],
|
||||
category: Category::Temperature,
|
||||
conversion: Conversion::Custom {
|
||||
to_base: |v| (v - 32.0) * 5.0 / 9.0 + 273.15,
|
||||
from_base: |v| (v - 273.15) * 9.0 / 5.0 + 32.0,
|
||||
},
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kelvin",
|
||||
symbol: "K",
|
||||
aliases: &["k", "kelvin"],
|
||||
category: Category::Temperature,
|
||||
conversion: Conversion::Factor(1.0), // base
|
||||
},
|
||||
// Length (base: meter)
|
||||
UnitDef {
|
||||
_id: "millimeter",
|
||||
symbol: "mm",
|
||||
aliases: &["mm", "millimeter", "millimeters", "millimetre"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(0.001),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "centimeter",
|
||||
symbol: "cm",
|
||||
aliases: &["cm", "centimeter", "centimeters", "centimetre"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(0.01),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "meter",
|
||||
symbol: "m",
|
||||
aliases: &["m", "meter", "meters", "metre", "metres"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilometer",
|
||||
symbol: "km",
|
||||
aliases: &["km", "kms", "kilometer", "kilometers", "kilometre"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(1000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "inch",
|
||||
symbol: "in",
|
||||
aliases: &["in", "inch", "inches"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(0.0254),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "foot",
|
||||
symbol: "ft",
|
||||
aliases: &["ft", "foot", "feet"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(0.3048),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "yard",
|
||||
symbol: "yd",
|
||||
aliases: &["yd", "yard", "yards"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(0.9144),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "mile",
|
||||
symbol: "mi",
|
||||
aliases: &["mi", "mile", "miles"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(1609.344),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "nautical_mile",
|
||||
symbol: "nmi",
|
||||
aliases: &["nmi", "nautical_mile", "nautical_miles"],
|
||||
category: Category::Length,
|
||||
conversion: Conversion::Factor(1852.0),
|
||||
},
|
||||
// Weight (base: kg)
|
||||
UnitDef {
|
||||
_id: "milligram",
|
||||
symbol: "mg",
|
||||
aliases: &["mg", "milligram", "milligrams"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(0.000001),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "gram",
|
||||
symbol: "g",
|
||||
aliases: &["g", "gram", "grams"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(0.001),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilogram",
|
||||
symbol: "kg",
|
||||
aliases: &["kg", "kilogram", "kilograms", "kilo", "kilos"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "tonne",
|
||||
symbol: "t",
|
||||
aliases: &["t", "ton", "tons", "tonne", "tonnes", "metric_ton"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(1000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "short_ton",
|
||||
symbol: "short_ton",
|
||||
aliases: &["short_ton", "ton_us"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(907.185),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "ounce",
|
||||
symbol: "oz",
|
||||
aliases: &["oz", "ounce", "ounces"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(0.0283495),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "pound",
|
||||
symbol: "lb",
|
||||
aliases: &["lb", "lbs", "pound", "pounds"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(0.453592),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "stone",
|
||||
symbol: "st",
|
||||
aliases: &["st", "stone", "stones"],
|
||||
category: Category::Weight,
|
||||
conversion: Conversion::Factor(6.35029),
|
||||
},
|
||||
// Volume (base: liter)
|
||||
UnitDef {
|
||||
_id: "milliliter",
|
||||
symbol: "ml",
|
||||
aliases: &["ml", "milliliter", "milliliters", "millilitre"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.001),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "liter",
|
||||
symbol: "l",
|
||||
aliases: &["l", "liter", "liters", "litre", "litres"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "us_gallon",
|
||||
symbol: "gal",
|
||||
aliases: &["gal", "gallon", "gallons"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(3.78541),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "imp_gallon",
|
||||
symbol: "imp gal",
|
||||
aliases: &["imp_gal", "gal_uk", "imperial_gallon"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(4.54609),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "quart",
|
||||
symbol: "qt",
|
||||
aliases: &["qt", "quart", "quarts"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.946353),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "pint",
|
||||
symbol: "pt",
|
||||
aliases: &["pt", "pint", "pints"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.473176),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "cup",
|
||||
symbol: "cup",
|
||||
aliases: &["cup", "cups"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.236588),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "fluid_ounce",
|
||||
symbol: "fl oz",
|
||||
aliases: &["floz", "fl_oz", "fluid_ounce", "fluid_ounces"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.0295735),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "tablespoon",
|
||||
symbol: "tbsp",
|
||||
aliases: &["tbsp", "tablespoon", "tablespoons"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.0147868),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "teaspoon",
|
||||
symbol: "tsp",
|
||||
aliases: &["tsp", "teaspoon", "teaspoons"],
|
||||
category: Category::Volume,
|
||||
conversion: Conversion::Factor(0.00492892),
|
||||
},
|
||||
// Speed (base: m/s)
|
||||
UnitDef {
|
||||
_id: "mps",
|
||||
symbol: "m/s",
|
||||
aliases: &["m/s", "mps", "meters_per_second"],
|
||||
category: Category::Speed,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kmh",
|
||||
symbol: "km/h",
|
||||
aliases: &["km/h", "kmh", "kph", "kilometers_per_hour"],
|
||||
category: Category::Speed,
|
||||
conversion: Conversion::Factor(0.277778),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "mph",
|
||||
symbol: "mph",
|
||||
aliases: &["mph", "miles_per_hour"],
|
||||
category: Category::Speed,
|
||||
conversion: Conversion::Factor(0.44704),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "knot",
|
||||
symbol: "kn",
|
||||
aliases: &["kn", "kt", "knot", "knots"],
|
||||
category: Category::Speed,
|
||||
conversion: Conversion::Factor(0.514444),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "fps",
|
||||
symbol: "ft/s",
|
||||
aliases: &["ft/s", "fps", "feet_per_second"],
|
||||
category: Category::Speed,
|
||||
conversion: Conversion::Factor(0.3048),
|
||||
},
|
||||
// Area (base: m²)
|
||||
UnitDef {
|
||||
_id: "sqmm",
|
||||
symbol: "mm²",
|
||||
aliases: &["mm2", "sqmm", "square_millimeter"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(0.000001),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "sqcm",
|
||||
symbol: "cm²",
|
||||
aliases: &["cm2", "sqcm", "square_centimeter"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(0.0001),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "sqm",
|
||||
symbol: "m²",
|
||||
aliases: &["m2", "sqm", "square_meter", "square_meters"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "sqkm",
|
||||
symbol: "km²",
|
||||
aliases: &["km2", "sqkm", "square_kilometer"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(1000000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "sqft",
|
||||
symbol: "ft²",
|
||||
aliases: &["ft2", "sqft", "square_foot", "square_feet"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(0.092903),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "acre",
|
||||
symbol: "ac",
|
||||
aliases: &["ac", "acre", "acres"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(4046.86),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "hectare",
|
||||
symbol: "ha",
|
||||
aliases: &["ha", "hectare", "hectares"],
|
||||
category: Category::Area,
|
||||
conversion: Conversion::Factor(10000.0),
|
||||
},
|
||||
// Data (base: byte)
|
||||
UnitDef {
|
||||
_id: "byte",
|
||||
symbol: "B",
|
||||
aliases: &["b", "byte", "bytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilobyte",
|
||||
symbol: "KB",
|
||||
aliases: &["kb", "kilobyte", "kilobytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "megabyte",
|
||||
symbol: "MB",
|
||||
aliases: &["mb", "megabyte", "megabytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1_000_000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "gigabyte",
|
||||
symbol: "GB",
|
||||
aliases: &["gb", "gigabyte", "gigabytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1_000_000_000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "terabyte",
|
||||
symbol: "TB",
|
||||
aliases: &["tb", "terabyte", "terabytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1_000_000_000_000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kibibyte",
|
||||
symbol: "KiB",
|
||||
aliases: &["kib", "kibibyte", "kibibytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1024.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "mebibyte",
|
||||
symbol: "MiB",
|
||||
aliases: &["mib", "mebibyte", "mebibytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1_048_576.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "gibibyte",
|
||||
symbol: "GiB",
|
||||
aliases: &["gib", "gibibyte", "gibibytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1_073_741_824.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "tebibyte",
|
||||
symbol: "TiB",
|
||||
aliases: &["tib", "tebibyte", "tebibytes"],
|
||||
category: Category::Data,
|
||||
conversion: Conversion::Factor(1_099_511_627_776.0),
|
||||
},
|
||||
// Time (base: second)
|
||||
UnitDef {
|
||||
_id: "second",
|
||||
symbol: "s",
|
||||
aliases: &["s", "sec", "second", "seconds"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "minute",
|
||||
symbol: "min",
|
||||
aliases: &["min", "minute", "minutes"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(60.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "hour",
|
||||
symbol: "h",
|
||||
aliases: &["h", "hr", "hour", "hours"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(3600.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "day",
|
||||
symbol: "d",
|
||||
aliases: &["d", "day", "days"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(86400.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "week",
|
||||
symbol: "wk",
|
||||
aliases: &["wk", "week", "weeks"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(604800.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "month",
|
||||
symbol: "mo",
|
||||
aliases: &["mo", "month", "months"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(2_592_000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "year",
|
||||
symbol: "yr",
|
||||
aliases: &["yr", "year", "years"],
|
||||
category: Category::Time,
|
||||
conversion: Conversion::Factor(31_536_000.0),
|
||||
},
|
||||
// Pressure (base: Pa)
|
||||
UnitDef {
|
||||
_id: "pascal",
|
||||
symbol: "Pa",
|
||||
aliases: &["pa", "pascal", "pascals"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "hectopascal",
|
||||
symbol: "hPa",
|
||||
aliases: &["hpa", "hectopascal"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(100.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilopascal",
|
||||
symbol: "kPa",
|
||||
aliases: &["kpa", "kilopascal"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(1000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "bar",
|
||||
symbol: "bar",
|
||||
aliases: &["bar", "bars"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(100_000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "millibar",
|
||||
symbol: "mbar",
|
||||
aliases: &["mbar", "millibar"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(100.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "psi",
|
||||
symbol: "psi",
|
||||
aliases: &["psi", "pounds_per_square_inch"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(6894.76),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "atmosphere",
|
||||
symbol: "atm",
|
||||
aliases: &["atm", "atmosphere", "atmospheres"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(101_325.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "mmhg",
|
||||
symbol: "mmHg",
|
||||
aliases: &["mmhg", "torr"],
|
||||
category: Category::Pressure,
|
||||
conversion: Conversion::Factor(133.322),
|
||||
},
|
||||
// Energy (base: Joule)
|
||||
UnitDef {
|
||||
_id: "joule",
|
||||
symbol: "J",
|
||||
aliases: &["j", "joule", "joules"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(1.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilojoule",
|
||||
symbol: "kJ",
|
||||
aliases: &["kj", "kilojoule", "kilojoules"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(1000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "calorie",
|
||||
symbol: "cal",
|
||||
aliases: &["cal", "calorie", "calories"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(4.184),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilocalorie",
|
||||
symbol: "kcal",
|
||||
aliases: &["kcal", "kilocalorie", "kilocalories"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(4184.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "watt_hour",
|
||||
symbol: "Wh",
|
||||
aliases: &["wh", "watt_hour"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(3600.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "kilowatt_hour",
|
||||
symbol: "kWh",
|
||||
aliases: &["kwh", "kilowatt_hour"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(3_600_000.0),
|
||||
},
|
||||
UnitDef {
|
||||
_id: "btu",
|
||||
symbol: "BTU",
|
||||
aliases: &["btu", "british_thermal_unit"],
|
||||
category: Category::Energy,
|
||||
conversion: Conversion::Factor(1055.06),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_celsius_to_fahrenheit() {
|
||||
let r = convert_to(&100.0, "c", "f").unwrap();
|
||||
assert!((r.value - 212.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fahrenheit_to_celsius() {
|
||||
let r = convert_to(&32.0, "f", "c").unwrap();
|
||||
assert!((r.value - 0.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_body_temp_f_to_c() {
|
||||
let r = convert_to(&98.6, "f", "c").unwrap();
|
||||
assert!((r.value - 37.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_km_to_miles() {
|
||||
let r = convert_to(&100.0, "km", "mi").unwrap();
|
||||
assert!((r.value - 62.1371).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_miles_to_km() {
|
||||
let r = convert_to(&1.0, "mi", "km").unwrap();
|
||||
assert!((r.value - 1.60934).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kg_to_lb() {
|
||||
let r = convert_to(&1.0, "kg", "lb").unwrap();
|
||||
assert!((r.value - 2.20462).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lb_to_kg() {
|
||||
let r = convert_to(&100.0, "lbs", "kg").unwrap();
|
||||
assert!((r.value - 45.3592).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_liters_to_gallons() {
|
||||
let r = convert_to(&3.78541, "l", "gal").unwrap();
|
||||
assert!((r.value - 1.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kmh_to_mph() {
|
||||
let r = convert_to(&100.0, "kmh", "mph").unwrap();
|
||||
assert!((r.value - 62.1371).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gb_to_mb() {
|
||||
let r = convert_to(&1.0, "gb", "mb").unwrap();
|
||||
assert!((r.value - 1000.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gib_to_mib() {
|
||||
let r = convert_to(&1.0, "gib", "mib").unwrap();
|
||||
assert!((r.value - 1024.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hours_to_minutes() {
|
||||
let r = convert_to(&2.5, "h", "min").unwrap();
|
||||
assert!((r.value - 150.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bar_to_psi() {
|
||||
let r = convert_to(&1.0, "bar", "psi").unwrap();
|
||||
assert!((r.value - 14.5038).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kcal_to_kj() {
|
||||
let r = convert_to(&1.0, "kcal", "kj").unwrap();
|
||||
assert!((r.value - 4.184).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqm_to_sqft() {
|
||||
let r = convert_to(&1.0, "m2", "ft2").unwrap();
|
||||
assert!((r.value - 10.7639).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_unit_returns_none() {
|
||||
assert!(convert_to(&100.0, "xyz", "abc").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cross_category_returns_none() {
|
||||
assert!(convert_to(&100.0, "km", "kg").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_common_returns_results() {
|
||||
let results = convert_common(&100.0, "km");
|
||||
assert!(!results.is_empty());
|
||||
assert!(results.len() <= 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_common_excludes_source() {
|
||||
let results = convert_common(&100.0, "km");
|
||||
for r in &results {
|
||||
assert_ne!(r.target_symbol, "km");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias_case_insensitive() {
|
||||
let r1 = convert_to(&100.0, "KM", "MI").unwrap();
|
||||
let r2 = convert_to(&100.0, "km", "mi").unwrap();
|
||||
assert!((r1.value - r2.value).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_name_alias() {
|
||||
let r = convert_to(&100.0, "kilometers", "miles").unwrap();
|
||||
assert!((r.value - 62.1371).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_currency_two_decimals() {
|
||||
let r = convert_to(&1.0, "km", "mi").unwrap();
|
||||
// display_value should have reasonable formatting
|
||||
assert!(!r.display_value.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_currency_alias_convert_to() {
|
||||
// "dollar" and "euro" are aliases, not in the UNITS table
|
||||
let r = convert_to(&20.0, "dollar", "euro");
|
||||
// May return None if ECB rates unavailable (network), but should not panic
|
||||
// In a network-available environment, this should return Some
|
||||
if let Some(r) = r {
|
||||
assert!(r.value > 0.0);
|
||||
assert_eq!(r.target_symbol, "EUR");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_currency_alias_convert_common() {
|
||||
let results = convert_common(&20.0, "dollar");
|
||||
// May be empty if ECB rates unavailable, but should not panic
|
||||
for r in &results {
|
||||
assert!(r.value > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_value_no_double_unit() {
|
||||
let r = convert_to(&100.0, "km", "mi").unwrap();
|
||||
// display_value should contain the symbol exactly once
|
||||
let count = r.display_value.matches(&r.target_symbol).count();
|
||||
assert_eq!(count, 1, "display_value '{}' should contain '{}' exactly once", r.display_value, r.target_symbol);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-emoji"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-ssh"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
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"
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-websearch"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
1768
docs/superpowers/plans/2026-03-26-converter-plugin.md
Normal file
1768
docs/superpowers/plans/2026-03-26-converter-plugin.md
Normal file
File diff suppressed because it is too large
Load Diff
419
docs/superpowers/specs/2026-03-26-converter-plugin-design.md
Normal file
419
docs/superpowers/specs/2026-03-26-converter-plugin-design.md
Normal 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)
|
||||
Reference in New Issue
Block a user