feat: add abi stable crates

This commit is contained in:
2020-12-27 14:43:46 +08:00
parent cd4a02f513
commit 8d34b85a9c
21 changed files with 3072 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
url
https://github.com/rodrimati1992/abi_stable_crates/tree/master/examples/1_trait_objects

View File

@@ -0,0 +1,367 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "abi_stable"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112fbc83f18cf0f12358975cac27eb4556cc5d777bde72312e0cec7a294e1699"
dependencies = [
"abi_stable_derive",
"abi_stable_shared",
"core_extensions",
"crossbeam-channel",
"generational-arena",
"libloading",
"lock_api",
"parking_lot",
"repr_offset",
"rustc_version",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "abi_stable_derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be19f4da4a4a1f2537f1f14d8f0917c07758acb7419d3bad9e9d62a11f6b5b0"
dependencies = [
"abi_stable_shared",
"as_derive_utils",
"core_extensions",
"proc-macro2",
"quote",
"rustc_version",
"syn",
"typed-arena",
]
[[package]]
name = "abi_stable_shared"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96112d6a13f37dd1cc2a2f07f7f12b7c3d176b6eec36e7987432a70fece8b48"
dependencies = [
"core_extensions",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "as_derive_utils"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc3613a62c7b739739a2cb1ee166ac275f16c5b86caf454ba21a2f79f04b025"
dependencies = [
"core_extensions",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core_extensions"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff55d62b113b98e0b0a55112741376c4cfe36f1290d18392c2c5727e09629a7f"
dependencies = [
"rustc_version",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "example_application"
version = "0.1.0"
dependencies = [
"abi_stable",
"core_extensions",
"example_interface",
"serde",
"serde_json",
"smallvec",
]
[[package]]
name = "example_interface"
version = "0.1.0"
dependencies = [
"abi_stable",
"arrayvec",
"core_extensions",
"serde",
"serde_json",
]
[[package]]
name = "generational-arena"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601"
dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "libloading"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "repr_offset"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68e33940e1f1e7ab77493ad7d983dba544ce72185c61d4b1b23e6c32dd2a165"
dependencies = [
"rustc_version",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "syn"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "typed-arena"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,13 @@
[package]
name = "example_application"
version = "0.1.0"
authors = ["rodrimati1992 <rodrimatt1985@gmail.com>"]
edition = "2018"
[dependencies]
abi_stable = "0.9.1"
core_extensions={ version = "0.1.18", default_features = false, features = ["std"] }
serde={ version = "1.0.117", features = ["derive"] }
serde_json = { version = "1.0.59", features = ["raw_value"] }
smallvec= "1.4.2"
example_interface={ version="0.1", path="../interface" }

View File

@@ -0,0 +1,33 @@
{
"plugins":[
{
"name":"example_plugin_text_munging",
"instances":2,
"rename":"text_munging"
},
{
"name":"example_plugin_utils",
"rename":"utils"
}
],
"_hidden":[
],
"commands":{
"text_munging:first":{"Rot13":"what"},
"text_munging:last":"GetCommands",
"text_munging:all":{"CapitalizeWords":{
"text":"This is an example text.",
"words":["an","text"]
}},
"utils":{"Batch":{
"plugin":"text_munging:first",
"commands":[
{"Rot13":"Fancr xvyyf Qhzoyrqber"},
{"CapitalizeWords":{
"text":"Oh,that's not something I expected.",
"words":["not","expected"]
}}
]
}}
}
}

View File

@@ -0,0 +1,206 @@
use super::*;
use std::time::{Duration,Instant};
pub struct TheApplication{
pub(super) plugins:Vec<PluginType>,
pub(super) state:ApplicationState,
}
pub struct ApplicationState{
// This is separate so that I can use the address
// of a PluginType to find its index inside of plugins.
pub(super) plugin_ids:Vec<PluginId>,
pub(super) id_map:HashMap<RString,PluginModAndIndices>,
pub(super) delayed_commands:VecDeque<DelayedCommand>,
pub(super) responses:VecDeque<DelayedResponse>,
pub(super) sender:RSender<AsyncCommand>,
pub(super) receiver:RReceiver<AsyncCommand>,
pub(super) last_run_at:Instant,
}
fn print_response(
plugin_id:&PluginId,
response:&str,
){
println!(
"reponse:\n{}\nfrom:\n {:?}\n\n",
response.left_pad(4),
plugin_id,
);
}
impl TheApplication{
/// Runs a command,
pub fn run_command(&mut self,which_plugin:WhichPlugin,command:RStr<'_>)->Result<(),AppError>{
let list=self.state.expand_which_plugin(which_plugin)?;
for index in list {
let state=Application_TO::from_ptr(&mut self.state,TU_Opaque);
let plugin=&mut self.plugins[index as usize];
println!("command:\n{}", command.left_pad(4));
let resp=plugin.json_command(command,state).into_result()?;
self.state.register_command_run();
print_response(&self.state.plugin_ids[index],&resp);
}
Ok(())
}
pub fn tick(&mut self) -> Result<(), AppError>{
if let Ok(ac)=self.state.receiver.try_recv() {
self.state.send_command_to_plugin(&ac.from,ac.which_plugin,ac.command).into_result()?;
}
if let Some(dc)=self.state.delayed_commands.pop_front() {
self.run_command_(&dc.from,dc.plugin_index,&dc.command)?;
}
let mut responses=mem::replace(&mut self.state.responses, VecDeque::new());
for DelayedResponse{to,from,response} in responses.drain(..) {
let response=PluginResponse::owned_response(from,response);
let state=Application_TO::from_ptr(&mut self.state,TU_Opaque);
if let RSome(res)=self.plugins[to]
.handle_response(response, state)
.into_result()?
{
print_response(&res.plugin_id, &res.response);
}
}
self.state.responses=responses;
Ok(())
}
pub fn is_finished(&self)->bool{
self.state.last_run_at.elapsed()>=Duration::from_secs(5)
}
fn run_command_(&mut self,from:&PluginId, to:usize, command:&str) -> Result<(), AppError> {
let state=Application_TO::from_ptr(&mut self.state,TU_Opaque);
let response=self.plugins[to].json_command(command.into(),state).into_result()?;
let to=self.state.index_for_plugin_id(from)?;
self.state.register_command_run();
let response = DelayedResponse {
from:self.state.plugin_ids[to].clone(),
to,
response,
};
self.state.responses.push_back(response);
Ok(())
}
}
impl ApplicationState{
pub(crate) fn new() -> Self {
let (sender,receiver)=crossbeam_channel::unbounded();
Self{
plugin_ids:Vec::new(),
id_map:HashMap::new(),
delayed_commands:VecDeque::new(),
responses:VecDeque::new(),
sender,
receiver,
last_run_at:Instant::now(),
}
}
fn register_command_run(&mut self) {
self.last_run_at=Instant::now();
}
fn index_for_plugin_id(&self, id:&PluginId) -> Result<usize,AppError> {
self.id_map.get(&*id.named)
.and_then(|x| x.indices.get(id.instance as usize).cloned())
.ok_or_else(|| AppError::InvalidPlugin(WhichPlugin::Id(id.clone())))
}
fn expand_which_plugin(&self, which_plugin:WhichPlugin) -> Result<PluginIndices, AppError> {
match which_plugin {
WhichPlugin::Id(id)=>{
self.index_for_plugin_id(&id)
.map(|i| PluginIndices::from([i]) )
},
WhichPlugin::First{ref named}
| WhichPlugin::Last{ref named}
| WhichPlugin::Every{ref named} => {
self.id_map.get(&**named).and_then(|x| {
let list=&x.indices;
match which_plugin {
WhichPlugin::First{..} => {
PluginIndices::from([*list.first()?])
},
WhichPlugin::Last{..} => {
PluginIndices::from([*list.last()?])
},
WhichPlugin::Every{..} => {
PluginIndices::from(&**list)
},
_=>unreachable!(),
}.piped(Some)
})
.ok_or_else(|| AppError::InvalidPlugin(which_plugin.clone()))
},
WhichPlugin::Many(list) => {
let mut plugin_indices=PluginIndices::new();
for elem in list {
plugin_indices.extend(self.expand_which_plugin(elem)?);
}
Ok(plugin_indices)
}
}
}
}
impl Application for ApplicationState {
fn send_command_to_plugin(
&mut self,
from:&PluginId,
which_plugin:WhichPlugin,
command:RString,
) -> RResult<(),AppError> {
self.expand_which_plugin(which_plugin).map(|plugin_indices|{
let command=Arc::new(command);
for plugin_index in plugin_indices {
let from=from.clone();
self.delayed_commands.push_back(DelayedCommand {
from,
plugin_index,
command:command.clone(),
});
}
}).into()
}
fn get_plugin_id(&self, which_plugin:WhichPlugin) -> RResult<RVec<PluginId>, AppError> {
self.expand_which_plugin(which_plugin)
.map(|list|{
list.into_iter()
.map(|i| self.plugin_ids[i].clone() )
.collect::<RVec<PluginId>>()
})
.into()
}
fn sender(&self) -> RSender<AsyncCommand> {
self.sender.clone()
}
fn loaded_plugins(&self) -> RVec<PluginId> {
self.plugin_ids.clone().into()
}
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,315 @@
use std::{collections::{HashMap, VecDeque}, io, path::{Path, PathBuf}, mem, sync::Arc};
use abi_stable::{
external_types::crossbeam_channel::{self, RSender, RReceiver},
std_types::{RString, RStr, RResult, RVec, ROk, RErr, RSome},
sabi_trait::prelude::TU_Opaque,
library::{RawLibrary, LibraryError, LibrarySuffix, lib_header_from_path},
};
#[allow(unused_imports)]
use core_extensions::{SelfOps, SliceExt, StringExt};
use example_interface::{
AsyncCommand,
Application_TO,
Application,
Error as AppError,
PluginId,
PluginMod_Ref,
PluginType,
PluginResponse,
WhichPlugin,
};
use serde::Deserialize;
use serde_json::value::RawValue;
use smallvec::SmallVec;
mod vec_from_map;
mod app;
use crate::{
app::{TheApplication,ApplicationState},
vec_from_map::VecFromMap,
};
/// Returns the path the plugin will be loaded from.
fn compute_plugin_path(base_name:&str)->io::Result<PathBuf>{
let debug_dir = "pluginlibs/".as_ref_::<Path>().into_(PathBuf::T);
let release_dir = "pluginlibs/".as_ref_::<Path>().into_(PathBuf::T);
let debug_path=
RawLibrary::path_in_directory(&debug_dir ,base_name,LibrarySuffix::NoSuffix);
let release_path=
RawLibrary::path_in_directory(&release_dir,base_name,LibrarySuffix::NoSuffix);
match (debug_path.exists(),release_path.exists()) {
(false,false)=>debug_path,
(true,false)=>debug_path,
(false,true)=>release_path,
(true,true)=>{
if debug_path.metadata()?.modified()? < release_path.metadata()?.modified()? {
release_path
}else{
debug_path
}
}
}.piped(Ok)
}
/// A description of what plugin to load.
#[derive(Debug,Clone,Deserialize)]
#[serde(untagged)]
pub enum PluginToLoad{
Named(String),
WithInstances{
#[serde(alias = "name")]
named:String,
#[serde(default="one_u64")]
instances:u64,
#[serde(alias = "renamed")]
rename:Option<String>,
}
}
fn one_u64()->u64{
1
}
/// The type that the configuration file is deserialized into.
#[derive(Debug,Clone,Deserialize)]
pub struct Config{
pub plugins:RVec<PluginToLoad>,
pub commands:VecFromMap<WhichPlugin,Box<RawValue>>,
}
/// A description of plugin instances that were instantiated and left to instantiate,
/// as well as the root module of the plugin's dynamic library to instantiate the plugins.
pub struct PluginModAndIndices{
root_module:PluginMod_Ref,
to_be_instantiated:u64,
indices:Vec<usize>,
}
pub type PluginIndices=SmallVec<[usize;1]>;
/// Commands sent to plugins after calling `Application::send_command_to_plugin`.
#[derive(Debug)]
pub struct DelayedCommand{
/// Which plugin sent the command
from:PluginId,
/// The index in plugins to which the command is sent.
plugin_index:usize,
/// The command for the `plugin_index` plugin.
command:Arc<RString>,
}
/// Used to handle the responses to the delayed commands sent to plugins after calling
/// `Application::send_command_to_plugin`.
#[derive(Debug)]
pub struct DelayedResponse{
/// The plugin that sends the reponse.
from:PluginId,
/// The plugin that sent the command for which this is the reponse.
to:usize,
/// The response from the `from` plugin to the `to` plugin.
response:RString,
}
fn main()-> io::Result<()> {
let config_path = match std::env::args_os().nth(1) {
Some(os)=>PathBuf::from(os),
None=>{
println!(
"Help:You can pass a configuration's path as a command-line argument."
);
PathBuf::from("./data/app_config.json")
}
};
let file_contents=match std::fs::read_to_string(&*config_path) {
Ok(x)=>x,
Err(e)=>{
eprintln!(
"Could not load the configuration file at:\n\
\t{}\n\
Because of this error:\n{}\n",
config_path.display(),
e
);
std::process::exit(1);
}
};
let config:Config=match serde_json::from_str(&file_contents) {
Ok(x) => x,
Err(e) => {
eprintln!(
"Could not parse the configuration file at:\n\
\t{}\n\
Because of this error:\n\
{}\n",
config_path.display(),
e
);
std::process::exit(1);
},
};
let mut nonexistent_files=Vec::<(String,io::Error)>::new();
let mut library_errs=Vec::<(String,LibraryError)>::new();
let mut loaded_libraries=Vec::<String>::new();
let mut plugins=Vec::new();
let mut state=ApplicationState::new();
for plug in &config.plugins {
let (named, instances, rename) = match plug {
PluginToLoad::Named(named) => ((*named).clone(),1,None),
PluginToLoad::WithInstances{named,instances,rename} =>
((*named).clone(),*instances,rename.clone()),
};
let name_key=rename.unwrap_or_else(||named.clone());
if let Some(mod_i)=state.id_map.get_mut(&*name_key) {
mod_i.to_be_instantiated+=instances;
continue;
}
let library_path:PathBuf = match compute_plugin_path(named.as_ref()) {
Ok(x) => x,
Err(e) => {
nonexistent_files.push((named.clone(),e));
continue;
}
};
let res = (|| {
let header=lib_header_from_path(&library_path)?;
header.init_root_module::<PluginMod_Ref>()
})();
let root_module=match res {
Ok(x) => x,
Err(e) => {
library_errs.push((named.clone(),e));
continue;
}
};
loaded_libraries.push(name_key.clone());
state.id_map.insert(name_key.into_(RString::T),PluginModAndIndices{
root_module,
to_be_instantiated:instances,
indices:Vec::with_capacity(instances as usize),
});
}
if !nonexistent_files.is_empty(){
for (name, e) in nonexistent_files {
eprintln!(
"Could not load librarr:\n\
\t{}\n\
because of this error:\n\
{}\n\
",
name,e
)
}
std::process::exit(1);
}
if !library_errs.is_empty(){
for (name,e) in library_errs {
eprintln!(
"Could not load librarr:\n\
\t{}\n\
because of this error:\n\
{}\n\
",
name,e
)
}
std::process::exit(1);
}
let mut plugin_new_errs=Vec::<(String,AppError)>::new();
for name in loaded_libraries {
let mod_i=state.id_map.get_mut(&*name).unwrap();
for _ in 0..mem::replace(&mut mod_i.to_be_instantiated,0) {
let plugin_constructor=mod_i.root_module.new();
let new_id=PluginId{
named:name.clone().into(),
instance:mod_i.indices.len() as u64,
};
let plugin=match plugin_constructor(state.sender.clone(),new_id.clone()) {
ROk(x)=>x,
RErr(e)=>{
plugin_new_errs.push((name.clone(),e));
continue;
}
};
let new_index=plugins.len();
plugins.push(plugin);
mod_i.indices.push(new_index);
state.plugin_ids.push(new_id);
}
}
if !plugin_new_errs.is_empty() {
for (name,e) in plugin_new_errs {
eprintln!(
"Could not instantiate plugin:\n\
\t{}\n\
because of this error:\n\
{}\n\
",
name,e
)
}
std::process::exit(1);
}
let mut config_commands=config.commands.vec.into_iter();
let mut app=TheApplication{
plugins,
state,
};
while !app.is_finished() {
if let Some((which_plugin,command))=config_commands.next() {
let command=command.get();
if let Err(e)=app.run_command(which_plugin.clone(),command.into()){
eprintln!(
"Error while running command on:\n{:?}\nError:{}\nCommand:\n{:?}\n",
which_plugin,e,command
);
}
}
if let Err(e)=app.tick() {
eprintln!("Error in application loop:\n{}\n",e);
}
}
if app.is_finished() {
println!("timeout waiting for events");
}
Ok(())
}

View File

@@ -0,0 +1,57 @@
use std::{fmt, marker::PhantomData};
use serde::de::{Deserialize, Deserializer, Visitor, MapAccess};
/// Used to deserialize a json object to a list of key-value pairs
#[derive(Clone,Debug)]
pub struct VecFromMap<K,V>{
pub vec:Vec<(K,V)>,
}
struct VecFromMapVisitor<K, V> {
marker: PhantomData<(K, V)>
}
impl<K, V> VecFromMapVisitor<K, V> {
const NEW:Self=Self{marker:PhantomData};
}
impl<'de, K, V> Visitor<'de> for VecFromMapVisitor<K, V>
where
K: Deserialize<'de>,
V: Deserialize<'de>,
{
type Value = VecFromMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let cap=access.size_hint().unwrap_or(0);
let mut vec = Vec::<(K,V)>::with_capacity(cap);
while let Some(pair) = access.next_entry()? {
vec.push(pair);
}
Ok(VecFromMap{vec})
}
}
impl<'de, K, V> Deserialize<'de> for VecFromMap<K, V>
where
K: Deserialize<'de>,
V: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(VecFromMapVisitor::NEW)
}
}

View File

@@ -0,0 +1,12 @@
[package]
name = "example_interface"
version = "0.1.0"
authors = ["rodrimati1992 <rodrimatt1985@gmail.com>"]
edition = "2018"
[dependencies]
abi_stable = "0.9.1"
serde={ version = "1.0.117", features = ["derive"] }
arrayvec= "0.5.1"
core_extensions={ version = "0.1.18", default_features = false, features = ["std"] }
serde_json= "1.0.59"

View File

@@ -0,0 +1,154 @@
use crate::{PluginId,WhichPlugin};
use std::fmt;
use serde::{
Serialize,Deserialize,
de::{self,Deserializer, DeserializeOwned, IgnoredAny, Visitor, MapAccess, Error as _},
};
use abi_stable::{StableAbi, std_types::*};
/// The commands that map to methods in the Plugin trait.
// This is intentionally not `#[derive(StableAbi)]`,
// since it can be extended in minor versions of the interface.
// I has to be serialized to pass it through ffi.
#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)]
pub enum BasicCommand{
GetCommands,
}
/// These is the (serialized) return value of calling `PluginExt::send_basic_command`.
// This is intentionally not `#[derive(StableAbi)]`,
// since it can be extended in minor versions of the interface.
// I has to be serialized to pass it through ffi.
#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)]
pub enum BasicRetVal{
GetCommands(RVec<CommandDescription>),
}
// This is intentionally not `#[derive(StableAbi)]`,
// since it can be extended in minor versions of the interface.
// I has to be serialized to pass it through ffi.
#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)]
#[serde(untagged)]
pub enum CommandUnion<T>{
ForPlugin(T),
Basic(BasicCommand),
}
#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)]
#[serde(untagged)]
pub enum ReturnValUnion<T>{
ForPlugin(T),
Basic(BasicRetVal),
}
////////////////////////////////////////////////////////////////////////////////
/// A partially deserialize command,that only deserialized its variant.
#[derive(Debug,Clone)]
pub struct WhichVariant{
pub variant:RString,
}
struct WhichVariantVisitor;
impl<'de> Visitor<'de> for WhichVariantVisitor{
type Value = WhichVariant;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with a single entry,or a string")
}
fn visit_str<E>(self, value: &str) -> Result<WhichVariant, E>
where
E: de::Error,
{
Ok(WhichVariant{variant:value.to_string().into()})
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let (variant,_)=access.next_entry::<RString,IgnoredAny>()?
.ok_or_else(||M::Error::custom("Expected a map with a single entry"))?;
if let Some((second,_))=access.next_entry::<RString,IgnoredAny>()? {
let s=format!(
"Expected a map with a single field,\n\
instead found both {{ \"{}\":... , \"{}\": ... }}",
variant,
second,
);
return Err(M::Error::custom(s));
}
Ok(WhichVariant{variant})
}
}
impl<'de> Deserialize<'de> for WhichVariant{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(WhichVariantVisitor)
}
}
////////////////////////////////////////////////////////////////////////////////
/// Denotes this as a command type.
pub trait CommandTrait:Serialize{
type Returns:DeserializeOwned;
}
impl CommandTrait for BasicCommand{
type Returns=BasicRetVal;
}
/// Describes a command.
#[repr(C)]
#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize,StableAbi)]
pub struct CommandDescription{
/// A description of what this command does.
pub name:RCow<'static,str>,
/// A description of what this command does,
/// optionally with a description of the command format.
pub description:RCow<'static,str>,
}
impl CommandDescription{
pub fn from_literals(
name:&'static str,
description:&'static str,
)->Self{
CommandDescription{
name:name.into(),
description:description.into(),
}
}
}
////////////////////////////////////////////////////////////////////////////////
#[repr(C)]
#[derive(Debug,Clone,PartialEq,Eq,StableAbi)]
pub struct AsyncCommand{
pub from:PluginId,
pub which_plugin:WhichPlugin,
pub command:RString,
}

View File

@@ -0,0 +1,127 @@
use crate::{commands::CommandDescription,WhichPlugin,WhichCommandRet};
use abi_stable::{StableAbi, std_types::{RBoxError,RBox,RString,RVec}};
use std::{error::Error as ErrorTrait, fmt::{self,Display}};
use core_extensions::strings::StringExt;
#[repr(u8)]
#[derive(Debug,StableAbi)]
pub enum Error{
/// An error produced by `serde_json::to_string`.
Serialize(RBoxError,WhichCommandRet),
/// An error produced by `serde_json::from_string`.
Deserialize(RBoxError,WhichCommandRet),
/// A deserialization error produced when trying to deserialize json
/// as a particular command type.
UnsupportedCommand(RBox<Unsupported>),
/// A deserialization error produced when trying to deserialize json
/// as a particular return value type.
UnsupportedReturnValue(RBox<Unsupported>),
/// An invalid plugin.
InvalidPlugin(WhichPlugin),
/// A custom error.
Custom(RBoxError),
/// A list of errors.
Many(RVec<Error>),
}
/// Represents a command or return value that wasn't supported.
#[repr(C)]
#[derive(Debug,StableAbi)]
pub struct Unsupported{
/// The name of the plugin for which the command/return value wasn't supported.
pub plugin_name:RString,
/// The command/return value that wasn't supported.
pub command_name:RString,
/// A custom error.
pub error:RBoxError,
/// A list of the commands that the plugin supports
pub supported_commands:RVec<CommandDescription>,
}
impl Error{
pub fn unsupported_command(what:Unsupported)->Self{
Error::UnsupportedCommand(RBox::new(what))
}
pub fn unsupported_return_value(what:Unsupported)->Self{
Error::UnsupportedReturnValue(RBox::new(what))
}
}
impl Display for Error{
fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result {
match self {
Error::Serialize(e,which)=>{
let which=match which {
WhichCommandRet::Command=>"command",
WhichCommandRet::Return=>"return value",
};
writeln!(f,"Error happened while serializing the {}:\n{}\n",which,e)
}
Error::Deserialize(e,which)=>{
let which=match which {
WhichCommandRet::Command=>"command",
WhichCommandRet::Return=>"return value",
};
writeln!(f,"Error happened while deserializing {}:\n{}\n",which,e)
}
Error::UnsupportedCommand(v)=>{
writeln!(
f,
"Plugin '{}' ooes not support this command:\n\
\t'{}'\n\
Because of this error:\n{}\n\
Supported commands:\
",
v.plugin_name,
v.command_name,
v.error,
)?;
for supported in &v.supported_commands {
write!(
f,
"{}",
format!(
"\nName:\n{}\nDescription:\n{}\n\n",
supported.name.left_padder(4),
supported.description.left_padder(4),
).left_padder(4)
)?;
}
Ok(())
}
Error::UnsupportedReturnValue(v)=>
writeln!(
f,
"Unrecognized return value from '{}',named:\n\
\t'{}'\n\
Because of this error:\n{}\n\
",
v.plugin_name,
v.command_name,
v.error,
),
Error::InvalidPlugin(wc)=>
writeln!(
f,
"Attempted to access a nonexistent plugin with the WhichPlugin:\n\t{:?}\n",
wc
),
Error::Custom(e)=>Display::fmt(e,f),
Error::Many(list)=>{
for e in list {
writeln!(f,"{}",e)?;
}
Ok(())
}
}
}
}
impl ErrorTrait for Error{}

View File

@@ -0,0 +1,234 @@
/*!
This is an example `interface crate`,
where all publically available modules(structs of function pointers) and types are declared,
To load the library and the modules together,
call `<PluginMod_Ref as RootModule>::load_from_directory`,
which will load the dynamic library from a directory(folder),
and all the modules inside of the library.
*/
use abi_stable::{
StableAbi,
sabi_trait,
package_version_strings,
declare_root_module_statics,
library::RootModule,
sabi_types::VersionStrings,
external_types::{
crossbeam_channel::RSender,
},
std_types::{RBox, RCow, RVec, RStr, RString,RResult, ROption, ROk,RSome},
};
use serde::{Serialize,Deserialize};
mod commands;
mod error;
mod which_plugin;
mod vec_from_map;
pub mod utils;
pub use self::{
commands::{
BasicCommand,BasicRetVal,CommandDescription,CommandTrait,WhichVariant,AsyncCommand,
},
error::{Error,Unsupported},
which_plugin::WhichPlugin,
vec_from_map::VecFromMap,
};
///////////////////////////////////////////////////////////////////////////////
/**
The identifier for a plugin.
*/
#[repr(C)]
#[derive(Debug,Clone,PartialEq,Eq,StableAbi,Serialize,Deserialize)]
pub struct PluginId{
pub named:RCow<'static,str>,
/// The number of the instance of this Plugin.
pub instance:u64,
}
/// Describes whether a boxed error is a command or a return value.
#[repr(u8)]
#[derive(Debug,Clone,PartialEq,Eq,StableAbi,Serialize,Deserialize)]
pub enum WhichCommandRet{
Command,
Return,
}
/// The response from having called `ApplicationMut::send_command_to_plugin` ealier.
#[repr(C)]
#[derive(Debug,Clone,PartialEq,Eq,StableAbi)]
pub struct PluginResponse<'a>{
/// The id of the plugin that is responding.
pub plugin_id:PluginId,
/// The response from the plugin
pub response:RCow<'a,str>,
}
impl<'a> PluginResponse<'a>{
pub fn owned_response(plugin_id:PluginId,response:RString)->Self{
Self{plugin_id,response:response.into()}
}
pub fn borrowed_response(plugin_id:PluginId,response:RStr<'a>)->Self{
Self{plugin_id,response:response.into()}
}
}
///////////////////////////////////////////////////////////////////////////////
pub type PluginType=Plugin_TO<'static,RBox<()>>;
/**
A plugin which is loaded by the application,and provides some functionality.
*/
#[sabi_trait]
//#[sabi(debug_print)]
pub trait Plugin {
/// Handles a JSON encoded command.
fn json_command(
&mut self,
command: RStr<'_>,
app:ApplicationMut<'_>,
)->RResult<RString,Error>;
/// Handles a response from another Plugin,
/// from having called `ApplicationMut::send_command_to_plugin` ealier.
fn handle_response<'a>(
&mut self,
response:PluginResponse<'a>,
_app:ApplicationMut<'_>,
)->RResult<ROption<PluginResponse<'a>>,Error>{
ROk(RSome(response))
}
/// Gets the PluginId that was passed to this plugin in its constructor.
fn plugin_id(&self)->&PluginId;
/// Gets a description of all commands from this Plugin.
fn list_commands(&self)->RVec<CommandDescription>;
/*
Closes the plugin,
This does not unload the dynamic library of this plugin,
you can instantiate another instance of this plugin with
`PluginMod_Ref::get_module().new()(application_handle)`.
The `#[sabi(last_prefix_field)]` attribute here means that this is the last method
that was defined in the first compatible version of the library
(0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
requiring new methods to always be added below preexisting ones.
The `#[sabi(last_prefix_field)]` attribute would stay on this method until the library
bumps its "major" version,
at which point it would be moved to the last method at the time.
*/
#[sabi(last_prefix_field)]
fn close(self,app:ApplicationMut<'_>);
}
///////////////////////////////////////////////////////////////////////////////
/// The root module of a`plugin` dynamic library.
///
/// To load this module,
/// call <PluginMod as RootModule>::load_from_directory(some_directory_path)
#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref="PluginMod_Ref")))]
#[sabi(missing_field(panic))]
pub struct PluginMod {
/**
Constructs the plugin.
The `#[sabi(last_prefix_field)]` attribute here means that this is the last field in this struct
that was defined in the first compatible version of the library
(0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
requiring new fields to always be added below preexisting ones.
The `#[sabi(last_prefix_field)]` attribute would stay on this field until the library
bumps its "major" version,
at which point it would be moved to the last field at the time.
*/
#[sabi(last_prefix_field)]
pub new: extern "C" fn(RSender<AsyncCommand>,PluginId) -> RResult<PluginType,Error>,
}
impl RootModule for PluginMod_Ref {
declare_root_module_statics!{PluginMod_Ref}
const BASE_NAME: &'static str = "plugin";
const NAME: &'static str = "plugin";
const VERSION_STRINGS: VersionStrings = package_version_strings!();
}
///////////////////////////////////////////////////////////////////////////////
/// A mutable reference to the application implementation.
pub type ApplicationMut<'a>=Application_TO<'a,&'a mut ()>;
#[sabi_trait]
pub trait Application{
/// Asynchronously Sends a command to the plugin(s) specified by `which_plugin`.
///
/// # Errors
///
/// Returns an `Error::InvalidPlugin` if `which_plugin` is invalid.
fn send_command_to_plugin(
&mut self,
from:&PluginId,
which_plugin:WhichPlugin,
command:RString,
)->RResult<(),Error>;
/**
Gets the `PluginId`s of the plugins specified by `which_plugin`.
The `#[sabi(last_prefix_field)]` attribute here means that this is the last method
that was defined in the first compatible version of the library
(0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
requiring new methods to always be added below preexisting ones.
The `#[sabi(last_prefix_field)]` attribute would stay on this method until the library
bumps its "major" version,
at which point it would be moved to the last method at the time.
*/
#[sabi(last_prefix_field)]
fn get_plugin_id(&self,which_plugin:WhichPlugin)->RResult<RVec<PluginId>,Error>;
/// Gets the sender end of a channel to send commands to the application/other plugins.
fn sender(&self)->RSender<AsyncCommand>;
/// Gets the PluginId of all loaded plugins
fn loaded_plugins(&self)->RVec<PluginId>;
}

View File

@@ -0,0 +1,129 @@
use crate::{
commands::{
BasicCommand,
CommandUnion,
CommandUnion as CU,
CommandTrait,
ReturnValUnion,
ReturnValUnion as RVU,
WhichVariant,
BasicRetVal,
},
error::Unsupported,
ApplicationMut,WhichCommandRet,Error,Plugin,PluginType,
};
use abi_stable::{std_types::{RStr, RString, RBoxError, RResult}};
use serde::{Serialize,Deserialize};
/**
Sends a json encoded command to a plugin,and returns the response by encoding it to json.
# Errors
These are all error that this function returns
(this does not include error returned as part of the command):
- Error::Serialize:
If the command/return value could not be serialized to JSON.
- Error::Deserialize
If the command/return value could not be deserialized from JSON(this comes from the plugin).
- Error::UnsupportedCommand
If the command is not supported by the plugin.
*/
pub fn process_command<'de,P,C,R,F>(this:&mut P,command:RStr<'de>,f:F)->RResult<RString,Error>
where
P:Plugin,
F:FnOnce(&mut P,C)->Result<R,Error>,
C:Deserialize<'de>,
R:Serialize,
{
(||->Result<RString,Error>{
let command=command.as_str();
let which_variant=serde_json::from_str::<WhichVariant>(&command)
.map_err(|e| Error::Deserialize(RBoxError::new(e),WhichCommandRet::Command) )?;
let command=serde_json::from_str::<CommandUnion<C>>(command)
.map_err(|e|{
Error::unsupported_command(Unsupported{
plugin_name:this.plugin_id().named.clone().into_owned(),
command_name:which_variant.variant,
error:RBoxError::new(e),
supported_commands:this.list_commands(),
})
})?;
let ret:ReturnValUnion<R>=match command {
CU::Basic(BasicCommand::GetCommands)=>{
let commands=this.list_commands();
RVU::Basic(BasicRetVal::GetCommands(commands))
}
CU::ForPlugin(cmd)=>{
RVU::ForPlugin(f(this,cmd)?)
}
};
match serde_json::to_string(&ret) {
Ok(v)=>Ok(v.into()),
Err(e)=>Err(Error::Serialize(RBoxError::new(e),WhichCommandRet::Return)),
}
})().into()
}
/**
Sends a typed command to a plugin.
# Errors
These are all error that this function returns
(this does not include error returned as part of the command):
- Error::Serialize:
If the command/return value could not be serialized to JSON.
- Error::Deserialize
If the command/return value could not be deserialized from JSON(this comes from the plugin).
- Error::UnsupportedReturnValue:
If the return value could not be deserialized from JSON
(after checking that it has the `{"name":"...",description: ... }` format),
containing the name of the command this is a return value for .
- Error::UnsupportedCommand
If the command is not supported by the plugin.
*/
pub fn send_command<C>(
this:&mut PluginType,
command:&C,
app:ApplicationMut<'_>
)->Result<C::Returns,Error>
where
C:CommandTrait,
{
let cmd=serde_json::to_string(&command)
.map_err(|e| Error::Serialize(RBoxError::new(e),WhichCommandRet::Command) )?;
let ret=this.json_command(RStr::from(&*cmd),app).into_result()?;
let which_variant=serde_json::from_str::<WhichVariant>(&*ret)
.map_err(|e| Error::Deserialize(RBoxError::new(e),WhichCommandRet::Return) )?;
serde_json::from_str::<C::Returns>(&ret)
.map_err(|e|{
Error::unsupported_return_value(Unsupported{
plugin_name:this.plugin_id().named.clone().into_owned(),
command_name:which_variant.variant,
error:RBoxError::new(e),
supported_commands:this.list_commands(),
})
})
}

View File

@@ -0,0 +1,57 @@
use std::{fmt, marker::PhantomData};
use serde::de::{Deserialize, Deserializer, Visitor, MapAccess};
/// Used to deserialize a list of key-value pairs from a json object.
#[derive(Clone,Debug)]
pub struct VecFromMap<K,V>{
pub vec:Vec<(K,V)>,
}
struct VecFromMapVisitor<K, V> {
marker: PhantomData<(K, V)>
}
impl<K, V> VecFromMapVisitor<K, V> {
const NEW:Self=Self{marker:PhantomData};
}
impl<'de, K, V> Visitor<'de> for VecFromMapVisitor<K, V>
where
K: Deserialize<'de>,
V: Deserialize<'de>,
{
type Value = VecFromMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let cap=access.size_hint().unwrap_or(0);
let mut vec = Vec::<(K,V)>::with_capacity(cap);
while let Some(pair) = access.next_entry()? {
vec.push(pair);
}
Ok(VecFromMap{vec})
}
}
impl<'de, K, V> Deserialize<'de> for VecFromMap<K, V>
where
K: Deserialize<'de>,
V: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(VecFromMapVisitor::NEW)
}
}

View File

@@ -0,0 +1,262 @@
use crate::PluginId;
use std::{fmt::{self,Display}, str::FromStr};
use arrayvec::ArrayVec;
use abi_stable::{StableAbi, std_types::{RCow,RVec,RString,cow::BorrowingRCowStr}};
use core_extensions::{StringExt,SelfOps};
use serde::{Serialize,Deserialize,Deserializer,Serializer};
/// A way to choose to which plugins one refers to when sending commands,and other operations.
#[repr(u8)]
#[derive(Debug,Clone,PartialEq,Eq,StableAbi)]
pub enum WhichPlugin{
Id(PluginId),
First{
named:RCow<'static,str>,
},
Last{
named:RCow<'static,str>,
},
Every{
named:RCow<'static,str>,
},
Many(RVec<WhichPlugin>),
}
impl WhichPlugin{
/// Converts this `WhichPlugin` to its json representation,
/// generally used as a key in a json object.
pub fn to_key(&self)->RString{
let mut buffer=RString::new();
self.write_key(&mut buffer);
buffer
}
/// Writes the value of this as a key usable in the application config.
pub fn write_key(&self,buf:&mut RString){
use std::fmt::Write;
match self {
WhichPlugin::Id(id)=>write!(buf,"{}:{}",id.named,id.instance).drop_(),
WhichPlugin::First{named}=>write!(buf,"{}:first",named).drop_(),
WhichPlugin::Last{named}=>write!(buf,"{}:last",named).drop_(),
WhichPlugin::Every{named}=>write!(buf,"{}:every",named).drop_(),
WhichPlugin::Many(list)=>{
for elem in list {
elem.write_key(buf);
buf.push(',');
}
},
}
}
}
impl FromStr for WhichPlugin{
type Err=WhichPluginError;
fn from_str(full_str:&str)->Result<Self,WhichPluginError>{
let mut comma_sep=full_str.split(',').peekable();
let first=comma_sep.next().unwrap_or("").piped(|s|Self::parse_single(s,full_str))?;
if comma_sep.peek().is_some() {
let mut list:RVec<WhichPlugin>=vec![first].into();
for s in comma_sep.filter(|s| !s.is_empty() ) {
list.push( Self::parse_single(s,full_str)? );
}
WhichPlugin::Many(list)
}else{
first
}.piped(Ok)
}
}
impl WhichPlugin{
fn parse_single(s:&str,full_str:&str)->Result<Self,WhichPluginError>{
let splitted=s.splitn(2,':').map(|s|s.trim()).collect::<ArrayVec<[&str;2]>>();
let named=splitted.get(0)
.filter(|s| !s.is_empty() )
.ok_or_else(|| WhichPluginError(full_str.into()) )?
.to_string()
.into_(RCow::<'static,str>::T);
let selector=splitted.get(1).map_or("",|x|*x);
match selector {
"first"=>return Ok(WhichPlugin::First{named}),
""|"last"=>return Ok(WhichPlugin::Last{named}),
"all"|"every"=>return Ok(WhichPlugin::Every{named}),
_=>(),
}
let instance=selector.parse::<u64>().map_err(|_| WhichPluginError(full_str.into()) )?;
Ok(WhichPlugin::Id(PluginId{named,instance}))
}
pub const FMT_MSG:&'static str=r##"
"plugin name":
refers to the last plugin named "plugin name".
"plugin name:10":
refers to the 10th instance of the plugin named "plugin name".
"plugin name:first":
refers to the first instance of the plugin named "plugin name".
"plugin name:last":
refers to the last instance of the plugin named "plugin name".
"plugin name:every":
refers to all the instances of the plugin named "plugin name".
"plugin name 1,plugin name 2:first,plugin name 3:every":
refers to the last instance of the plugin named "plugin name 1".
refers to the first instance of the plugin named "plugin name 2".
refers to the all the instances of the plugin named "plugin name 3".
Plugin names:
- Are trimmed,so you can add spaces at the start and the end.
- Cannot contain commas,since they will be interpreted as a list of plugins.
"##;
}
impl<'de> Deserialize<'de> for WhichPlugin{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
BorrowingRCowStr::deserialize(deserializer)?
.cow
.parse::<Self>()
.map_err(de::Error::custom)
}
}
impl Serialize for WhichPlugin{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_key().serialize(serializer)
}
}
///////////////////////////////////////
#[repr(transparent)]
#[derive(Debug,Clone,StableAbi)]
pub struct WhichPluginError(RString);
impl Display for WhichPluginError{
fn fmt(&self,f:&mut fmt::Formatter)->fmt::Result{
writeln!(
f,
"Could not parse this as a `WhichPlugin`:\n\t'{}'\nExpected format:\n{}\n",
self.0,
WhichPlugin::FMT_MSG.left_padder(4),
)
}
}
#[cfg(test)]
mod tests{
use super::*;
fn new_str_expected()->Vec<(&'static str,WhichPlugin)>{
vec![
(
"plugin name",
WhichPlugin::Last{named:"plugin name".into()}
),
(
"plugin name:10",
WhichPlugin::Id(PluginId{
named:"plugin name".into(),
instance:10,
})
),
(
"plugin name:first",
WhichPlugin::First{named:"plugin name".into()}
),
(
"plugin name:last",
WhichPlugin::Last{named:"plugin name".into()}
),
(
"plugin name:every",
WhichPlugin::Every{named:"plugin name".into()}
),
(
"plugin name 1,plugin name 2:first,plugin name 3:every",
WhichPlugin::Many(vec![
WhichPlugin::Last{named:"plugin name 1".into()},
WhichPlugin::First{named:"plugin name 2".into()},
WhichPlugin::Every{named:"plugin name 3".into()},
].into())
),
]
}
#[test]
fn parses_correctly(){
let str_expected=new_str_expected();
for (str_,expected) in str_expected {
let parsed=str_.parse::<WhichPlugin>().unwrap();
assert_eq!(parsed,expected);
assert_eq!(
parsed.to_key().parse::<WhichPlugin>().unwrap(),
expected,
);
}
}
#[test]
fn serde_(){
let str_expected=new_str_expected();
for (_,elem) in str_expected {
let str_=serde_json::to_string(&elem).unwrap();
let other:WhichPlugin=serde_json::from_str(&str_)
.unwrap_or_else(|e| panic!("{}",e) );
assert_eq!(other,elem);
}
}
#[test]
fn parses_incorrectly(){
let list=vec![
// An empty plugin name is invalid
"",
":",
":first",
":last",
",",
",,,:first,:last",
];
for str_ in list {
str_.parse::<WhichPlugin>().unwrap_err();
}
}
}

View File

@@ -0,0 +1,366 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "abi_stable"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112fbc83f18cf0f12358975cac27eb4556cc5d777bde72312e0cec7a294e1699"
dependencies = [
"abi_stable_derive",
"abi_stable_shared",
"core_extensions",
"crossbeam-channel",
"generational-arena",
"libloading",
"lock_api",
"parking_lot",
"repr_offset",
"rustc_version",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "abi_stable_derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be19f4da4a4a1f2537f1f14d8f0917c07758acb7419d3bad9e9d62a11f6b5b0"
dependencies = [
"abi_stable_shared",
"as_derive_utils",
"core_extensions",
"proc-macro2",
"quote",
"rustc_version",
"syn",
"typed-arena",
]
[[package]]
name = "abi_stable_shared"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96112d6a13f37dd1cc2a2f07f7f12b7c3d176b6eec36e7987432a70fece8b48"
dependencies = [
"core_extensions",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "as_derive_utils"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc3613a62c7b739739a2cb1ee166ac275f16c5b86caf454ba21a2f79f04b025"
dependencies = [
"core_extensions",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core_extensions"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff55d62b113b98e0b0a55112741376c4cfe36f1290d18392c2c5727e09629a7f"
dependencies = [
"rustc_version",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "example_interface"
version = "0.1.0"
dependencies = [
"abi_stable",
"arrayvec",
"core_extensions",
"serde",
"serde_json",
]
[[package]]
name = "example_plugin_text_munging"
version = "0.1.0"
dependencies = [
"abi_stable",
"core_extensions",
"example_interface",
"serde",
"serde_json",
]
[[package]]
name = "generational-arena"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601"
dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "libloading"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "repr_offset"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68e33940e1f1e7ab77493ad7d983dba544ce72185c61d4b1b23e6c32dd2a165"
dependencies = [
"rustc_version",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "syn"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "typed-arena"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,16 @@
[package]
name = "example_plugin_text_munging"
version = "0.1.0"
authors = ["rodrimati1992 <rodrimatt1985@gmail.com>"]
edition = "2018"
[dependencies]
abi_stable = "0.9.1"
core_extensions={ version = "0.1.18", default_features = false, features = ["std"] }
serde={ version = "1.0.117", features = ["derive"] }
serde_json= "1.0.59"
example_interface={ version="0.1", path="../interface"}
[lib]
name = "example_plugin_text_munging"
crate-type = ["cdylib",'rlib']

View File

@@ -0,0 +1,161 @@
/*!
This is an `implementation crate`,
It exports the root module(a struct of function pointers) required by the
`example_0_interface`(the `interface crate`).
*/
use std::collections::HashSet;
use abi_stable::{
export_root_module,
sabi_extern_fn,
external_types::crossbeam_channel::RSender,
prefix_type::PrefixTypeTrait,
sabi_trait::prelude::TU_Opaque,
std_types::{RStr,RVec, RString,RResult,ROk},
};
use example_interface::{
AsyncCommand,
ApplicationMut,
CommandDescription,
Error as AppError,
Plugin,PluginType,PluginId,PluginMod,PluginMod_Ref,
Plugin_TO,
utils::process_command,
};
use core_extensions::{SelfOps,StringExt};
use serde::{Serialize,Deserialize};
///////////////////////////////////////////////////////////////////////////////////
/// Exports the root module of this library.
///
/// This code isn't run until the layout of the type it returns is checked.
#[export_root_module]
fn instantiate_root_module()->PluginMod_Ref{
PluginMod {
new,
}.leak_into_prefix()
}
//////////////////////////////////////////////////////////////////////////////////////
/// Instantiates the plugin.
#[sabi_extern_fn]
pub fn new(_sender:RSender<AsyncCommand>,plugin_id:PluginId) -> RResult<PluginType,AppError> {
let this=TextMunging{
plugin_id,
};
ROk(Plugin_TO::from_value(this,TU_Opaque))
}
//////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug,Serialize,Deserialize)]
pub enum ProcessTextCmd{
Rot13(String),
CapitalizeWords{
text:String,
words:HashSet<String>
},
}
#[derive(Debug,Serialize,Deserialize)]
pub enum ReturnValue{
Rot13(String),
CapitalizeWords(String),
}
//////////////////////////////////////////////////////////////////////////////////////
fn run_command_inner(
_this:&mut TextMunging,
command:ProcessTextCmd,
_app:ApplicationMut<'_>,
)->Result<ReturnValue,AppError>{
match command {
ProcessTextCmd::Rot13(text)=>{
pub fn rot13(this:char)-> char{
match this{
v@'a'..='z'=>
((((v as u8 - b'a')+13)%26)+b'a')as char,
v@'A'..='Z'=>
((((v as u8 - b'A')+13)%26)+b'A')as char,
v=>v
}
}
text.chars()
.map(rot13)
.collect::<String>()
.piped(ReturnValue::Rot13)
}
ProcessTextCmd::CapitalizeWords{text,words}=>{
let mut buffer=String::with_capacity(10);
for kv in text.split_while(|c|c.is_alphabetic()) {
let str_=kv.str;
let is_a_word=kv.key;
if is_a_word && words.contains(str_) {
buffer.extend(str_.chars().flat_map(char::to_uppercase));
}else{
buffer.push_str(str_);
}
}
ReturnValue::CapitalizeWords(buffer)
}
}.piped(Ok)
}
//////////////////////////////////////////////////////////////////////////////////////
struct TextMunging{
plugin_id:PluginId,
}
impl Plugin for TextMunging {
fn json_command(
&mut self,
command: RStr<'_>,
app:ApplicationMut<'_>,
)->RResult<RString,AppError>{
process_command(self,command,|this,command:ProcessTextCmd|{
run_command_inner(this,command,app)
})
}
fn plugin_id(&self)->&PluginId{
&self.plugin_id
}
fn list_commands(&self)->RVec<CommandDescription>{
vec![
CommandDescription::from_literals(
"Rot13",
"Uses the rot13 algorithm to hide spoilers in plain text."
),
CommandDescription::from_literals(
"CapitalizeWords",
"Capitalizes the words specified in words.\n\
\n\
Command parans:\n\
{\"text\":\"text here\",words:[\"word0\",\"word1\"]}",
),
].into()
}
fn close(self,_app:ApplicationMut<'_>){}
}

View File

@@ -0,0 +1,366 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "abi_stable"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112fbc83f18cf0f12358975cac27eb4556cc5d777bde72312e0cec7a294e1699"
dependencies = [
"abi_stable_derive",
"abi_stable_shared",
"core_extensions",
"crossbeam-channel",
"generational-arena",
"libloading",
"lock_api",
"parking_lot",
"repr_offset",
"rustc_version",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "abi_stable_derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be19f4da4a4a1f2537f1f14d8f0917c07758acb7419d3bad9e9d62a11f6b5b0"
dependencies = [
"abi_stable_shared",
"as_derive_utils",
"core_extensions",
"proc-macro2",
"quote",
"rustc_version",
"syn",
"typed-arena",
]
[[package]]
name = "abi_stable_shared"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96112d6a13f37dd1cc2a2f07f7f12b7c3d176b6eec36e7987432a70fece8b48"
dependencies = [
"core_extensions",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "as_derive_utils"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc3613a62c7b739739a2cb1ee166ac275f16c5b86caf454ba21a2f79f04b025"
dependencies = [
"core_extensions",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core_extensions"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff55d62b113b98e0b0a55112741376c4cfe36f1290d18392c2c5727e09629a7f"
dependencies = [
"rustc_version",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "example_interface"
version = "0.1.0"
dependencies = [
"abi_stable",
"arrayvec",
"core_extensions",
"serde",
"serde_json",
]
[[package]]
name = "example_plugin_utils"
version = "0.1.0"
dependencies = [
"abi_stable",
"core_extensions",
"example_interface",
"serde",
"serde_json",
]
[[package]]
name = "generational-arena"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601"
dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "libloading"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "repr_offset"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68e33940e1f1e7ab77493ad7d983dba544ce72185c61d4b1b23e6c32dd2a165"
dependencies = [
"rustc_version",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "syn"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "typed-arena"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,16 @@
[package]
name = "example_plugin_utils"
version = "0.1.0"
authors = ["rodrimati1992 <rodrimatt1985@gmail.com>"]
edition = "2018"
[dependencies]
abi_stable = "0.9.1"
core_extensions={ version = "0.1.18", default_features = false, features = ["std"] }
serde={ version = "1.0.117", features = ["derive"] }
serde_json = { version = "1.0.59", features = ["raw_value"] }
example_interface={ version="0.1", path="../interface"}
[lib]
name = "example_plugin_utils"
crate-type = ["cdylib",'rlib']

View File

@@ -0,0 +1,174 @@
/*!
This is an `implementation crate`,
It exports the root module(a struct of function pointers) required by the
`example_0_interface`(the `interface crate`).
*/
use abi_stable::{
export_root_module,
sabi_extern_fn,
external_types::crossbeam_channel::RSender,
prefix_type::PrefixTypeTrait,
sabi_trait::prelude::TU_Opaque,
std_types::{RStr,RVec, RString,RResult,ROk},
};
use example_interface::{
AsyncCommand,
ApplicationMut,
CommandDescription,
Error as AppError,
Plugin,PluginType,PluginId,PluginMod,PluginMod_Ref,
Plugin_TO,
utils::process_command,
WhichPlugin,
};
use core_extensions::SelfOps;
use serde::{Serialize, Deserialize};
use serde_json::value::Value;
///////////////////////////////////////////////////////////////////////////////////
/// Exports the root module of this library.
///
/// This code isn't run until the layout of the type it returns is checked.
#[export_root_module]
fn instantiate_root_module()->PluginMod_Ref{
PluginMod {
new,
}.leak_into_prefix()
}
//////////////////////////////////////////////////////////////////////////////////////
#[sabi_extern_fn]
pub fn new(_sender:RSender<AsyncCommand>,plugin_id:PluginId) -> RResult<PluginType,AppError> {
let this=CommandUtils{
plugin_id,
};
ROk(Plugin_TO::from_value(this,TU_Opaque))
}
//////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug,Serialize,Deserialize)]
pub enum UtilCmd{
Repeat{
how_much:usize,
plugin:WhichPlugin,
command:Value,
},
Batch{
plugin:WhichPlugin,
commands:Vec<Value>,
},
}
#[derive(Debug,Serialize,Deserialize)]
pub enum ReturnValue{
Repeat,
Batch,
}
//////////////////////////////////////////////////////////////////////////////////////
fn run_command_inner(
this:&mut CommandUtils,
command:UtilCmd,
mut app:ApplicationMut<'_>,
)->Result<ReturnValue,AppError>{
match command {
UtilCmd::Repeat{how_much,plugin,command}=>{
let s=serde_json::to_string(&command).unwrap();
for _ in 0..how_much {
app.send_command_to_plugin(
this.plugin_id(),
plugin.clone(),
s.clone().into(),
).into_result()?;
}
ReturnValue::Repeat
}
UtilCmd::Batch{plugin,commands}=>{
for command in commands {
let s=serde_json::to_string(&command).unwrap();
app.send_command_to_plugin(
this.plugin_id(),
plugin.clone(),
s.into(),
).into_result()?;
}
ReturnValue::Batch
}
}.piped(Ok)
}
//////////////////////////////////////////////////////////////////////////////////////
struct CommandUtils{
plugin_id:PluginId,
}
impl Plugin for CommandUtils {
fn json_command(
&mut self,
command: RStr<'_>,
app:ApplicationMut<'_>,
)->RResult<RString,AppError>{
process_command(self,command,|this,command|{
run_command_inner(this,command,app)
})
}
fn plugin_id(&self)->&PluginId{
&self.plugin_id
}
fn list_commands(&self)->RVec<CommandDescription>{
vec![
CommandDescription::from_literals(
"Repeat",
"\
Sends a command to a plugin N times.
Command params:
{
\"how_much\":10,
\"plugin\":\"plugin:last\",
\"command\":{ ... some command ... }
}
"
),
CommandDescription::from_literals(
"Batch",
"\
Sends a sequence of commands to a plugin.
Command params:
{
\"plugin\":\"plugin_name\",
\"commands\":[
{ ... some command ... },
{ ... some command ... },
{ ... some command ... }
]
}",
),
].into()
}
fn close(self,_app:ApplicationMut<'_>){}
}