-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "example-provider" | ||
version = "1.0.0" | ||
edition = "2021" | ||
|
||
# This bit is important to make sure we get a loadable .so/.dll. | ||
[lib] | ||
crate-type = [ "cdylib" ] | ||
|
||
# This feature can be used to statically link a plugin with the gravel binary | ||
[features] | ||
no-root = [] | ||
|
||
[dependencies] | ||
# Contains definitions for creating a gravel plugin. | ||
gravel-ffi = { path = "../../gravel-ffi" } | ||
|
||
# Handles the FFI to make the plugin loadable. | ||
abi_stable = { version = "0.11.3", default-features = false } | ||
|
||
# Not strictly required, but most plugins will use these. | ||
log = "0.4.21" | ||
serde = { version = "1.0.203", features = ["derive"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
//! Simplistic example of a gravel provider. | ||
use gravel_ffi::prelude::*; | ||
use serde::Deserialize; | ||
|
||
// Default configs are defined as yml and loaded at runtime. | ||
// | ||
// Use `include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/config.yml"));` | ||
// to read it from a file at compile time. | ||
pub const DEFAULT_CONFIG: &str = "number: 2"; | ||
|
||
// The provider can define any state, as long as it's immutable. | ||
pub struct ExampleProvider { | ||
_number: u32, | ||
} | ||
|
||
// This macro generates the FFI loader code to make this library work as a plugin. | ||
// The name of the plugin should be unique. | ||
#[gravel_provider("example")] | ||
impl ProviderDef for ExampleProvider { | ||
// Creates a new instance of the provider. | ||
// There might be several with different configs! | ||
fn new(config: &PluginConfigAdapter<'_>) -> Self { | ||
// Plug in the default config, and the adapter will fetch | ||
// the config for this provider from the user's main config. | ||
let config = config.get::<Config>(DEFAULT_CONFIG); | ||
|
||
log::info!("configuration number was: {}", config.number); | ||
|
||
Self { _number: config.number } | ||
} | ||
|
||
// This function is called every time the user types a letter, | ||
// so it must be very quick to execute. | ||
fn query(&self, query: &str) -> ProviderResult { | ||
let subtitle = format!("you were searching for {query}?"); | ||
|
||
// Plugins can define their own hit type, but for most anything | ||
// the default SimpleHit combined with a closure does the trick. | ||
let hit = SimpleHit::new("example hit", subtitle, |hit, context| { | ||
// When the user actually selects this hit in the frontend, | ||
// this closure is called. | ||
|
||
log::info!("hit action was called: title '{}'", hit.title); | ||
|
||
// Sends a message to the frontend and asks it to hide. | ||
// If this isn't called, the frontend will just stay open! | ||
context.hide_frontend(); | ||
}); | ||
|
||
// Wrap the hit in a ProviderResult and return it. | ||
ProviderResult::single(hit) | ||
} | ||
} | ||
|
||
// The config struct must be deserializable. | ||
#[derive(Deserialize)] | ||
pub struct Config { | ||
pub number: u32, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#![allow(unused_crate_dependencies)] | ||
|
||
use abi_stable::std_types::{ROption, RSlice, RString}; | ||
use abi_stable::{library::RootModule, sabi_trait, traits::IntoReprC}; | ||
use gravel_ffi::{logging::NoOpLogTarget, HitActionContext, PluginConfigAdapter, PluginLibRef, RefDynHitActionContext}; | ||
use mockall::mock; | ||
use std::path::PathBuf; | ||
|
||
mock! { | ||
HitActionContext {} | ||
impl HitActionContext for HitActionContext { | ||
fn hide_frontend(&self); | ||
fn refresh_frontend(&self); | ||
fn exit(&self); | ||
fn restart(&self); | ||
} | ||
} | ||
|
||
impl<'a> From<&'a MockHitActionContext> for RefDynHitActionContext<'a> { | ||
fn from(value: &'a MockHitActionContext) -> Self { | ||
Self::from_ptr(value, sabi_trait::TD_Opaque) | ||
} | ||
} | ||
|
||
/// This is a smoke test to check for breaking changes in the plugin interface | ||
#[cfg_attr(unix, test)] | ||
pub fn use_provider() { | ||
// abi_stable checks if the types are compatible | ||
let lib = PluginLibRef::load_from_file(&PathBuf::from("tests/data/libexample_provider.so")) | ||
.expect("plugin must be loadable"); | ||
|
||
// but just to be sure, let's do a test run of the provider | ||
let definitions = lib.plugin()(NoOpLogTarget::get()); | ||
let definition = definitions.first().expect("must export at least one definition"); | ||
|
||
let factory = definition.factory.provider(); | ||
let factory = factory.expect("plugin must define provider factory"); | ||
|
||
let adapter = PluginConfigAdapter::from(RString::from("noconfig"), RSlice::default()); | ||
let provider = (factory)(&adapter); | ||
|
||
let result = provider.query("something".into_c()); | ||
|
||
assert!(!result.hits.is_empty()); | ||
|
||
let hit = &result.hits[0]; | ||
|
||
assert_eq!("example hit", hit.title()); | ||
assert_eq!("you were searching for something?", hit.subtitle()); | ||
assert_eq!(ROption::RNone, hit.override_score()); | ||
|
||
let mut context = MockHitActionContext::default(); | ||
|
||
context.expect_hide_frontend().return_const(()); | ||
|
||
hit.action((&context).into()); | ||
} |