Compare commits
19 Commits
owlry-plug
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d5088c3d64 | |||
| f613dcb2d1 | |||
| da5085e0d2 | |||
| d10fa3cdce | |||
| d6df6ca96e | |||
| 4a7693d50b | |||
| 4068037a9a | |||
| 461a9c2249 | |||
| ecdcca93a4 | |||
| 7ffbd46358 | |||
| 627cbcbf91 | |||
| 9e221b2328 | |||
| effbfa68e4 | |||
| 7d69105930 | |||
| 2bd72c8af1 | |||
| b4de6a3791 | |||
| c73f57578d | |||
| b46477ae88 | |||
| 9c3dec9d14 |
857
Cargo.lock
generated
857
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
[workspace]
|
||||
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",
|
||||
@@ -10,7 +8,6 @@ members = [
|
||||
"crates/owlry-plugin-pomodoro",
|
||||
"crates/owlry-plugin-scripts",
|
||||
"crates/owlry-plugin-ssh",
|
||||
"crates/owlry-plugin-system",
|
||||
"crates/owlry-plugin-systemd",
|
||||
"crates/owlry-plugin-weather",
|
||||
"crates/owlry-plugin-websearch",
|
||||
|
||||
84
README.md
84
README.md
@@ -1,56 +1,66 @@
|
||||
# owlry-plugins
|
||||
|
||||
Official plugins and script runtimes for [owlry](https://somegit.dev/Owlibou/owlry).
|
||||
Official plugins for [owlry](https://somegit.dev/Owlibou/owlry).
|
||||
|
||||
> **Note:** Calculator, converter, and system actions are built into `owlry-core` (>= 1.2.0) and do not require separate packages.
|
||||
|
||||
## Plugins
|
||||
|
||||
| Plugin | Description |
|
||||
|--------|-------------|
|
||||
| calculator | Mathematical expression evaluation |
|
||||
| bookmarks | Browser bookmark search (Firefox, Chrome) |
|
||||
| clipboard | Clipboard history via cliphist |
|
||||
| emoji | Emoji picker |
|
||||
| filesearch | File search via fd/locate |
|
||||
| media | MPRIS media player widget |
|
||||
| pomodoro | Pomodoro timer widget |
|
||||
| scripts | User script launcher |
|
||||
| ssh | SSH host quick-connect |
|
||||
| system | Power and session management |
|
||||
| systemd | systemd user service control |
|
||||
| weather | Weather widget |
|
||||
| websearch | Web search with configurable engines |
|
||||
|
||||
## Runtimes
|
||||
|
||||
| Runtime | Description |
|
||||
|---------|-------------|
|
||||
| owlry-lua | Lua 5.4 scripting runtime for user plugins |
|
||||
| owlry-rune | Rune scripting runtime for user plugins |
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
just build # Debug build
|
||||
just release # Release build (optimized)
|
||||
just plugin calc # Build a single plugin
|
||||
just check # cargo check + clippy
|
||||
just test # Run tests
|
||||
```
|
||||
| Plugin | Description | Dependencies |
|
||||
|--------|-------------|-------------|
|
||||
| bookmarks | Browser bookmark search (Firefox, Chrome, Brave, Edge) | — |
|
||||
| clipboard | Clipboard history | `cliphist`, `wl-clipboard` |
|
||||
| emoji | Emoji picker (400+) | `wl-clipboard`, `noto-fonts-emoji` |
|
||||
| filesearch | File search (`/ filename`) | `fd` or `mlocate` |
|
||||
| media | MPRIS media player widget | `playerctl` |
|
||||
| pomodoro | Pomodoro timer widget | — |
|
||||
| scripts | User script launcher | — |
|
||||
| ssh | SSH host quick-connect | `openssh` |
|
||||
| systemd | systemd user service control | `systemd` |
|
||||
| weather | Weather widget | — |
|
||||
| websearch | Web search with configurable engines | — |
|
||||
|
||||
## Installation
|
||||
|
||||
### Arch Linux (AUR)
|
||||
|
||||
```bash
|
||||
just install-local # Install all plugins and runtimes to /usr/lib/owlry/
|
||||
# Install individual plugins
|
||||
yay -S owlry-plugin-bookmarks owlry-plugin-clipboard owlry-plugin-weather
|
||||
|
||||
# Or install several at once
|
||||
yay -S owlry-plugin-{bookmarks,clipboard,emoji,ssh,websearch}
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
|
||||
Requires Rust 1.90+ and `owlry-core` installed.
|
||||
|
||||
```bash
|
||||
git clone https://somegit.dev/Owlibou/owlry-plugins.git
|
||||
cd owlry-plugins
|
||||
|
||||
just build # Debug build (all plugins)
|
||||
just release # Release build (optimized)
|
||||
just plugin bookmarks # Build a single plugin
|
||||
|
||||
just install-local # Install all plugins to /usr/lib/owlry/plugins/
|
||||
```
|
||||
|
||||
Plugins are compiled as `.so` files and installed to `/usr/lib/owlry/plugins/`.
|
||||
Runtimes are installed to `/usr/lib/owlry/runtimes/`.
|
||||
|
||||
## Development
|
||||
|
||||
See [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for plugin authoring guide.
|
||||
Each plugin is a `cdylib` crate implementing the `owlry-plugin-api` ABI-stable interface from the [core repo](https://somegit.dev/Owlibou/owlry).
|
||||
|
||||
Plugins depend on `owlry-plugin-api` from the core repo for the ABI-stable interface.
|
||||
```bash
|
||||
just check # cargo check + clippy
|
||||
just test # Run tests
|
||||
just fmt # Format code
|
||||
just show-versions # List all plugin versions
|
||||
```
|
||||
|
||||
See [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for the plugin authoring guide.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
pkgbase = owlry-meta-essentials
|
||||
pkgdesc = Essential plugin bundle 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-core
|
||||
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,19 +0,0 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-meta-essentials
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="Essential plugin bundle for Owlry (calculator, system, ssh, scripts, bookmarks)"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=(
|
||||
'owlry'
|
||||
'owlry-core'
|
||||
'owlry-plugin-bookmarks'
|
||||
'owlry-plugin-calculator'
|
||||
'owlry-plugin-scripts'
|
||||
'owlry-plugin-ssh'
|
||||
'owlry-plugin-system'
|
||||
)
|
||||
replaces=('owlry-essentials')
|
||||
conflicts=('owlry-essentials')
|
||||
@@ -1,28 +0,0 @@
|
||||
pkgbase = owlry-meta-full
|
||||
pkgdesc = Complete Owlry installation with all official plugins and runtimes
|
||||
pkgver = 1.0.0
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
arch = any
|
||||
license = GPL-3.0-or-later
|
||||
depends = owlry
|
||||
depends = owlry-core
|
||||
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,34 +0,0 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-meta-full
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="Complete Owlry installation with all official plugins and runtimes"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=(
|
||||
'owlry'
|
||||
'owlry-core'
|
||||
# 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,18 +0,0 @@
|
||||
pkgbase = owlry-meta-tools
|
||||
pkgdesc = Tool plugin bundle for Owlry (clipboard, emoji, web search, file search, 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-core
|
||||
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,19 +0,0 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-meta-tools
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="Tool plugin bundle for Owlry (clipboard, emoji, web search, file search, systemd)"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=(
|
||||
'owlry'
|
||||
'owlry-core'
|
||||
'owlry-plugin-clipboard'
|
||||
'owlry-plugin-emoji'
|
||||
'owlry-plugin-filesearch'
|
||||
'owlry-plugin-systemd'
|
||||
'owlry-plugin-websearch'
|
||||
)
|
||||
replaces=('owlry-tools')
|
||||
conflicts=('owlry-tools')
|
||||
@@ -1,16 +0,0 @@
|
||||
pkgbase = owlry-meta-widgets
|
||||
pkgdesc = Widget plugin bundle for Owlry (weather, media controls, pomodoro timer)
|
||||
pkgver = 1.0.0
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
arch = any
|
||||
license = GPL-3.0-or-later
|
||||
depends = owlry
|
||||
depends = owlry-core
|
||||
depends = owlry-plugin-media
|
||||
depends = owlry-plugin-pomodoro
|
||||
depends = owlry-plugin-weather
|
||||
conflicts = owlry-widgets
|
||||
replaces = owlry-widgets
|
||||
|
||||
pkgname = owlry-meta-widgets
|
||||
@@ -1,17 +0,0 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-meta-widgets
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="Widget plugin bundle for Owlry (weather, media controls, pomodoro timer)"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=(
|
||||
'owlry'
|
||||
'owlry-core'
|
||||
'owlry-plugin-media'
|
||||
'owlry-plugin-pomodoro'
|
||||
'owlry-plugin-weather'
|
||||
)
|
||||
replaces=('owlry-widgets')
|
||||
conflicts=('owlry-widgets')
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-bookmarks
|
||||
pkgdesc = Bookmarks plugin for Owlry — search and launch browser bookmarks (Firefox, Chrome, Chromium)
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-bookmarks.install
|
||||
@@ -11,7 +11,7 @@ pkgbase = owlry-plugin-bookmarks
|
||||
optdepends = firefox: Firefox bookmarks support
|
||||
optdepends = chromium: Chromium bookmarks support
|
||||
optdepends = google-chrome: Chrome bookmarks support
|
||||
source = owlry-plugin-bookmarks-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-bookmarks-v1.0.0.tar.gz
|
||||
b2sums = SKIP
|
||||
source = owlry-plugin-bookmarks-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-bookmarks-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-bookmarks
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-bookmarks
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Bookmarks plugin for Owlry — search and launch browser bookmarks (Firefox, Chrome, Chromium)"
|
||||
arch=('x86_64')
|
||||
@@ -15,7 +15,7 @@ optdepends=(
|
||||
'google-chrome: Chrome bookmarks support'
|
||||
)
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-bookmarks
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
pkgbase = owlry-plugin-calculator
|
||||
pkgdesc = Calculator plugin for Owlry — evaluate math expressions inline
|
||||
pkgver = 1.0.0
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-calculator.install
|
||||
arch = x86_64
|
||||
pkgdesc = Transitional package — calculator is now built into owlry-core
|
||||
pkgver = 1.0.1
|
||||
pkgrel = 99
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
arch = any
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
source = owlry-plugin-calculator-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-calculator-v1.0.0.tar.gz
|
||||
b2sums = 3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98
|
||||
depends = owlry-core>=1.2.0
|
||||
replaces = owlry-plugin-calculator
|
||||
|
||||
pkgname = owlry-plugin-calculator
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-calculator
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="Calculator plugin for Owlry — evaluate math expressions inline"
|
||||
arch=('x86_64')
|
||||
url="https://somegit.dev/Owlibou/owlry-plugins"
|
||||
pkgver=1.0.1
|
||||
pkgrel=99
|
||||
pkgdesc="Transitional package — calculator is now built into owlry-core"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('owlry-core')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-calculator.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('3d9a096485d5dea487a69fd48019eb5cddf2781bda5acce0503ecc5f19412f274b3a6f60a1922c35bd7855e4f72eaacb131d8affd0ee21d00e213345134a1b98')
|
||||
|
||||
_cratename=owlry-plugin-calculator
|
||||
|
||||
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"
|
||||
}
|
||||
depends=('owlry-core>=1.2.0')
|
||||
replaces=('owlry-plugin-calculator')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-clipboard
|
||||
pkgdesc = Clipboard history plugin for Owlry — search and paste previous clipboard entries
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-clipboard.install
|
||||
@@ -10,7 +10,7 @@ pkgbase = owlry-plugin-clipboard
|
||||
depends = owlry-core
|
||||
depends = cliphist
|
||||
depends = wl-clipboard
|
||||
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 = SKIP
|
||||
source = owlry-plugin-clipboard-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-clipboard-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-clipboard
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-clipboard
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Clipboard history plugin for Owlry — search and paste previous clipboard entries"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core' 'cliphist' 'wl-clipboard')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-clipboard.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-clipboard
|
||||
|
||||
|
||||
11
aur/owlry-plugin-converter/.SRCINFO
Normal file
11
aur/owlry-plugin-converter/.SRCINFO
Normal file
@@ -0,0 +1,11 @@
|
||||
pkgbase = owlry-plugin-converter
|
||||
pkgdesc = Transitional package — converter is now built into owlry-core
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 99
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
arch = any
|
||||
license = GPL-3.0-or-later
|
||||
depends = owlry-core>=1.2.0
|
||||
replaces = owlry-plugin-converter
|
||||
|
||||
pkgname = owlry-plugin-converter
|
||||
10
aur/owlry-plugin-converter/PKGBUILD
Normal file
10
aur/owlry-plugin-converter/PKGBUILD
Normal file
@@ -0,0 +1,10 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-converter
|
||||
pkgver=1.0.2
|
||||
pkgrel=99
|
||||
pkgdesc="Transitional package — converter is now built into owlry-core"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('owlry-core>=1.2.0')
|
||||
replaces=('owlry-plugin-converter')
|
||||
BIN
aur/owlry-plugin-converter/owlry-plugin-converter-1.0.0.tar.gz
Normal file
BIN
aur/owlry-plugin-converter/owlry-plugin-converter-1.0.0.tar.gz
Normal file
Binary file not shown.
@@ -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,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-emoji
|
||||
pkgdesc = Emoji picker plugin for Owlry — search and insert emoji characters
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-emoji.install
|
||||
@@ -10,7 +10,7 @@ pkgbase = owlry-plugin-emoji
|
||||
depends = owlry-core
|
||||
depends = wl-clipboard
|
||||
depends = noto-fonts-emoji
|
||||
source = owlry-plugin-emoji-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-emoji-v1.0.0.tar.gz
|
||||
b2sums = SKIP
|
||||
source = owlry-plugin-emoji-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-emoji-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-emoji
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-emoji
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Emoji picker plugin for Owlry — search and insert emoji characters"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core' 'wl-clipboard' 'noto-fonts-emoji')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-emoji.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-emoji
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-filesearch
|
||||
pkgdesc = File search plugin for Owlry — find files using fd or mlocate
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-filesearch.install
|
||||
@@ -10,7 +10,7 @@ pkgbase = owlry-plugin-filesearch
|
||||
depends = owlry-core
|
||||
optdepends = fd: fast file finding (recommended)
|
||||
optdepends = mlocate: locate-based file search
|
||||
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 = SKIP
|
||||
source = owlry-plugin-filesearch-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-filesearch-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-filesearch
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-filesearch
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="File search plugin for Owlry — find files using fd or mlocate"
|
||||
arch=('x86_64')
|
||||
@@ -14,7 +14,7 @@ optdepends=(
|
||||
'mlocate: locate-based file search'
|
||||
)
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-filesearch
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-media
|
||||
pkgdesc = Media controls plugin for Owlry — control MPRIS-compatible media players
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-media.install
|
||||
@@ -9,7 +9,7 @@ pkgbase = owlry-plugin-media
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
depends = playerctl
|
||||
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 = SKIP
|
||||
source = owlry-plugin-media-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-media-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-media
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-media
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Media controls plugin for Owlry — control MPRIS-compatible media players"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core' 'playerctl')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-media.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-media
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-pomodoro
|
||||
pkgdesc = Pomodoro timer widget for Owlry — track focus and break intervals
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-pomodoro.install
|
||||
@@ -8,7 +8,7 @@ pkgbase = owlry-plugin-pomodoro
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
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 = SKIP
|
||||
source = owlry-plugin-pomodoro-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-pomodoro-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-pomodoro
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-pomodoro
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Pomodoro timer widget for Owlry — track focus and break intervals"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-pomodoro.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-pomodoro
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-scripts
|
||||
pkgdesc = Scripts plugin for Owlry — launch custom scripts from a configured directory
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-scripts.install
|
||||
@@ -8,7 +8,7 @@ pkgbase = owlry-plugin-scripts
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
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 = SKIP
|
||||
source = owlry-plugin-scripts-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-scripts-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-scripts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-scripts
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Scripts plugin for Owlry — launch custom scripts from a configured directory"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-scripts.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-scripts
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-ssh
|
||||
pkgdesc = SSH plugin for Owlry — quickly connect to SSH hosts from config
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-ssh.install
|
||||
@@ -9,7 +9,7 @@ pkgbase = owlry-plugin-ssh
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
depends = openssh
|
||||
source = owlry-plugin-ssh-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-ssh-v1.0.0.tar.gz
|
||||
b2sums = SKIP
|
||||
source = owlry-plugin-ssh-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-ssh-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-ssh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-ssh
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="SSH plugin for Owlry — quickly connect to SSH hosts from config"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core' 'openssh')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-ssh.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-ssh
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
pkgbase = owlry-plugin-system
|
||||
pkgdesc = System actions plugin for Owlry — shutdown, reboot, logout, lock, and suspend
|
||||
pkgdesc = Transitional package — system actions is now built into owlry-core
|
||||
pkgver = 1.0.0
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-system.install
|
||||
arch = x86_64
|
||||
pkgrel = 99
|
||||
url = https://somegit.dev/Owlibou/owlry
|
||||
arch = any
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
depends = systemd
|
||||
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 = SKIP
|
||||
depends = owlry-core>=1.2.0
|
||||
replaces = owlry-plugin-system
|
||||
|
||||
pkgname = owlry-plugin-system
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-system
|
||||
pkgver=1.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="System actions plugin for Owlry — shutdown, reboot, logout, lock, and suspend"
|
||||
arch=('x86_64')
|
||||
url="https://somegit.dev/Owlibou/owlry-plugins"
|
||||
pkgrel=99
|
||||
pkgdesc="Transitional package — system actions is now built into owlry-core"
|
||||
arch=('any')
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('owlry-core' 'systemd')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-system.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
|
||||
_cratename=owlry-plugin-system
|
||||
|
||||
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"
|
||||
}
|
||||
depends=('owlry-core>=1.2.0')
|
||||
replaces=('owlry-plugin-system')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-systemd
|
||||
pkgdesc = Systemd plugin for Owlry — manage systemd user services
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-systemd.install
|
||||
@@ -9,7 +9,7 @@ pkgbase = owlry-plugin-systemd
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
depends = systemd
|
||||
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 = SKIP
|
||||
source = owlry-plugin-systemd-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-systemd-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-systemd
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-systemd
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Systemd plugin for Owlry — manage systemd user services"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core' 'systemd')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-systemd.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-systemd
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-weather
|
||||
pkgdesc = Weather widget for Owlry — display current weather conditions
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.3
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-weather.install
|
||||
@@ -8,7 +8,8 @@ pkgbase = owlry-plugin-weather
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
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 = SKIP
|
||||
depends = openssl
|
||||
source = owlry-plugin-weather-1.0.3.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-weather-v1.0.3.tar.gz
|
||||
b2sums = a0988b7eb5496a1b9f0f5a9936b84990c79736b66da26de06e63d542bf4b0d9e2a382e0257c3237f67fdd41a278d0a8a38e683361f50cb0dcf0c6afe8d6ac7cd
|
||||
|
||||
pkgname = owlry-plugin-weather
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-weather
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.3
|
||||
pkgrel=1
|
||||
pkgdesc="Weather widget for Owlry — display current weather conditions"
|
||||
arch=('x86_64')
|
||||
url="https://somegit.dev/Owlibou/owlry-plugins"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('owlry-core')
|
||||
depends=('owlry-core' 'openssl')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-weather.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('a0988b7eb5496a1b9f0f5a9936b84990c79736b66da26de06e63d542bf4b0d9e2a382e0257c3237f67fdd41a278d0a8a38e683361f50cb0dcf0c6afe8d6ac7cd')
|
||||
|
||||
_cratename=owlry-plugin-weather
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = owlry-plugin-websearch
|
||||
pkgdesc = Web search plugin for Owlry — search DuckDuckGo, Google, and custom engines
|
||||
pkgver = 1.0.0
|
||||
pkgver = 1.0.2
|
||||
pkgrel = 1
|
||||
url = https://somegit.dev/Owlibou/owlry-plugins
|
||||
install = owlry-plugin-websearch.install
|
||||
@@ -8,7 +8,7 @@ pkgbase = owlry-plugin-websearch
|
||||
license = GPL-3.0-or-later
|
||||
makedepends = cargo
|
||||
depends = owlry-core
|
||||
source = owlry-plugin-websearch-1.0.0.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-websearch-v1.0.0.tar.gz
|
||||
b2sums = SKIP
|
||||
source = owlry-plugin-websearch-1.0.2.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/owlry-plugin-websearch-v1.0.2.tar.gz
|
||||
b2sums = ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd
|
||||
|
||||
pkgname = owlry-plugin-websearch
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: vikingowl <christian@nachtigall.dev>
|
||||
pkgname=owlry-plugin-websearch
|
||||
pkgver=1.0.0
|
||||
pkgver=1.0.2
|
||||
pkgrel=1
|
||||
pkgdesc="Web search plugin for Owlry — search DuckDuckGo, Google, and custom engines"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('owlry-core')
|
||||
makedepends=('cargo')
|
||||
install=owlry-plugin-websearch.install
|
||||
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry-plugins/archive/$pkgname-v$pkgver.tar.gz")
|
||||
b2sums=('SKIP')
|
||||
b2sums=('ce86d6ca5cfb8ce6b57bd998fe2e6c242d2e09f147f3d97bd2de5eedbfd4d081f7bb196d930da8d7158a07771b5b3b6e3e29fe79726c248d30c5a494e1bf63dd')
|
||||
|
||||
_cratename=owlry-plugin-websearch
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-bookmarks"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "owlry-plugin-calculator"
|
||||
version = "1.0.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Calculator plugin for owlry - evaluates mathematical expressions"
|
||||
keywords = ["owlry", "plugin", "calculator"]
|
||||
categories = ["mathematics"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"] # Compile as dynamic library (.so)
|
||||
|
||||
[dependencies]
|
||||
# Plugin API for owlry
|
||||
owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" }
|
||||
|
||||
# Math expression evaluation
|
||||
meval = "0.2"
|
||||
|
||||
# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc)
|
||||
abi_stable = "0.11"
|
||||
@@ -1,231 +0,0 @@
|
||||
//! Calculator Plugin for Owlry
|
||||
//!
|
||||
//! A dynamic provider that evaluates mathematical expressions.
|
||||
//! Supports queries prefixed with `=` or `calc `.
|
||||
//!
|
||||
//! Examples:
|
||||
//! - `= 5 + 3` → 8
|
||||
//! - `calc sqrt(16)` → 4
|
||||
//! - `= pi * 2` → 6.283185...
|
||||
|
||||
use abi_stable::std_types::{ROption, RStr, RString, RVec};
|
||||
use owlry_plugin_api::{
|
||||
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
|
||||
ProviderPosition, owlry_plugin,
|
||||
};
|
||||
|
||||
// Plugin metadata
|
||||
const PLUGIN_ID: &str = "calculator";
|
||||
const PLUGIN_NAME: &str = "Calculator";
|
||||
const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const PLUGIN_DESCRIPTION: &str = "Evaluate mathematical expressions";
|
||||
|
||||
// Provider metadata
|
||||
const PROVIDER_ID: &str = "calculator";
|
||||
const PROVIDER_NAME: &str = "Calculator";
|
||||
const PROVIDER_PREFIX: &str = "=";
|
||||
const PROVIDER_ICON: &str = "accessories-calculator";
|
||||
const PROVIDER_TYPE_ID: &str = "calc";
|
||||
|
||||
/// Calculator provider state (empty for now, but could cache results)
|
||||
struct CalculatorState;
|
||||
|
||||
// ============================================================================
|
||||
// Plugin Interface Implementation
|
||||
// ============================================================================
|
||||
|
||||
extern "C" fn plugin_info() -> PluginInfo {
|
||||
PluginInfo {
|
||||
id: RString::from(PLUGIN_ID),
|
||||
name: RString::from(PLUGIN_NAME),
|
||||
version: RString::from(PLUGIN_VERSION),
|
||||
description: RString::from(PLUGIN_DESCRIPTION),
|
||||
api_version: API_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
|
||||
vec![ProviderInfo {
|
||||
id: RString::from(PROVIDER_ID),
|
||||
name: RString::from(PROVIDER_NAME),
|
||||
prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)),
|
||||
icon: RString::from(PROVIDER_ICON),
|
||||
provider_type: ProviderKind::Dynamic,
|
||||
type_id: RString::from(PROVIDER_TYPE_ID),
|
||||
position: ProviderPosition::Normal,
|
||||
priority: 10000, // Dynamic: calculator results first
|
||||
}]
|
||||
.into()
|
||||
}
|
||||
|
||||
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
|
||||
// Create state and return handle
|
||||
let state = Box::new(CalculatorState);
|
||||
ProviderHandle::from_box(state)
|
||||
}
|
||||
|
||||
extern "C" fn provider_refresh(_handle: ProviderHandle) -> RVec<PluginItem> {
|
||||
// Dynamic provider - refresh does nothing
|
||||
RVec::new()
|
||||
}
|
||||
|
||||
extern "C" fn provider_query(_handle: ProviderHandle, query: RStr<'_>) -> RVec<PluginItem> {
|
||||
let query_str = query.as_str();
|
||||
|
||||
// Extract expression from query
|
||||
let expr = match extract_expression(query_str) {
|
||||
Some(e) if !e.is_empty() => e,
|
||||
_ => return RVec::new(),
|
||||
};
|
||||
|
||||
// Evaluate the expression
|
||||
match evaluate_expression(expr) {
|
||||
Some(item) => vec![item].into(),
|
||||
None => RVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn provider_drop(handle: ProviderHandle) {
|
||||
if !handle.ptr.is_null() {
|
||||
// SAFETY: We created this handle from Box<CalculatorState>
|
||||
unsafe {
|
||||
handle.drop_as::<CalculatorState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the plugin vtable
|
||||
owlry_plugin! {
|
||||
info: plugin_info,
|
||||
providers: plugin_providers,
|
||||
init: provider_init,
|
||||
refresh: provider_refresh,
|
||||
query: provider_query,
|
||||
drop: provider_drop,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Calculator Logic
|
||||
// ============================================================================
|
||||
|
||||
/// Extract expression from query (handles `= expr` and `calc expr` formats)
|
||||
fn extract_expression(query: &str) -> Option<&str> {
|
||||
let trimmed = query.trim();
|
||||
|
||||
// Support both "= expr" and "=expr" (with or without space)
|
||||
if let Some(expr) = trimmed.strip_prefix("= ") {
|
||||
Some(expr.trim())
|
||||
} else if let Some(expr) = trimmed.strip_prefix('=') {
|
||||
Some(expr.trim())
|
||||
} else if let Some(expr) = trimmed.strip_prefix("calc ") {
|
||||
Some(expr.trim())
|
||||
} else {
|
||||
// For filter mode - accept raw expressions
|
||||
Some(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a mathematical expression and return a PluginItem
|
||||
fn evaluate_expression(expr: &str) -> Option<PluginItem> {
|
||||
match meval::eval_str(expr) {
|
||||
Ok(result) => {
|
||||
// Format result nicely
|
||||
let result_str = format_result(result);
|
||||
|
||||
Some(
|
||||
PluginItem::new(
|
||||
format!("calc:{}", expr),
|
||||
result_str.clone(),
|
||||
format!("printf '%s' '{}' | wl-copy", result_str.replace('\'', "'\\''")),
|
||||
)
|
||||
.with_description(format!("= {}", expr))
|
||||
.with_icon(PROVIDER_ICON)
|
||||
.with_keywords(vec!["math".to_string(), "calculator".to_string()]),
|
||||
)
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a numeric result nicely
|
||||
fn format_result(result: f64) -> String {
|
||||
if result.fract() == 0.0 && result.abs() < 1e15 {
|
||||
// Integer result
|
||||
format!("{}", result as i64)
|
||||
} else {
|
||||
// Float result with reasonable precision, trimming trailing zeros
|
||||
let formatted = format!("{:.10}", result);
|
||||
formatted
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.')
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_expression() {
|
||||
assert_eq!(extract_expression("= 5+3"), Some("5+3"));
|
||||
assert_eq!(extract_expression("=5+3"), Some("5+3"));
|
||||
assert_eq!(extract_expression("calc 5+3"), Some("5+3"));
|
||||
assert_eq!(extract_expression(" = 5 + 3 "), Some("5 + 3"));
|
||||
assert_eq!(extract_expression("5+3"), Some("5+3")); // Raw expression
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_result() {
|
||||
assert_eq!(format_result(8.0), "8");
|
||||
assert_eq!(format_result(2.5), "2.5");
|
||||
assert_eq!(format_result(3.14159265358979), "3.1415926536");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_basic() {
|
||||
let item = evaluate_expression("5+3").unwrap();
|
||||
assert_eq!(item.name.as_str(), "8");
|
||||
|
||||
let item = evaluate_expression("10 * 2").unwrap();
|
||||
assert_eq!(item.name.as_str(), "20");
|
||||
|
||||
let item = evaluate_expression("15 / 3").unwrap();
|
||||
assert_eq!(item.name.as_str(), "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_float() {
|
||||
let item = evaluate_expression("5/2").unwrap();
|
||||
assert_eq!(item.name.as_str(), "2.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_functions() {
|
||||
let item = evaluate_expression("sqrt(16)").unwrap();
|
||||
assert_eq!(item.name.as_str(), "4");
|
||||
|
||||
let item = evaluate_expression("abs(-5)").unwrap();
|
||||
assert_eq!(item.name.as_str(), "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_constants() {
|
||||
let item = evaluate_expression("pi").unwrap();
|
||||
assert!(item.name.as_str().starts_with("3.14159"));
|
||||
|
||||
let item = evaluate_expression("e").unwrap();
|
||||
assert!(item.name.as_str().starts_with("2.718"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_evaluate_invalid() {
|
||||
assert!(evaluate_expression("").is_none());
|
||||
assert!(evaluate_expression("invalid").is_none());
|
||||
assert!(evaluate_expression("5 +").is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-clipboard"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "owlry-plugin-converter"
|
||||
version = "1.0.1"
|
||||
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"
|
||||
@@ -1,313 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
//! 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";
|
||||
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,
|
||||
r.target_symbol,
|
||||
))
|
||||
.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());
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -1,905 +0,0 @@
|
||||
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> {
|
||||
let (_, from_def) = lookup_unit(from)?;
|
||||
let (_, to_def) = lookup_unit(to)?;
|
||||
|
||||
// Currency needs special handling
|
||||
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> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-emoji"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-filesearch"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-media"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-pomodoro"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-scripts"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-ssh"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "owlry-plugin-system"
|
||||
version = "1.0.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "System plugin for owlry - power and session management commands"
|
||||
keywords = ["owlry", "plugin", "system", "power"]
|
||||
categories = ["os"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"] # Compile as dynamic library (.so)
|
||||
|
||||
[dependencies]
|
||||
# Plugin API for owlry
|
||||
owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" }
|
||||
|
||||
# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc)
|
||||
abi_stable = "0.11"
|
||||
@@ -1,257 +0,0 @@
|
||||
//! System Plugin for Owlry
|
||||
//!
|
||||
//! A static provider that provides system power and session management commands.
|
||||
//!
|
||||
//! Commands:
|
||||
//! - Shutdown - Power off the system
|
||||
//! - Reboot - Restart the system
|
||||
//! - Reboot into BIOS - Restart into UEFI/BIOS setup
|
||||
//! - Suspend - Suspend to RAM
|
||||
//! - Hibernate - Suspend to disk
|
||||
//! - Lock Screen - Lock the session
|
||||
//! - Log Out - End the current session
|
||||
|
||||
use abi_stable::std_types::{ROption, RStr, RString, RVec};
|
||||
use owlry_plugin_api::{
|
||||
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
|
||||
ProviderPosition, owlry_plugin,
|
||||
};
|
||||
|
||||
// Plugin metadata
|
||||
const PLUGIN_ID: &str = "system";
|
||||
const PLUGIN_NAME: &str = "System";
|
||||
const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const PLUGIN_DESCRIPTION: &str = "Power and session management commands";
|
||||
|
||||
// Provider metadata
|
||||
const PROVIDER_ID: &str = "system";
|
||||
const PROVIDER_NAME: &str = "System";
|
||||
const PROVIDER_PREFIX: &str = ":sys";
|
||||
const PROVIDER_ICON: &str = "system-shutdown";
|
||||
const PROVIDER_TYPE_ID: &str = "system";
|
||||
|
||||
/// System provider state - holds cached items
|
||||
struct SystemState {
|
||||
items: Vec<PluginItem>,
|
||||
}
|
||||
|
||||
impl SystemState {
|
||||
fn new() -> Self {
|
||||
Self { items: Vec::new() }
|
||||
}
|
||||
|
||||
fn load_commands(&mut self) {
|
||||
self.items.clear();
|
||||
|
||||
// Define system commands
|
||||
// Format: (id, name, description, icon, command)
|
||||
let commands: &[(&str, &str, &str, &str, &str)] = &[
|
||||
(
|
||||
"system:shutdown",
|
||||
"Shutdown",
|
||||
"Power off the system",
|
||||
"system-shutdown",
|
||||
"systemctl poweroff",
|
||||
),
|
||||
(
|
||||
"system:reboot",
|
||||
"Reboot",
|
||||
"Restart the system",
|
||||
"system-reboot",
|
||||
"systemctl reboot",
|
||||
),
|
||||
(
|
||||
"system:reboot-bios",
|
||||
"Reboot into BIOS",
|
||||
"Restart into UEFI/BIOS setup",
|
||||
"system-reboot",
|
||||
"systemctl reboot --firmware-setup",
|
||||
),
|
||||
(
|
||||
"system:suspend",
|
||||
"Suspend",
|
||||
"Suspend to RAM",
|
||||
"system-suspend",
|
||||
"systemctl suspend",
|
||||
),
|
||||
(
|
||||
"system:hibernate",
|
||||
"Hibernate",
|
||||
"Suspend to disk",
|
||||
"system-suspend-hibernate",
|
||||
"systemctl hibernate",
|
||||
),
|
||||
(
|
||||
"system:lock",
|
||||
"Lock Screen",
|
||||
"Lock the session",
|
||||
"system-lock-screen",
|
||||
"loginctl lock-session",
|
||||
),
|
||||
(
|
||||
"system:logout",
|
||||
"Log Out",
|
||||
"End the current session",
|
||||
"system-log-out",
|
||||
"loginctl terminate-session self",
|
||||
),
|
||||
];
|
||||
|
||||
for (id, name, description, icon, command) in commands {
|
||||
self.items.push(
|
||||
PluginItem::new(*id, *name, *command)
|
||||
.with_description(*description)
|
||||
.with_icon(*icon)
|
||||
.with_keywords(vec!["power".to_string(), "system".to_string()]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Plugin Interface Implementation
|
||||
// ============================================================================
|
||||
|
||||
extern "C" fn plugin_info() -> PluginInfo {
|
||||
PluginInfo {
|
||||
id: RString::from(PLUGIN_ID),
|
||||
name: RString::from(PLUGIN_NAME),
|
||||
version: RString::from(PLUGIN_VERSION),
|
||||
description: RString::from(PLUGIN_DESCRIPTION),
|
||||
api_version: API_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
|
||||
vec![ProviderInfo {
|
||||
id: RString::from(PROVIDER_ID),
|
||||
name: RString::from(PROVIDER_NAME),
|
||||
prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)),
|
||||
icon: RString::from(PROVIDER_ICON),
|
||||
provider_type: ProviderKind::Static,
|
||||
type_id: RString::from(PROVIDER_TYPE_ID),
|
||||
position: ProviderPosition::Normal,
|
||||
priority: 0, // Static: use frecency ordering
|
||||
}]
|
||||
.into()
|
||||
}
|
||||
|
||||
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
|
||||
let state = Box::new(SystemState::new());
|
||||
ProviderHandle::from_box(state)
|
||||
}
|
||||
|
||||
extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec<PluginItem> {
|
||||
if handle.ptr.is_null() {
|
||||
return RVec::new();
|
||||
}
|
||||
|
||||
// SAFETY: We created this handle from Box<SystemState>
|
||||
let state = unsafe { &mut *(handle.ptr as *mut SystemState) };
|
||||
|
||||
// Load/reload commands
|
||||
state.load_commands();
|
||||
|
||||
// Return items
|
||||
state.items.to_vec().into()
|
||||
}
|
||||
|
||||
extern "C" fn provider_query(_handle: ProviderHandle, _query: RStr<'_>) -> RVec<PluginItem> {
|
||||
// Static provider - query is handled by the core using cached items
|
||||
RVec::new()
|
||||
}
|
||||
|
||||
extern "C" fn provider_drop(handle: ProviderHandle) {
|
||||
if !handle.ptr.is_null() {
|
||||
// SAFETY: We created this handle from Box<SystemState>
|
||||
unsafe {
|
||||
handle.drop_as::<SystemState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the plugin vtable
|
||||
owlry_plugin! {
|
||||
info: plugin_info,
|
||||
providers: plugin_providers,
|
||||
init: provider_init,
|
||||
refresh: provider_refresh,
|
||||
query: provider_query,
|
||||
drop: provider_drop,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_system_state_new() {
|
||||
let state = SystemState::new();
|
||||
assert!(state.items.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_commands_loaded() {
|
||||
let mut state = SystemState::new();
|
||||
state.load_commands();
|
||||
|
||||
assert!(state.items.len() >= 6);
|
||||
|
||||
// Check for specific commands
|
||||
let names: Vec<&str> = state.items.iter().map(|i| i.name.as_str()).collect();
|
||||
assert!(names.contains(&"Shutdown"));
|
||||
assert!(names.contains(&"Reboot"));
|
||||
assert!(names.contains(&"Suspend"));
|
||||
assert!(names.contains(&"Lock Screen"));
|
||||
assert!(names.contains(&"Log Out"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reboot_bios_command() {
|
||||
let mut state = SystemState::new();
|
||||
state.load_commands();
|
||||
|
||||
let bios_cmd = state
|
||||
.items
|
||||
.iter()
|
||||
.find(|i| i.name.as_str() == "Reboot into BIOS")
|
||||
.expect("Reboot into BIOS should exist");
|
||||
|
||||
assert_eq!(
|
||||
bios_cmd.command.as_str(),
|
||||
"systemctl reboot --firmware-setup"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_commands_have_icons() {
|
||||
let mut state = SystemState::new();
|
||||
state.load_commands();
|
||||
|
||||
for item in &state.items {
|
||||
assert!(
|
||||
item.icon.is_some(),
|
||||
"Item '{}' should have an icon",
|
||||
item.name.as_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_commands_have_descriptions() {
|
||||
let mut state = SystemState::new();
|
||||
state.load_commands();
|
||||
|
||||
for item in &state.items {
|
||||
assert!(
|
||||
item.description.is_some(),
|
||||
"Item '{}' should have a description",
|
||||
item.name.as_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-systemd"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-weather"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
@@ -20,7 +20,7 @@ owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugi
|
||||
abi_stable = "0.11"
|
||||
|
||||
# HTTP client for weather API requests
|
||||
reqwest = { version = "0.13", features = ["blocking", "json"] }
|
||||
reqwest = { version = "0.13", default-features = false, features = ["native-tls", "blocking", "json"] }
|
||||
|
||||
# JSON parsing for API responses
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-websearch"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
263
justfile
263
justfile
@@ -1,12 +1,24 @@
|
||||
# Owlry Plugins build and release automation
|
||||
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# === Build ===
|
||||
|
||||
build:
|
||||
cargo build --workspace
|
||||
|
||||
release:
|
||||
cargo build --workspace --release
|
||||
|
||||
plugin name:
|
||||
cargo build -p owlry-plugin-{{name}} --release
|
||||
|
||||
plugins:
|
||||
cargo build --workspace --release
|
||||
|
||||
# === Quality ===
|
||||
|
||||
check:
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace
|
||||
@@ -17,29 +29,7 @@ test:
|
||||
fmt:
|
||||
cargo fmt --all
|
||||
|
||||
plugin name:
|
||||
cargo build -p owlry-plugin-{{name}} --release
|
||||
|
||||
plugins:
|
||||
cargo build --workspace --release
|
||||
|
||||
show-versions:
|
||||
@for dir in crates/owlry-plugin-*; do \
|
||||
name=$$(basename $$dir); \
|
||||
version=$$(grep '^version' $$dir/Cargo.toml | head -1 | cut -d'"' -f2); \
|
||||
printf "%-35s %s\n" "$$name" "$$version"; \
|
||||
done
|
||||
|
||||
bump-crate crate new_version:
|
||||
@cd crates/{{crate}} && \
|
||||
sed -i 's/^version = ".*"/version = "{{new_version}}"/' Cargo.toml
|
||||
@echo "Bumped {{crate}} to {{new_version}}"
|
||||
|
||||
bump-all new_version:
|
||||
@for dir in crates/owlry-plugin-*; do \
|
||||
sed -i 's/^version = ".*"/version = "{{new_version}}"/' $$dir/Cargo.toml; \
|
||||
done
|
||||
@echo "Bumped all crates to {{new_version}}"
|
||||
# === Install ===
|
||||
|
||||
install-local:
|
||||
just plugins
|
||||
@@ -47,3 +37,230 @@ install-local:
|
||||
sudo install -Dm755 "$$f" /usr/lib/owlry/plugins/$$(basename "$$f"); \
|
||||
done
|
||||
@echo "Installed all plugins"
|
||||
|
||||
# === Version Management ===
|
||||
|
||||
show-versions:
|
||||
#!/usr/bin/env bash
|
||||
for dir in crates/owlry-plugin-*; do
|
||||
name=$(basename "$dir")
|
||||
ver=$(grep '^version' "$dir/Cargo.toml" | head -1 | cut -d'"' -f2)
|
||||
printf " %-35s %s\n" "$name" "$ver"
|
||||
done
|
||||
|
||||
# Bump a single plugin version, update Cargo.lock, commit
|
||||
bump-crate crate new_version:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
toml="crates/{{crate}}/Cargo.toml"
|
||||
[ -f "$toml" ] || { echo "Error: $toml not found"; exit 1; }
|
||||
|
||||
old=$(grep '^version' "$toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||||
[ "$old" = "{{new_version}}" ] && { echo "{{crate}} already at {{new_version}}"; exit 0; }
|
||||
|
||||
echo "Bumping {{crate}} from $old to {{new_version}}"
|
||||
sed -i 's/^version = ".*"/version = "{{new_version}}"/' "$toml"
|
||||
cargo check -p {{crate}}
|
||||
git add "$toml" Cargo.lock
|
||||
git commit -m "chore({{crate}}): bump version to {{new_version}}"
|
||||
echo "{{crate}} bumped to {{new_version}}"
|
||||
|
||||
# Bump all plugin crates to same version
|
||||
bump-all new_version:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for dir in crates/owlry-plugin-*; do
|
||||
crate=$(basename "$dir")
|
||||
old=$(grep '^version' "$dir/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||||
[ "$old" = "{{new_version}}" ] && continue
|
||||
echo "Bumping $crate from $old to {{new_version}}"
|
||||
sed -i 's/^version = ".*"/version = "{{new_version}}"/' "$dir/Cargo.toml"
|
||||
done
|
||||
cargo check --workspace
|
||||
git add crates/*/Cargo.toml Cargo.lock
|
||||
git commit -m "chore: bump all plugins to {{new_version}}"
|
||||
echo "All plugins bumped to {{new_version}}"
|
||||
|
||||
# === Tagging ===
|
||||
|
||||
# Tag a specific plugin (format: {crate}-v{version})
|
||||
tag-crate crate:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ver=$(grep '^version' "crates/{{crate}}/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||||
tag="{{crate}}-v$ver"
|
||||
if git rev-parse "$tag" >/dev/null 2>&1; then
|
||||
echo "Tag $tag already exists"
|
||||
exit 0
|
||||
fi
|
||||
git tag -a "$tag" -m "{{crate}} v$ver"
|
||||
echo "Created tag $tag"
|
||||
|
||||
push-tags:
|
||||
git push --tags
|
||||
|
||||
# === AUR Package Management ===
|
||||
|
||||
# Stage AUR files into git index (handles embedded .git dirs)
|
||||
aur-stage pkg:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
dir="aur/{{pkg}}"
|
||||
[ -d "$dir" ] || { echo "Error: $dir not found"; exit 1; }
|
||||
|
||||
# Build list of files to stage
|
||||
files=("$dir/PKGBUILD" "$dir/.SRCINFO")
|
||||
for f in "$dir"/*.install; do
|
||||
[ -f "$f" ] && files+=("$f")
|
||||
done
|
||||
|
||||
if [ -d "$dir/.git" ]; then
|
||||
mv "$dir/.git" "$dir/.git.bak"
|
||||
git add "${files[@]}"
|
||||
mv "$dir/.git.bak" "$dir/.git"
|
||||
else
|
||||
git add "${files[@]}"
|
||||
fi
|
||||
|
||||
# Update a specific plugin AUR package PKGBUILD
|
||||
aur-update-pkg pkg:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
aur_dir="aur/{{pkg}}"
|
||||
[ -d "$aur_dir" ] || { echo "Error: $aur_dir not found"; exit 1; }
|
||||
|
||||
crate_dir="crates/{{pkg}}"
|
||||
[ -d "$crate_dir" ] || { echo "Error: $crate_dir not found"; exit 1; }
|
||||
|
||||
ver=$(grep '^version' "$crate_dir/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||||
tag="{{pkg}}-v$ver"
|
||||
url="https://somegit.dev/Owlibou/owlry-plugins/archive/$tag.tar.gz"
|
||||
|
||||
echo "Updating {{pkg}} to $ver (tag: $tag)"
|
||||
sed -i "s/^pkgver=.*/pkgver=$ver/" "$aur_dir/PKGBUILD"
|
||||
sed -i 's/^pkgrel=.*/pkgrel=1/' "$aur_dir/PKGBUILD"
|
||||
|
||||
echo "Downloading tarball and computing checksum..."
|
||||
hash=$(curl -sL "$url" | b2sum | cut -d' ' -f1)
|
||||
if [ -z "$hash" ] || [ ${#hash} -lt 64 ]; then
|
||||
echo "Error: failed to download or hash $url"
|
||||
exit 1
|
||||
fi
|
||||
sed -i "s|^b2sums=.*|b2sums=('$hash')|" "$aur_dir/PKGBUILD"
|
||||
|
||||
(cd "$aur_dir" && makepkg --printsrcinfo > .SRCINFO)
|
||||
echo "{{pkg}} PKGBUILD updated to $ver"
|
||||
|
||||
# Publish a specific plugin to aur.archlinux.org
|
||||
aur-publish-pkg pkg:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
aur_dir="aur/{{pkg}}"
|
||||
[ -d "$aur_dir/.git" ] || { echo "Error: $aur_dir has no AUR git repo"; exit 1; }
|
||||
|
||||
cd "$aur_dir"
|
||||
ver=$(grep '^pkgver=' PKGBUILD | sed 's/pkgver=//')
|
||||
git add -A
|
||||
git commit -m "Update to v$ver" || { echo "Nothing to commit"; exit 0; }
|
||||
git push origin master
|
||||
echo "{{pkg}} v$ver published to AUR!"
|
||||
|
||||
# Update ALL plugin AUR packages
|
||||
aur-update-all:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for dir in aur/owlry-plugin-*/; do
|
||||
pkg=$(basename "$dir")
|
||||
[ -f "$dir/PKGBUILD" ] || continue
|
||||
echo "=== $pkg ==="
|
||||
just aur-update-pkg "$pkg"
|
||||
echo ""
|
||||
done
|
||||
echo "All updated. Run 'just aur-publish-all' to publish."
|
||||
|
||||
# Publish ALL plugin AUR packages
|
||||
aur-publish-all:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for dir in aur/owlry-plugin-*/; do
|
||||
pkg=$(basename "$dir")
|
||||
[ -d "$dir/.git" ] || continue
|
||||
echo "=== $pkg ==="
|
||||
just aur-publish-pkg "$pkg"
|
||||
echo ""
|
||||
done
|
||||
echo "All published!"
|
||||
|
||||
# Commit AUR file changes to the plugins repo
|
||||
aur-commit msg="chore(aur): update PKGBUILDs":
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for dir in aur/*/; do
|
||||
pkg=$(basename "$dir")
|
||||
[ -f "$dir/PKGBUILD" ] || continue
|
||||
just aur-stage "$pkg"
|
||||
done
|
||||
git diff --cached --quiet && { echo "No AUR changes to commit"; exit 0; }
|
||||
git commit -m "{{msg}}"
|
||||
|
||||
# Show AUR package status
|
||||
aur-status:
|
||||
#!/usr/bin/env bash
|
||||
echo "=== AUR Package Status ==="
|
||||
for dir in aur/*/; do
|
||||
pkg=$(basename "$dir")
|
||||
[ -f "$dir/PKGBUILD" ] || continue
|
||||
ver=$(grep '^pkgver=' "$dir/PKGBUILD" | sed 's/pkgver=//')
|
||||
if [ -d "$dir/.git" ]; then
|
||||
printf " ✓ %-35s %s\n" "$pkg" "$ver"
|
||||
else
|
||||
printf " ✗ %-35s %s (no AUR repo)\n" "$pkg" "$ver"
|
||||
fi
|
||||
done
|
||||
|
||||
# === Release Workflows ===
|
||||
|
||||
# Release a single plugin: bump → push → tag → update AUR → publish AUR
|
||||
release-plugin crate new_version:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
just bump-crate {{crate}} {{new_version}}
|
||||
git push
|
||||
|
||||
just tag-crate {{crate}}
|
||||
just push-tags
|
||||
|
||||
echo "Waiting for tag to propagate..."
|
||||
sleep 3
|
||||
|
||||
just aur-update-pkg {{crate}}
|
||||
just aur-commit "chore(aur): update {{crate}} to {{new_version}}"
|
||||
git push
|
||||
|
||||
just aur-publish-pkg {{crate}}
|
||||
echo ""
|
||||
echo "{{crate}} v{{new_version}} released and published to AUR!"
|
||||
|
||||
# === Testing ===
|
||||
|
||||
# Quick local build test (no chroot, uses host deps)
|
||||
aur-test-pkg pkg:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "aur/{{pkg}}"
|
||||
echo "Testing {{pkg}} PKGBUILD..."
|
||||
makepkg -sf
|
||||
echo "Package built successfully!"
|
||||
ls -lh *.pkg.tar.zst
|
||||
|
||||
# Build AUR packages from the local working tree in a clean chroot.
|
||||
# Packages current source (incl. uncommitted changes), patches PKGBUILD,
|
||||
# builds in dep order, injects local artifacts, restores PKGBUILD on exit.
|
||||
# owlry-core is not in the official repos — inject it with -I:
|
||||
#
|
||||
# Examples:
|
||||
# just aur-local-test -I ../owlry/aur/owlry-core/owlry-core-*.pkg.tar.zst owlry-plugin-weather
|
||||
# just aur-local-test -I ../owlry/aur/owlry-core/owlry-core-*.pkg.tar.zst --all
|
||||
aur-local-test *args:
|
||||
scripts/aur-local-test {{args}}
|
||||
|
||||
329
scripts/aur-local-test
Executable file
329
scripts/aur-local-test
Executable file
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/aur-local-test
|
||||
#
|
||||
# Build AUR packages from the local working tree in a clean extra chroot.
|
||||
#
|
||||
# Packages the current working tree (including uncommitted changes) into a
|
||||
# tarball, temporarily patches each PKGBUILD to use it, runs
|
||||
# extra-x86_64-build, then restores the PKGBUILD on exit regardless of
|
||||
# success or failure.
|
||||
#
|
||||
# Packages with local AUR deps (e.g. owlry-rune depends on owlry-core) are
|
||||
# built in topological order and their artifacts injected automatically.
|
||||
#
|
||||
# Usage: scripts/aur-local-test [OPTIONS] [PKG...]
|
||||
# See --help for details.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
|
||||
REPO_NAME="$(basename "$REPO_ROOT")"
|
||||
AUR_DIR="$REPO_ROOT/aur"
|
||||
|
||||
# State tracked for cleanup
|
||||
TMP_TARBALL=""
|
||||
declare -a PKGBUILD_BACKUPS=()
|
||||
declare -a PLACED_FILES=()
|
||||
|
||||
# Build config
|
||||
RESET_CHROOT=0
|
||||
declare -a INPUT_PKGS=()
|
||||
declare -a EXTRA_INJECT=() # --inject paths (external AUR deps)
|
||||
|
||||
# ─── Output helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
die() { echo "error: $*" >&2; exit 1; }
|
||||
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
|
||||
ok() { printf '\033[1;32m ->\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m !\033[0m %s\n' "$*" >&2; }
|
||||
fail() { printf '\033[1;31mFAIL\033[0m %s\n' "$*" >&2; }
|
||||
|
||||
# ─── Cleanup ─────────────────────────────────────────────────────────────────
|
||||
|
||||
cleanup() {
|
||||
local code=$?
|
||||
local f pkgbuild
|
||||
|
||||
# Remove tarballs placed in aur/ dirs
|
||||
for f in "${PLACED_FILES[@]+"${PLACED_FILES[@]}"}"; do
|
||||
[[ -f "$f" ]] && rm -f "$f"
|
||||
done
|
||||
|
||||
# Restore patched PKGBUILDs from backups
|
||||
for f in "${PKGBUILD_BACKUPS[@]+"${PKGBUILD_BACKUPS[@]}"}"; do
|
||||
pkgbuild="${f%.bak}"
|
||||
[[ -f "$f" ]] && mv "$f" "$pkgbuild"
|
||||
done
|
||||
|
||||
[[ -n "$TMP_TARBALL" && -f "$TMP_TARBALL" ]] && rm -f "$TMP_TARBALL"
|
||||
|
||||
exit "$code"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# ─── Usage ───────────────────────────────────────────────────────────────────
|
||||
|
||||
usage() {
|
||||
cat >&2 <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS] [PKG...]
|
||||
|
||||
Build AUR packages from the local working tree in a clean chroot.
|
||||
Packages current working tree (incl. uncommitted changes), patches PKGBUILD
|
||||
source + checksum, runs extra-x86_64-build, then restores on exit.
|
||||
|
||||
Packages with local AUR deps are built in topological order and their
|
||||
.pkg.tar.zst artifacts are injected into dependent builds automatically.
|
||||
|
||||
OPTIONS
|
||||
-c, --reset Reset chroot matrix (passes -c to extra-x86_64-build).
|
||||
Only applied to the first package; subsequent builds
|
||||
reuse the already-fresh chroot.
|
||||
-a, --all Build all packages in aur/ (respects dep order).
|
||||
-I, --inject FILE Inject FILE (.pkg.tar.zst) into the chroot before
|
||||
building. For AUR deps not in the official repos
|
||||
(e.g. owlry-core when testing owlry-plugins).
|
||||
Can be repeated.
|
||||
-h, --help Show this help.
|
||||
|
||||
EXAMPLES
|
||||
# Single package
|
||||
$(basename "$0") owlry-core
|
||||
|
||||
# Multiple packages with chroot reset
|
||||
$(basename "$0") -c owlry-core owlry-rune
|
||||
|
||||
# All packages in dependency order
|
||||
$(basename "$0") --all --reset
|
||||
|
||||
# owlry-plugins: inject owlry-core from sibling repo
|
||||
$(basename "$0") -I ../owlry/aur/owlry-core/owlry-core-*.pkg.tar.zst --all
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Argument parsing ────────────────────────────────────────────────────────
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-c|--reset)
|
||||
RESET_CHROOT=1
|
||||
shift ;;
|
||||
-a|--all)
|
||||
for dir in "$AUR_DIR"/*/; do
|
||||
pkg=$(basename "$dir")
|
||||
[[ -f "$dir/PKGBUILD" ]] && INPUT_PKGS+=("$pkg")
|
||||
done
|
||||
shift ;;
|
||||
-I|--inject)
|
||||
[[ $# -ge 2 ]] || die "--inject requires an argument"
|
||||
[[ -f "$2" ]] || die "inject file not found: $2"
|
||||
EXTRA_INJECT+=("$(realpath "$2")")
|
||||
shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) die "unknown option: $1" ;;
|
||||
*)
|
||||
if [[ "$1" == *.pkg.tar.zst ]]; then
|
||||
[[ -f "$1" ]] || die "inject file not found: $1"
|
||||
EXTRA_INJECT+=("$(realpath "$1")")
|
||||
else
|
||||
INPUT_PKGS+=("$1")
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ ${#INPUT_PKGS[@]} -eq 0 ]] && usage
|
||||
|
||||
# ─── Dependency resolution ───────────────────────────────────────────────────
|
||||
|
||||
# Return the names of local AUR packages that PKG depends on.
|
||||
local_deps_of() {
|
||||
local pkg="$1"
|
||||
local pkgbuild="$AUR_DIR/$pkg/PKGBUILD"
|
||||
[[ -f "$pkgbuild" ]] || return 0
|
||||
|
||||
local dep_line bare
|
||||
dep_line=$(grep '^depends=' "$pkgbuild" 2>/dev/null | head -1 || true)
|
||||
[[ -z "$dep_line" ]] && return 0
|
||||
|
||||
# Strip depends=, parens, and quotes; split on whitespace
|
||||
echo "$dep_line" \
|
||||
| sed "s/^depends=//; s/[()\"']/ /g" \
|
||||
| tr ' ' '\n' \
|
||||
| while IFS= read -r dep; do
|
||||
[[ -z "$dep" ]] && continue
|
||||
bare="${dep%%[><=]*}" # strip version constraints
|
||||
[[ -d "$AUR_DIR/$bare" ]] && echo "$bare"
|
||||
done
|
||||
}
|
||||
|
||||
# Topological sort (DFS) — deps before dependents.
|
||||
declare -A TOPO_VISITED=()
|
||||
declare -a TOPO_ORDER=()
|
||||
|
||||
topo_visit() {
|
||||
local pkg="$1"
|
||||
[[ -v "TOPO_VISITED[$pkg]" ]] && return 0
|
||||
TOPO_VISITED[$pkg]=1
|
||||
local dep
|
||||
while IFS= read -r dep; do
|
||||
topo_visit "$dep"
|
||||
done < <(local_deps_of "$pkg")
|
||||
TOPO_ORDER+=("$pkg")
|
||||
}
|
||||
|
||||
resolve_order() {
|
||||
TOPO_VISITED=()
|
||||
TOPO_ORDER=()
|
||||
local pkg
|
||||
for pkg in "$@"; do
|
||||
topo_visit "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Tarball creation ────────────────────────────────────────────────────────
|
||||
|
||||
make_tarball() {
|
||||
TMP_TARBALL=$(mktemp /tmp/aur-local-XXXXXX.tar.gz)
|
||||
info "Packaging ${REPO_NAME} working tree (incl. uncommitted changes)..."
|
||||
tar czf "$TMP_TARBALL" \
|
||||
--exclude='.git' \
|
||||
--exclude='target' \
|
||||
--transform "s|^\.|${REPO_NAME}|" \
|
||||
-C "$REPO_ROOT" .
|
||||
ok "Tarball ready: $(du -b "$TMP_TARBALL" | cut -f1 | numfmt --to=iec 2>/dev/null || wc -c < "$TMP_TARBALL") bytes"
|
||||
}
|
||||
|
||||
# ─── PKGBUILD patching ───────────────────────────────────────────────────────
|
||||
|
||||
# Patch a package's PKGBUILD to use the local tarball.
|
||||
# Backs up the original; cleanup() restores it on exit.
|
||||
patch_pkgbuild() {
|
||||
local pkg="$1"
|
||||
local pkgbuild="$AUR_DIR/$pkg/PKGBUILD"
|
||||
local pkgdir="$AUR_DIR/$pkg"
|
||||
|
||||
# Skip packages with no remote source (meta/group packages)
|
||||
if ! grep -q '^source=' "$pkgbuild" || grep -qE '^source=\(\s*\)' "$pkgbuild"; then
|
||||
ok "No source URL to patch — skipping tarball injection for $pkg"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local pkgname pkgver filename hash
|
||||
pkgname=$(grep '^pkgname=' "$pkgbuild" | cut -d= -f2- | tr -d "\"'")
|
||||
pkgver=$(grep '^pkgver=' "$pkgbuild" | cut -d= -f2- | tr -d "\"'")
|
||||
filename="${pkgname}-${pkgver}.tar.gz"
|
||||
hash=$(b2sum "$TMP_TARBALL" | cut -d' ' -f1)
|
||||
|
||||
# Backup original PKGBUILD
|
||||
cp "$pkgbuild" "${pkgbuild}.bak"
|
||||
PKGBUILD_BACKUPS+=("${pkgbuild}.bak")
|
||||
|
||||
# Place local tarball where makepkg looks for it
|
||||
cp "$TMP_TARBALL" "$pkgdir/$filename"
|
||||
PLACED_FILES+=("$pkgdir/$filename")
|
||||
|
||||
# Patch source and checksum lines in-place
|
||||
sed -i "s|^source=.*|source=(\"${filename}\")|" "$pkgbuild"
|
||||
sed -i "s|^b2sums=.*|b2sums=('${hash}')|" "$pkgbuild"
|
||||
|
||||
ok "Patched PKGBUILD: source=${filename}, b2sum=${hash:0:12}…"
|
||||
}
|
||||
|
||||
# ─── Build ───────────────────────────────────────────────────────────────────
|
||||
|
||||
# built_artifacts[pkg] = path to the .pkg.tar.zst produced in this run.
|
||||
# Used to inject pkg artifacts into dependent builds.
|
||||
declare -A BUILT_ARTIFACTS=()
|
||||
|
||||
find_artifact() {
|
||||
local pkg="$1"
|
||||
local pkgver
|
||||
# pkgver is the same in patched and original PKGBUILD
|
||||
pkgver=$(grep '^pkgver=' "$AUR_DIR/$pkg/PKGBUILD" | cut -d= -f2- | tr -d "\"'" \
|
||||
|| grep '^pkgver=' "$AUR_DIR/$pkg/PKGBUILD.bak" | cut -d= -f2- | tr -d "\"'")
|
||||
ls "$AUR_DIR/$pkg/${pkg}-${pkgver}-"*".pkg.tar.zst" 2>/dev/null \
|
||||
| grep -v -- '-debug-' | sort -V | tail -1 || true
|
||||
}
|
||||
|
||||
build_one() {
|
||||
local pkg="$1"
|
||||
local pkgdir="$AUR_DIR/$pkg"
|
||||
|
||||
info "[$pkg] Patching PKGBUILD..."
|
||||
patch_pkgbuild "$pkg"
|
||||
|
||||
# Collect inject args: extra (external) + artifacts of local deps built earlier
|
||||
local inject=()
|
||||
for f in "${EXTRA_INJECT[@]+"${EXTRA_INJECT[@]}"}"; do
|
||||
inject+=("-I" "$f")
|
||||
done
|
||||
while IFS= read -r dep; do
|
||||
if [[ -v "BUILT_ARTIFACTS[$dep]" ]]; then
|
||||
inject+=("-I" "${BUILT_ARTIFACTS[$dep]}")
|
||||
else
|
||||
warn "$pkg depends on $dep (local AUR) which was not built in this run"
|
||||
warn " → Build $dep first or pass: -I path/to/${dep}-*.pkg.tar.zst"
|
||||
fi
|
||||
done < <(local_deps_of "$pkg")
|
||||
|
||||
# Build args: -c only on the first package, then cleared
|
||||
local build_args=()
|
||||
if [[ $RESET_CHROOT -eq 1 ]]; then
|
||||
build_args+=("-c")
|
||||
RESET_CHROOT=0
|
||||
fi
|
||||
|
||||
info "[$pkg] Running extra-x86_64-build..."
|
||||
(
|
||||
cd "$pkgdir"
|
||||
if [[ ${#inject[@]} -gt 0 ]]; then
|
||||
extra-x86_64-build "${build_args[@]+"${build_args[@]}"}" -- "${inject[@]}"
|
||||
else
|
||||
extra-x86_64-build "${build_args[@]+"${build_args[@]}"}"
|
||||
fi
|
||||
)
|
||||
|
||||
# Record artifact for potential injection into dependents
|
||||
local artifact
|
||||
artifact=$(find_artifact "$pkg")
|
||||
if [[ -n "$artifact" ]]; then
|
||||
BUILT_ARTIFACTS[$pkg]="$artifact"
|
||||
ok "[$pkg] artifact: $(basename "$artifact")"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Main ────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Validate all requested packages exist
|
||||
for pkg in "${INPUT_PKGS[@]}"; do
|
||||
[[ -d "$AUR_DIR/$pkg" && -f "$AUR_DIR/$pkg/PKGBUILD" ]] \
|
||||
|| die "package not found: aur/$pkg/PKGBUILD"
|
||||
done
|
||||
|
||||
# Sort into build order (deps before dependents)
|
||||
resolve_order "${INPUT_PKGS[@]}"
|
||||
|
||||
# Create one tarball, reused for all packages in this run
|
||||
make_tarball
|
||||
|
||||
declare -a FAILED=()
|
||||
|
||||
for pkg in "${TOPO_ORDER[@]}"; do
|
||||
echo ""
|
||||
if build_one "$pkg"; then
|
||||
:
|
||||
else
|
||||
fail "[$pkg]"
|
||||
FAILED+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
||||
fail "packages failed: ${FAILED[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "All packages built successfully!"
|
||||
Reference in New Issue
Block a user