extism 1.0.0.pre.rc.1 → 1.0.0.pre.rc.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/Gemfile +16 -16
- data/Gemfile.lock +2 -1
- data/README.md +169 -191
- data/Rakefile +12 -12
- data/lib/extism/current_plugin.rb +35 -7
- data/lib/extism/host_environment.rb +36 -5
- data/lib/extism/manifest.rb +68 -0
- data/lib/extism/plugin.rb +25 -2
- data/lib/extism/version.rb +5 -5
- data/lib/extism/wasm.rb +16 -1
- data/lib/extism.rb +59 -38
- data/wasm/count_vowels.wasm +0 -0
- data/wasm/count_vowels_kvstore.wasm +0 -0
- data/wasm/reflect.wasm +0 -0
- data/wasm/store_credit.wasm +0 -0
- metadata +4 -3
- data/example.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff531578509d2c71f3db9d20e01fdbbba922e342f76dbb84aafa69ce94db3350
|
4
|
+
data.tar.gz: 163c31c8cddeec68fd774af387effcbd13e3a8e9ea40ab26445ab94fa6db75a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5b97afbbeddff3e52a0627832fa92b860ef921cee1b82d0f1a4753b2b9a47ae89810e2f2c8b061f94aff9fa639283a7886d06026e3c98a588693f86369cbdb1
|
7
|
+
data.tar.gz: 5399c2a2d9f0183bad275c212f7009b73ef9234ddc2cc4a90433972a3decb36926c967fd3935f20e58f2cb18966c9e76546e967db4882ecc01936eb0593092ab
|
data/.yardopts
CHANGED
data/Gemfile
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
source 'https://rubygems.org'
|
4
|
-
|
5
|
-
# Specify your gem's dependencies in extism.gemspec
|
6
|
-
gemspec
|
7
|
-
|
8
|
-
gem 'ffi', '~> 1.15.5'
|
9
|
-
gem 'rake', '~> 13.0'
|
10
|
-
|
11
|
-
group :development do
|
12
|
-
gem 'debug'
|
13
|
-
gem 'minitest', '~> 5.20.0'
|
14
|
-
gem 'rufo', '~> 0.13.0'
|
15
|
-
gem 'yard', '~> 0.9.28'
|
16
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in extism.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'ffi', '~> 1.15.5'
|
9
|
+
gem 'rake', '~> 13.0'
|
10
|
+
|
11
|
+
group :development do
|
12
|
+
gem 'debug'
|
13
|
+
gem 'minitest', '~> 5.20.0'
|
14
|
+
gem 'rufo', '~> 0.13.0'
|
15
|
+
gem 'yard', '~> 0.9.28'
|
16
|
+
end
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,191 +1,169 @@
|
|
1
|
-
# Extism Ruby Host SDK
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
This repo
|
6
|
-
|
7
|
-
## Installation
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
```
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
```
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
|
29
|
-
```
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
plugin.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
```ruby
|
171
|
-
env = Environment.new
|
172
|
-
plugin = Extism::Plugin.new(manifest, environment: env)
|
173
|
-
```
|
174
|
-
|
175
|
-
Now we can invoke the event:
|
176
|
-
|
177
|
-
```ruby
|
178
|
-
event = {
|
179
|
-
event_type: 'charge.succeeded',
|
180
|
-
customer: CUSTOMER
|
181
|
-
}
|
182
|
-
result = plugin.call('on_charge_succeeded', JSON.generate(event))
|
183
|
-
```
|
184
|
-
|
185
|
-
This will print:
|
186
|
-
|
187
|
-
```
|
188
|
-
Adding Credit {"amount_in_cents"=>1000, "currency"=>"USD"} for customer abcd1234
|
189
|
-
Sending email {"subject"=>"A gift for you John Smith", "body"=>"You have received $10 in store credi
|
190
|
-
t!"} to customer abcd1234
|
191
|
-
```
|
1
|
+
# Extism Ruby Host SDK
|
2
|
+
|
3
|
+
This repo contains the ruby gem for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host ruby application to run Extism plug-ins.
|
4
|
+
|
5
|
+
> **Note**: This repo is 1.0 alpha version of the Ruby SDK and we may push breaking changes in between versions until we hit 1.0.0 in December, 2023. However, it is ready to use and you should use this if you're building a new integration. We'd love any feedback on it.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
### Install the Extism Runtime Dependency
|
10
|
+
|
11
|
+
For this library, you first need to install the Extism Runtime. You can [download the shared object directly from a release](https://github.com/extism/extism/releases) or use the [Extism CLI](https://github.com/extism/cli) to install it:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
sudo extism lib install latest
|
15
|
+
|
16
|
+
#=> Fetching https://github.com/extism/extism/releases/download/v0.5.2/libextism-aarch64-apple-darwin-v0.5.2.tar.gz
|
17
|
+
#=> Copying libextism.dylib to /usr/local/lib/libextism.dylib
|
18
|
+
#=> Copying extism.h to /usr/local/include/extism.h
|
19
|
+
```
|
20
|
+
|
21
|
+
> **Note**: This library has breaking changes and targets 1.0 of the runtime. For the time being, install the runtime from our nightly development builds on git: `sudo extism lib install --version git`.
|
22
|
+
|
23
|
+
### Install the Gem
|
24
|
+
|
25
|
+
Add this library to your [Gemfile](https://bundler.io/):
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'extism', '1.0.0.pre.rc.3'
|
29
|
+
```
|
30
|
+
|
31
|
+
Or if installing on the system level:
|
32
|
+
|
33
|
+
```
|
34
|
+
gem install extism --pre
|
35
|
+
```
|
36
|
+
|
37
|
+
## Getting Started
|
38
|
+
|
39
|
+
This guide should walk you through some of the concepts in Extism and this ruby library.
|
40
|
+
|
41
|
+
> *Note*: You should be able to follow this guide by copy pasting the code into `irb`.
|
42
|
+
|
43
|
+
### Creating A Plug-in
|
44
|
+
|
45
|
+
The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file.
|
46
|
+
|
47
|
+
Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# First require the library
|
51
|
+
require "extism"
|
52
|
+
|
53
|
+
url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
|
54
|
+
manifest = Extism::Manifest.from_url url
|
55
|
+
plugin = Extism::Plugin.new(manifest)
|
56
|
+
```
|
57
|
+
|
58
|
+
> **Note**: See [the Manifest docs](https://extism.github.io/ruby-sdk/Extism/Manifest.html) as it has a rich schema and a lot of options.
|
59
|
+
|
60
|
+
### Calling A Plug-in's Exports
|
61
|
+
|
62
|
+
This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Extism::Plugin#call](https://extism.github.io/ruby-sdk/Extism/Plugin.html#call-instance_method):
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
plugin.call("count_vowels", "Hello, World!")
|
66
|
+
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
|
67
|
+
```
|
68
|
+
|
69
|
+
All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results.
|
70
|
+
|
71
|
+
### Plug-in State
|
72
|
+
|
73
|
+
Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
plugin.call("count_vowels", "Hello, World!")
|
77
|
+
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
|
78
|
+
plugin.call("count_vowels", "Hello, World!")
|
79
|
+
# => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
|
80
|
+
```
|
81
|
+
|
82
|
+
These variables will persist until this plug-in is freed or you initialize a new one.
|
83
|
+
|
84
|
+
### Configuration
|
85
|
+
|
86
|
+
Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
plugin = Extism::Plugin.new(manifest)
|
90
|
+
plugin.call("count_vowels", "Yellow, World!")
|
91
|
+
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
|
92
|
+
|
93
|
+
plugin = Extism::Plugin.new(manifest, config: { vowels: "aeiouyAEIOUY" })
|
94
|
+
plugin.call("count_vowels", "Yellow, World!")
|
95
|
+
# => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"}
|
96
|
+
```
|
97
|
+
|
98
|
+
### Host Functions
|
99
|
+
|
100
|
+
Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store!
|
101
|
+
|
102
|
+
Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in.
|
103
|
+
|
104
|
+
[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some ruby methods you write which can be passed down and invoked from any language inside the plug-in.
|
105
|
+
|
106
|
+
Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"
|
110
|
+
manifest = Extism::Manifest.from_url(url)
|
111
|
+
```
|
112
|
+
|
113
|
+
> *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.
|
114
|
+
|
115
|
+
Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store.
|
116
|
+
|
117
|
+
In the ruby sdk, we have a concept for this called a [Host Environment](https://extism.github.io/ruby-sdk/Extism/HostEnvironment.html). An environment is an instance of a class that implements any host functions your plug-in needs.
|
118
|
+
|
119
|
+
We want to expose two functions to our plugin, `kv_write(key: String, value: Bytes)` which writes a bytes value to a key and `kv_read(key: String) -> Bytes` which reads the bytes at the given `key`.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
# pretend this is Redis or something :)
|
123
|
+
KV_STORE = {}
|
124
|
+
|
125
|
+
class KvEnvironment
|
126
|
+
include Extism::HostEnvironment
|
127
|
+
|
128
|
+
# We need to describe the wasm function signature of each host function
|
129
|
+
# to register them to this environment
|
130
|
+
register_import :kv_read, [Extism::ValType::PTR], [Extism::ValType::PTR]
|
131
|
+
register_import :kv_write, [Extism::ValType::PTR, Extism::ValType::PTR], []
|
132
|
+
|
133
|
+
def kv_read(plugin, inputs, outputs, _user_data)
|
134
|
+
key = plugin.input_as_string(inputs.first)
|
135
|
+
val = KV_STORE[key] || [0].pack('V') # get 4 LE bytes for 0 default
|
136
|
+
puts "Read from key=#{key}"
|
137
|
+
plugin.output_string(outputs.first, val)
|
138
|
+
end
|
139
|
+
|
140
|
+
def kv_write(plugin, inputs, _outputs, _user_data)
|
141
|
+
key = plugin.input_as_string(inputs.first)
|
142
|
+
val = plugin.input_as_string(inputs[1])
|
143
|
+
puts "Writing value=#{val.unpack1('V')} from key=#{key}"
|
144
|
+
KV_STORE[key] = val
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
> *Note*: In order to write host functions you should get familiar with the methods on the [Extism::CurrentPlugin](https://extism.github.io/ruby-sdk/Extism/CurrentPlugin.html) class. The `plugin` parameter is an instance of this class.
|
150
|
+
|
151
|
+
Now we just need to create a new host environment and pass it in when loading the plug-in. Here our environment initializer takes no arguments, but you could imagine putting some customer specific instance variables in there:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
env = KvEnvironment.new
|
155
|
+
plugin = Extism::Plugin.new(manifest, environment: env)
|
156
|
+
```
|
157
|
+
|
158
|
+
Now we can invoke the event:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
plugin.call("count_vowels", "Hello, World!")
|
162
|
+
# => Read from key=count-vowels"
|
163
|
+
# => Writing value=3 from key=count-vowels"
|
164
|
+
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
|
165
|
+
plugin.call("count_vowels", "Hello, World!")
|
166
|
+
# => Read from key=count-vowels"
|
167
|
+
# => Writing value=6 from key=count-vowels"
|
168
|
+
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
|
169
|
+
```
|
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
require "rake/testtask"
|
5
|
-
|
6
|
-
Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs << "test"
|
8
|
-
t.libs << "lib"
|
9
|
-
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
-
end
|
11
|
-
|
12
|
-
task default: :test
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
task default: :test
|
@@ -16,22 +16,34 @@ module Extism
|
|
16
16
|
|
17
17
|
# Allocates a memory block in the plugin
|
18
18
|
#
|
19
|
-
# @
|
19
|
+
# @example Allocate 1kB
|
20
|
+
# mem = current_plugin.alloc(1_024)
|
21
|
+
# current_plugin.free(mem)
|
22
|
+
#
|
23
|
+
# @param num_bytes [Integer] The amount in bytes to allocate
|
20
24
|
# @return [Extism::Memory] The reference to the freshly allocated memory
|
21
|
-
def alloc(
|
22
|
-
offset = LibExtism.extism_current_plugin_memory_alloc(@ptr,
|
23
|
-
Memory.new(offset,
|
25
|
+
def alloc(num_bytes)
|
26
|
+
offset = LibExtism.extism_current_plugin_memory_alloc(@ptr, num_bytes)
|
27
|
+
Memory.new(offset, num_bytes)
|
24
28
|
end
|
25
29
|
|
26
30
|
# Frees the memory block
|
27
31
|
#
|
32
|
+
# @example
|
33
|
+
# mem = current_plugin.alloc(1_024)
|
34
|
+
# current_plugin.free(mem)
|
35
|
+
#
|
28
36
|
# @param memory [Extism::Memory] The memory object you wish to free
|
29
37
|
# @return [Extism::Memory] The reference to the freshly allocated memory
|
30
38
|
def free(memory)
|
31
39
|
LibExtism.extism_current_plugin_memory_free(@ptr, memory.offset)
|
32
40
|
end
|
33
41
|
|
34
|
-
# Gets the memory block at a given offset
|
42
|
+
# Gets the memory block at a given offset. Note: try to use input_* and output_* methods where possible.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# mem = current_plugin.memory_at_offset(123456789)
|
46
|
+
# current_plugin.free(mem)
|
35
47
|
#
|
36
48
|
# @raise [Extism::Error] if memory block could not be found
|
37
49
|
#
|
@@ -48,6 +60,10 @@ module Extism
|
|
48
60
|
#
|
49
61
|
# @raise [Extism::Error] if memory block could not be found
|
50
62
|
#
|
63
|
+
# @example
|
64
|
+
# param1 = current_plugin.input_as_string(inputs.first)
|
65
|
+
# raise "Failed" unless param1 == "First param from plug-in host function call"
|
66
|
+
#
|
51
67
|
# @param input [Extism::Val] The input val from the host function
|
52
68
|
# @return [String] raw bytes as a string
|
53
69
|
def input_as_string(input)
|
@@ -57,10 +73,14 @@ module Extism
|
|
57
73
|
memory_ptr(mem).read_bytes(mem.len)
|
58
74
|
end
|
59
75
|
|
60
|
-
# Gets the input as a
|
76
|
+
# Gets the input as a JSON parsed Hash
|
61
77
|
#
|
62
78
|
# @raise [Extism::Error] if memory block could not be found
|
63
79
|
#
|
80
|
+
# @example
|
81
|
+
# param1 = current_plugin.input_as_json(inputs.first)
|
82
|
+
# raise "Failed" unless param1 == {hello: "world"}
|
83
|
+
#
|
64
84
|
# @param input [Extism::Val] The input val from the host function
|
65
85
|
# @return [Hash] The Hash object
|
66
86
|
def input_as_json(input)
|
@@ -73,6 +93,10 @@ module Extism
|
|
73
93
|
|
74
94
|
# Sets string to the return of the host function
|
75
95
|
#
|
96
|
+
# @example
|
97
|
+
# msg = "A string returned from the host function"
|
98
|
+
# current_plugin.output_string(outputs.first, msg)
|
99
|
+
#
|
76
100
|
# @raise [Extism::Error] if memory block could not be found
|
77
101
|
#
|
78
102
|
# @param output [Extism::Val] The output val from the host function
|
@@ -85,6 +109,10 @@ module Extism
|
|
85
109
|
|
86
110
|
# Sets json to the return of the host function
|
87
111
|
#
|
112
|
+
# @example
|
113
|
+
# msg = {hello: "world"}
|
114
|
+
# current_plugin.output_json(outputs.first, msg)
|
115
|
+
#
|
88
116
|
# @raise [Extism::Error] if memory block could not be found
|
89
117
|
#
|
90
118
|
# @param output [Extism::Val] The output val from the host function
|
@@ -114,7 +142,7 @@ module Extism
|
|
114
142
|
private
|
115
143
|
|
116
144
|
# Returns a raw pointer (absolute to the host) to the given memory block
|
117
|
-
# Be careful with this. it's not exposed for a reason.
|
145
|
+
# **Danger**: Be careful with this. it's not exposed for a reason.
|
118
146
|
# This is a pointer in host memory so it could read outside of the plugin
|
119
147
|
# if manipulated
|
120
148
|
def memory_ptr(mem)
|
@@ -1,10 +1,32 @@
|
|
1
1
|
module Extism
|
2
|
+
# Represents an "environment" that can be imported to a plug-in
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# class MyEnvironment
|
6
|
+
# include Extism::HostEnvironment
|
7
|
+
# # we need to register each import that the plug-in expects and match the Wasm signature
|
8
|
+
# # register_import takes the name, the param types, and the return types
|
9
|
+
# register_import :reflect, [Extism::ValType::I64], [Extism::ValType::I64]
|
10
|
+
#
|
11
|
+
# # reflect just takes a string from the plug-in and reflects it back in return
|
12
|
+
# def reflect(plugin, inputs, outputs, _user_data)
|
13
|
+
# msg = plugin.input_as_string(inputs.first)
|
14
|
+
# plugin.output_string(outputs.first, msg)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
2
18
|
module HostEnvironment
|
3
19
|
def self.included(base)
|
4
20
|
base.extend ClassMethods
|
5
21
|
base.class_variable_set(:@@import_funcs, [])
|
6
22
|
end
|
7
23
|
|
24
|
+
# Creates the host functions to pass to the plug-in on intialization.
|
25
|
+
# Used internally by the Plugin initializer
|
26
|
+
#
|
27
|
+
# @see Extism::Plugin::new
|
28
|
+
#
|
29
|
+
# @return [Array<Extism::Function>]
|
8
30
|
def host_functions
|
9
31
|
import_funcs = self.class.class_variable_get(:@@import_funcs)
|
10
32
|
import_funcs.map do |f|
|
@@ -17,12 +39,21 @@ module Extism
|
|
17
39
|
)
|
18
40
|
end
|
19
41
|
end
|
20
|
-
end
|
21
42
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
43
|
+
module ClassMethods
|
44
|
+
# Register an import by name. You must know the wasm signature
|
45
|
+
# of the function to do this.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# register_import :my_func, [Extism::ValType::I64], [Extism::ValType::F64]
|
49
|
+
#
|
50
|
+
# @param [Symbol | String] func_name The name of the wasm import function. Assumes `env` namespace.
|
51
|
+
# @param [Array<Extism::ValType>] parameters The Wasm types of the parameters that the import takes
|
52
|
+
# @param [Array<Extism::ValType>] returns The Wasm types of the returns that the import returns. Will usually be just be one of these.
|
53
|
+
def register_import(func_name, parameters, returns)
|
54
|
+
import_funcs = class_variable_get(:@@import_funcs)
|
55
|
+
import_funcs << [func_name, parameters, returns]
|
56
|
+
end
|
26
57
|
end
|
27
58
|
end
|
28
59
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Extism
|
2
|
+
# The manifest represents a recipe to build a plug-in.
|
3
|
+
# It generally consists of a path to one wasm module
|
4
|
+
# but could contain more. It also helps you define some
|
5
|
+
# options and restrictions on the runtime behavior of the plug-in.
|
6
|
+
# See https://extism.org/docs/concepts/manifest for more info.
|
7
|
+
class Manifest
|
8
|
+
attr_reader :manifest_data
|
9
|
+
|
10
|
+
# Create a manifest of a single wasm from url.
|
11
|
+
# Look at {Manifest#initialize} for an interface with more control
|
12
|
+
#
|
13
|
+
# @see Manifest::initialize
|
14
|
+
# @param [String] url The url to the wasm module
|
15
|
+
# @param [String | nil] hash An optional sha256 integrity hash. Defaults to nil
|
16
|
+
# @param [String | nil] name An optional name. Defaults to nil
|
17
|
+
# @return [Extism::Manifest]
|
18
|
+
def self.from_url(url, hash: nil, name: nil)
|
19
|
+
wasm = { url: url }
|
20
|
+
wasm[:hash] = hash unless hash.nil?
|
21
|
+
wasm[:name] = name unless hash.nil?
|
22
|
+
|
23
|
+
Manifest.new({ wasm: [wasm] })
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create a manifest of a single wasm from file path.
|
27
|
+
# Look at {Manifest#initialize} for an interface with more control
|
28
|
+
#
|
29
|
+
# @see Manifest::initialize
|
30
|
+
# @param [String] path The path to the wasm module on disk
|
31
|
+
# @param [String | nil] hash An optional sha256 integrity hash. Defaults to nil
|
32
|
+
# @param [String | nil] name An optional name. Defaults to nil
|
33
|
+
# @return [Extism::Manifest]
|
34
|
+
def self.from_path(path, hash: nil, name: nil)
|
35
|
+
wasm = { path: path }
|
36
|
+
wasm[:hash] = hash unless hash.nil?
|
37
|
+
wasm[:name] = name unless hash.nil?
|
38
|
+
|
39
|
+
Manifest.new({ wasm: [wasm] })
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a manifest of a single wasm module with raw binary data.
|
43
|
+
# Look at {Manifest#initialize} for an interface with more control
|
44
|
+
# Consider using a file path instead of the raw wasm binary in memory.
|
45
|
+
# The performance is often better letting the runtime load the binary itself.
|
46
|
+
#
|
47
|
+
# @see Manifest::initialize
|
48
|
+
# @param [String] data The binary data of the wasm module
|
49
|
+
# @param [String | nil] hash An optional sha256 integrity hash. Defaults to nil
|
50
|
+
# @param [String | nil] name An optional name. Defaults to nil
|
51
|
+
# @return [Extism::Manifest]
|
52
|
+
def self.from_bytes(data, hash: nil, name: nil)
|
53
|
+
wasm = { data: data }
|
54
|
+
wasm[:hash] = hash unless hash.nil?
|
55
|
+
wasm[:name] = name unless hash.nil?
|
56
|
+
|
57
|
+
Manifest.new({ wasm: [wasm] })
|
58
|
+
end
|
59
|
+
|
60
|
+
# Initialize a manifest
|
61
|
+
# See https://extism.org/docs/concepts/manifest for schema
|
62
|
+
#
|
63
|
+
# @param [Hash] data The Hash data that conforms the Manifest schema
|
64
|
+
def initialize(data)
|
65
|
+
@manifest_data = data
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/extism/plugin.rb
CHANGED
@@ -4,11 +4,29 @@ module Extism
|
|
4
4
|
class Plugin
|
5
5
|
# Intialize a plugin
|
6
6
|
#
|
7
|
-
# @
|
7
|
+
# @example Initialize a plugin from a url
|
8
|
+
# manifest = Extism::Manifest.from_url "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
|
9
|
+
# plugin = Extism::Plugin.new(manifest)
|
10
|
+
#
|
11
|
+
# @example Pass a config object to configure the plug-in
|
12
|
+
# plugin = Extism::Plugin.new(manifest, config: { hello: "world" })
|
13
|
+
#
|
14
|
+
# @example Initalize a plug-in that needs WASI
|
15
|
+
# plugin = Extism::Plugin.new(manifest, wasi: true)
|
16
|
+
#
|
17
|
+
# @param wasm [Hash, String, Manifest] The manifest as a Hash or WASM binary as a String. See https://extism.org/docs/concepts/manifest/.
|
8
18
|
# @param wasi [Boolean] Enable WASI support
|
9
19
|
# @param config [Hash] The plugin config
|
10
20
|
def initialize(wasm, environment: nil, functions: [], wasi: false, config: nil)
|
11
|
-
wasm =
|
21
|
+
wasm = case wasm
|
22
|
+
when Hash
|
23
|
+
JSON.generate(wasm)
|
24
|
+
when Manifest
|
25
|
+
JSON.generate(wasm.manifest_data)
|
26
|
+
else
|
27
|
+
wasm
|
28
|
+
end
|
29
|
+
|
12
30
|
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
13
31
|
errmsg = FFI::MemoryPointer.new(:pointer)
|
14
32
|
code.put_bytes(0, wasm)
|
@@ -46,6 +64,11 @@ module Extism
|
|
46
64
|
|
47
65
|
# Call a function by name
|
48
66
|
#
|
67
|
+
# @example
|
68
|
+
# input = JSON.generate({hello: "world"})
|
69
|
+
# result = plugin.call("my_func", input)
|
70
|
+
# output = JSON.parse(result)
|
71
|
+
#
|
49
72
|
# @param name [String] The function name
|
50
73
|
# @param data [String] The input data for the function
|
51
74
|
# @return [String] The output from the function in String form
|
data/lib/extism/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Extism
|
4
|
-
VERSION = '1.0.0-rc.
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Extism
|
4
|
+
VERSION = '1.0.0-rc.3'
|
5
|
+
end
|
data/lib/extism/wasm.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
module Extism
|
2
|
+
# Extism specific values for Wasm types. Useful when you need to describe
|
3
|
+
# something in pure wasm like host function signatures.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# register_import :hostfunc, [Extism::ValType::I32, Extism::ValType::F64], [Extism::ValType::I64]
|
2
7
|
module ValType
|
3
8
|
I32 = 0
|
4
9
|
I64 = 1
|
10
|
+
PTR = 1
|
5
11
|
F32 = 2
|
6
12
|
F64 = 3
|
7
13
|
V128 = 4
|
@@ -9,6 +15,7 @@ module Extism
|
|
9
15
|
EXTERN_REF = 6
|
10
16
|
end
|
11
17
|
|
18
|
+
# A raw Wasm value. Contains the type and the data
|
12
19
|
class Val
|
13
20
|
def initialize(ptr)
|
14
21
|
@c_val = LibExtism::ExtismVal.new(ptr)
|
@@ -20,6 +27,8 @@ module Extism
|
|
20
27
|
:i32
|
21
28
|
when :I64
|
22
29
|
:i64
|
30
|
+
when :PTR
|
31
|
+
:i64
|
23
32
|
when :F32
|
24
33
|
:f32
|
25
34
|
when :F64
|
@@ -50,7 +59,10 @@ module Extism
|
|
50
59
|
end
|
51
60
|
end
|
52
61
|
|
53
|
-
# Represents a host function
|
62
|
+
# Represents a host function. This is mostly for internal use and you should
|
63
|
+
# try to use HostEnvironment instead
|
64
|
+
#
|
65
|
+
# @see Extism::HostEnvironment
|
54
66
|
class Function
|
55
67
|
# Create a new host function
|
56
68
|
#
|
@@ -81,6 +93,9 @@ module Extism
|
|
81
93
|
returns = LibExtism.from_int_array(@returns)
|
82
94
|
@_pointer = LibExtism.extism_function_new(@name, args, @params.length, returns, @returns.length, c_func, free,
|
83
95
|
nil)
|
96
|
+
$FUNCTIONS[object_id] = { function: @_pointer}
|
97
|
+
ObjectSpace.define_finalizer(self, $FREE_FUNCTION)
|
98
|
+
return @_pointer
|
84
99
|
end
|
85
100
|
|
86
101
|
def c_func
|
data/lib/extism.rb
CHANGED
@@ -1,38 +1,59 @@
|
|
1
|
-
require 'ffi'
|
2
|
-
require 'json'
|
3
|
-
require_relative './extism/
|
4
|
-
require_relative './extism/
|
5
|
-
require_relative './extism/
|
6
|
-
require_relative './extism/
|
7
|
-
require_relative './extism/
|
8
|
-
require_relative './extism/
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
#
|
23
|
-
# @param
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
$
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
1
|
+
require 'ffi'
|
2
|
+
require 'json'
|
3
|
+
require_relative './extism/manifest'
|
4
|
+
require_relative './extism/version'
|
5
|
+
require_relative './extism/plugin'
|
6
|
+
require_relative './extism/current_plugin'
|
7
|
+
require_relative './extism/libextism'
|
8
|
+
require_relative './extism/wasm'
|
9
|
+
require_relative './extism/host_environment'
|
10
|
+
|
11
|
+
module Extism
|
12
|
+
class Error < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return the version of Extism
|
16
|
+
#
|
17
|
+
# @return [String] The version string of the Extism runtime
|
18
|
+
def self.extism_version
|
19
|
+
LibExtism.extism_version
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set log file and level, this is a global configuration
|
23
|
+
# @param name [String] The path to the logfile
|
24
|
+
# @param level [String] The log level. One of {"debug", "error", "info", "trace" }
|
25
|
+
def self.set_log_file(name, level = nil)
|
26
|
+
LibExtism.extism_log_file(name, level)
|
27
|
+
end
|
28
|
+
|
29
|
+
$PLUGINS = {}
|
30
|
+
$FREE_PLUGIN = proc { |ptr|
|
31
|
+
x = $PLUGINS[ptr]
|
32
|
+
unless x.nil?
|
33
|
+
LibExtism.extism_plugin_free(x[:plugin])
|
34
|
+
$PLUGINS.delete(ptr)
|
35
|
+
end
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
$FUNCTIONS = {}
|
40
|
+
$FREE_FUNCTION = proc { |ptr|
|
41
|
+
x = $FUNCTIONS[ptr]
|
42
|
+
unless x.nil?
|
43
|
+
LibExtism.extism_function_free(x[:function])
|
44
|
+
$FUNCTIONS.delete(ptr)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
# Represents a "block" of memory in Extism.
|
49
|
+
# This memory is in the communication buffer b/w the
|
50
|
+
# guest in the host and technically lives in host memory.
|
51
|
+
class Memory
|
52
|
+
attr_reader :offset, :len
|
53
|
+
|
54
|
+
def initialize(offset, len)
|
55
|
+
@offset = offset
|
56
|
+
@len = len
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/wasm/count_vowels.wasm
CHANGED
Binary file
|
Binary file
|
data/wasm/reflect.wasm
CHANGED
Binary file
|
data/wasm/store_credit.wasm
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extism
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.rc.
|
4
|
+
version: 1.0.0.pre.rc.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zach
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -38,16 +38,17 @@ files:
|
|
38
38
|
- Makefile
|
39
39
|
- README.md
|
40
40
|
- Rakefile
|
41
|
-
- example.rb
|
42
41
|
- lib/extism.rb
|
43
42
|
- lib/extism/current_plugin.rb
|
44
43
|
- lib/extism/host_environment.rb
|
45
44
|
- lib/extism/libextism.rb
|
45
|
+
- lib/extism/manifest.rb
|
46
46
|
- lib/extism/plugin.rb
|
47
47
|
- lib/extism/version.rb
|
48
48
|
- lib/extism/wasm.rb
|
49
49
|
- sig/extism.rbs
|
50
50
|
- wasm/count_vowels.wasm
|
51
|
+
- wasm/count_vowels_kvstore.wasm
|
51
52
|
- wasm/reflect.wasm
|
52
53
|
- wasm/store_credit.wasm
|
53
54
|
homepage: https://github.com/extism/extism
|
data/example.rb
DELETED