extism 0.0.1.rc4 → 0.0.1.rc6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/GETTING_STARTED.md +35 -0
- data/Gemfile +6 -1
- data/Makefile +28 -0
- data/Rakefile +9 -0
- data/example.rb +9 -8
- data/lib/extism/version.rb +1 -1
- data/lib/extism.rb +124 -62
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da418ff834d018a2db1264a41bb5a1492ee9ed46afd23da98b7f1cd7d063daef
|
4
|
+
data.tar.gz: ff401836b2d1338d0c83941cd697c7045b4c966dabbf180fd912d785289abdb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5b255ef462bdb062b10c91830de25a95ca0c8178427a5de3ba83d2a47baa18557023ad3782e66c3340e9b5b96fdc0045fe8929387a5ce196d6add8c3ad6594b
|
7
|
+
data.tar.gz: c47fde60afa59d51f85267ef59e6ecb07125a4252b0c9ea4e265f1b23c0a5e2e2aa32bcb19db2f445fe647d3b09d9612a4d740ab67c209bcf73be96276c2e0f6
|
data/.yardopts
ADDED
data/GETTING_STARTED.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Extism
|
2
|
+
|
3
|
+
## Getting Started
|
4
|
+
|
5
|
+
### Example
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
require "extism"
|
9
|
+
require "json"
|
10
|
+
|
11
|
+
Extism.with_context do |ctx|
|
12
|
+
manifest = {
|
13
|
+
:wasm => [{ :path => "../wasm/code.wasm" }],
|
14
|
+
}
|
15
|
+
plugin = ctx.plugin(manifest)
|
16
|
+
res = JSON.parse(plugin.call("count_vowels", "this is a test"))
|
17
|
+
puts res["count"] # => 4
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
### API
|
22
|
+
|
23
|
+
There are two primary classes you need to understand:
|
24
|
+
|
25
|
+
* [Context](Extism/Context.html)
|
26
|
+
* [Plugin](Extism/Plugin.html)
|
27
|
+
|
28
|
+
#### Context
|
29
|
+
|
30
|
+
The [Context](Extism/Context.html) can be thought of as a session. You need a context to interact with the Extism runtime. The context holds your plugins and when you free the context, it frees your plugins. We recommend using the [Extism.with_context](Extism.html#with_context-class_method) method to ensure that your plugins are cleaned up. But if you need a long lived context for any reason, you can use the constructor [Extism::Context.new](Extism/Context.html#initialize-instance_method).
|
31
|
+
|
32
|
+
#### Plugin
|
33
|
+
|
34
|
+
The [Plugin](Extism/Plugin.html) represents an instance of your WASM program from the given manifest.
|
35
|
+
The key method to know here is [Extism::Plugin#call](Extism/Plugin.html#call-instance_method) which takes a function name to invoke and some input data, and returns the results from the plugin.
|
data/Gemfile
CHANGED
data/Makefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
.PHONY: prepare test
|
3
|
+
|
4
|
+
prepare:
|
5
|
+
bundle install
|
6
|
+
bundle binstubs --all
|
7
|
+
|
8
|
+
test: prepare
|
9
|
+
bundle exec rake test
|
10
|
+
|
11
|
+
clean:
|
12
|
+
rm -f extism-*.gem
|
13
|
+
|
14
|
+
publish: clean prepare
|
15
|
+
gem build extism.gemspec
|
16
|
+
gem push extism-*.gem -k ${RUBYGEMS_API_KEY}
|
17
|
+
|
18
|
+
lint:
|
19
|
+
bundle exec rufo --check .
|
20
|
+
|
21
|
+
format:
|
22
|
+
bundle exec rufo .
|
23
|
+
|
24
|
+
docs:
|
25
|
+
bundle exec yard
|
26
|
+
|
27
|
+
show-docs: docs
|
28
|
+
open doc/index.html
|
data/Rakefile
CHANGED
data/example.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "./lib/extism"
|
2
|
+
require "json"
|
3
3
|
|
4
4
|
# a Context provides a scope for plugins to be managed within. creating multiple contexts
|
5
5
|
# is expected and groups plugins based on source/tenant/lifetime etc.
|
6
|
-
|
7
|
-
Extism
|
6
|
+
# We recommend you use `Extism.with_context` unless you have a reason to keep your context around.
|
7
|
+
# If you do you can create a context with `Extism#new`, example: `ctx = Extism.new`
|
8
|
+
Extism.with_context do |ctx|
|
8
9
|
manifest = {
|
9
|
-
:wasm => [{:path => "../wasm/code.wasm"}]
|
10
|
+
:wasm => [{ :path => "../wasm/code.wasm" }],
|
10
11
|
}
|
11
12
|
|
12
13
|
plugin = ctx.plugin(manifest)
|
13
14
|
res = JSON.parse(plugin.call("count_vowels", ARGV[0] || "this is a test"))
|
14
|
-
|
15
|
-
puts res[
|
16
|
-
|
15
|
+
|
16
|
+
puts res["count"]
|
17
|
+
end
|
data/lib/extism/version.rb
CHANGED
data/lib/extism.rb
CHANGED
@@ -1,37 +1,23 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "ffi"
|
2
|
+
require "json"
|
3
|
+
require_relative "./extism/version"
|
3
4
|
|
4
5
|
module Extism
|
5
|
-
module C
|
6
|
-
extend FFI::Library
|
7
|
-
ffi_lib "extism"
|
8
|
-
attach_function :extism_context_new, [], :pointer
|
9
|
-
attach_function :extism_context_free, [:pointer], :void
|
10
|
-
attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :bool], :int32
|
11
|
-
attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :bool], :bool
|
12
|
-
attach_function :extism_error, [:pointer, :int32], :string
|
13
|
-
attach_function :extism_plugin_call, [:pointer, :int32, :string, :pointer, :uint64], :int32
|
14
|
-
attach_function :extism_plugin_function_exists, [:pointer, :int32, :string], :bool
|
15
|
-
attach_function :extism_plugin_output_length, [:pointer, :int32], :uint64
|
16
|
-
attach_function :extism_plugin_output_data, [:pointer, :int32], :pointer
|
17
|
-
attach_function :extism_log_file, [:string, :pointer], :void
|
18
|
-
attach_function :extism_plugin_free, [:pointer, :int32], :void
|
19
|
-
attach_function :extism_context_reset, [:pointer], :void
|
20
|
-
attach_function :extism_version, [], :string
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
6
|
class Error < StandardError
|
25
7
|
end
|
26
8
|
|
27
9
|
# Return the version of Extism
|
10
|
+
#
|
11
|
+
# @return [String] The version string of the Extism runtime
|
28
12
|
def self.extism_version
|
29
13
|
C.extism_version
|
30
14
|
end
|
31
15
|
|
32
16
|
# Set log file and level, this is a global configuration
|
33
|
-
|
34
|
-
|
17
|
+
# @param name [String] The path to the logfile
|
18
|
+
# @param level [String] The log level. One of {"debug", "error", "info", "trace" }
|
19
|
+
def self.set_log_file(name, level = nil)
|
20
|
+
if level
|
35
21
|
level = FFI::MemoryPointer::from_string(level)
|
36
22
|
end
|
37
23
|
C.extism_log_file(name, level)
|
@@ -40,7 +26,7 @@ module Extism
|
|
40
26
|
$PLUGINS = {}
|
41
27
|
$FREE_PLUGIN = proc { |id|
|
42
28
|
x = $PLUGINS[id]
|
43
|
-
if !x.nil?
|
29
|
+
if !x.nil?
|
44
30
|
C.extism_plugin_free(x[:context].pointer, x[:plugin])
|
45
31
|
$PLUGINS.delete(id)
|
46
32
|
end
|
@@ -49,44 +35,76 @@ module Extism
|
|
49
35
|
$CONTEXTS = {}
|
50
36
|
$FREE_CONTEXT = proc { |id|
|
51
37
|
x = $CONTEXTS[id]
|
52
|
-
if !x.nil?
|
38
|
+
if !x.nil?
|
53
39
|
C.extism_context_free($CONTEXTS[id])
|
54
40
|
$CONTEXTS.delete(id)
|
55
41
|
end
|
56
42
|
}
|
57
43
|
|
58
|
-
# Context is
|
44
|
+
# A Context is needed to create plugins. The Context
|
45
|
+
# is where your plugins live. Freeing the context
|
46
|
+
# frees all of the plugins in its scope.
|
47
|
+
#
|
48
|
+
# @example Create and free a context
|
49
|
+
# ctx = Extism::Context.new
|
50
|
+
# plugin = ctx.plugin(my_manifest)
|
51
|
+
# puts plugin.call("my_func", "my-input")
|
52
|
+
# ctx.free # frees any plugins
|
53
|
+
#
|
54
|
+
# @example Use with_context to auto-free
|
55
|
+
# Extism.with_context do |ctx|
|
56
|
+
# plugin = ctx.plugin(my_manifest)
|
57
|
+
# puts plugin.call("my_func", "my-input")
|
58
|
+
# end # frees context after exiting this block
|
59
|
+
#
|
60
|
+
# @attr_reader pointer [FFI::Pointer] Pointer to the Extism context. *Used internally*.
|
59
61
|
class Context
|
60
|
-
|
62
|
+
attr_reader :pointer
|
61
63
|
|
64
|
+
# Initialize a new context
|
62
65
|
def initialize
|
63
66
|
@pointer = C.extism_context_new()
|
64
67
|
$CONTEXTS[self.object_id] = @pointer
|
65
|
-
ObjectSpace.define_finalizer(self,
|
68
|
+
ObjectSpace.define_finalizer(self, $FREE_CONTEXT)
|
66
69
|
end
|
67
70
|
|
68
|
-
# Remove all registered plugins
|
71
|
+
# Remove all registered plugins in this context
|
72
|
+
# @return [void]
|
69
73
|
def reset
|
70
74
|
C.extism_context_reset(@pointer)
|
71
75
|
end
|
72
76
|
|
73
77
|
# Free the context, this should be called when it is no longer needed
|
78
|
+
# @return [void]
|
74
79
|
def free
|
75
|
-
if @pointer.nil?
|
76
|
-
|
77
|
-
end
|
80
|
+
return if @pointer.nil?
|
81
|
+
|
78
82
|
$CONTEXTS.delete(self.object_id)
|
79
83
|
C.extism_context_free(@pointer)
|
80
84
|
@pointer = nil
|
81
85
|
end
|
82
86
|
|
83
87
|
# Create a new plugin from a WASM module or JSON encoded manifest
|
84
|
-
|
85
|
-
|
88
|
+
#
|
89
|
+
# @param wasm [Hash, String] The manifest for the plugin. See https://extism.org/docs/concepts/manifest/.
|
90
|
+
# @param wasi [Boolean] Enable WASI support
|
91
|
+
# @param config [Hash] The plugin config
|
92
|
+
# @return [Plugin]
|
93
|
+
def plugin(wasm, wasi = false, config = nil)
|
94
|
+
Plugin.new(self, wasm, wasi, config)
|
86
95
|
end
|
87
96
|
end
|
88
97
|
|
89
|
-
|
98
|
+
# A context manager to create contexts and ensure that they get freed.
|
99
|
+
#
|
100
|
+
# @example Use with_context to auto-free
|
101
|
+
# Extism.with_context do |ctx|
|
102
|
+
# plugin = ctx.plugin(my_manifest)
|
103
|
+
# puts plugin.call("my_func", "my-input")
|
104
|
+
# end # frees context after exiting this block
|
105
|
+
#
|
106
|
+
# @yield [ctx] Yields the created Context
|
107
|
+
# @return [Object] returns whatever your block returns
|
90
108
|
def self.with_context(&block)
|
91
109
|
ctx = Context.new
|
92
110
|
begin
|
@@ -97,25 +115,34 @@ module Extism
|
|
97
115
|
end
|
98
116
|
end
|
99
117
|
|
118
|
+
# A Plugin represents an instance of your WASM program from the given manifest.
|
100
119
|
class Plugin
|
101
|
-
|
102
|
-
|
120
|
+
# Intialize a plugin
|
121
|
+
#
|
122
|
+
# @see Extism::Context#plugin
|
123
|
+
# @param context [Context] The context to manager this plugin
|
124
|
+
# @param wasm [Hash, String] The manifest or WASM binary. See https://extism.org/docs/concepts/manifest/.
|
125
|
+
# @param wasi [Boolean] Enable WASI support
|
126
|
+
# @param config [Hash] The plugin config
|
127
|
+
def initialize(context, wasm, wasi = false, config = nil)
|
128
|
+
@context = context
|
129
|
+
if wasm.class == Hash
|
103
130
|
wasm = JSON.generate(wasm)
|
104
131
|
end
|
105
132
|
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
106
133
|
code.put_bytes(0, wasm)
|
107
134
|
@plugin = C.extism_plugin_new(context.pointer, code, wasm.bytesize, wasi)
|
108
|
-
if @plugin < 0
|
109
|
-
err = C.extism_error(-1)
|
110
|
-
if err&.empty?
|
135
|
+
if @plugin < 0
|
136
|
+
err = C.extism_error(@context.pointer, -1)
|
137
|
+
if err&.empty?
|
111
138
|
raise Error.new "extism_plugin_new failed"
|
112
|
-
else
|
139
|
+
else
|
140
|
+
raise Error.new err
|
113
141
|
end
|
114
142
|
end
|
115
|
-
@context
|
116
|
-
|
117
|
-
|
118
|
-
if config != nil and @plugin >= 0 then
|
143
|
+
$PLUGINS[self.object_id] = { :plugin => @plugin, :context => context }
|
144
|
+
ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
|
145
|
+
if config != nil and @plugin >= 0
|
119
146
|
s = JSON.generate(config)
|
120
147
|
ptr = FFI::MemoryPointer::from_string(s)
|
121
148
|
C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
|
@@ -123,22 +150,28 @@ module Extism
|
|
123
150
|
end
|
124
151
|
|
125
152
|
# Update a plugin with new WASM module or manifest
|
126
|
-
|
127
|
-
|
153
|
+
#
|
154
|
+
# @param wasm [Hash, String] The manifest or WASM binary. See https://extism.org/docs/concepts/manifest/.
|
155
|
+
# @param wasi [Boolean] Enable WASI support
|
156
|
+
# @param config [Hash] The plugin config
|
157
|
+
# @return [void]
|
158
|
+
def update(wasm, wasi = false, config = nil)
|
159
|
+
if wasm.class == Hash
|
128
160
|
wasm = JSON.generate(wasm)
|
129
161
|
end
|
130
162
|
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
131
163
|
code.put_bytes(0, wasm)
|
132
164
|
ok = C.extism_plugin_update(@context.pointer, @plugin, code, wasm.bytesize, wasi)
|
133
|
-
if !ok
|
134
|
-
err = C.extism_error(
|
135
|
-
if err&.empty?
|
165
|
+
if !ok
|
166
|
+
err = C.extism_error(@context.pointer, @plugin)
|
167
|
+
if err&.empty?
|
136
168
|
raise Error.new "extism_plugin_update failed"
|
137
|
-
else
|
169
|
+
else
|
170
|
+
raise Error.new err
|
138
171
|
end
|
139
172
|
end
|
140
173
|
|
141
|
-
if config != nil
|
174
|
+
if config != nil
|
142
175
|
s = JSON.generate(config)
|
143
176
|
ptr = FFI::MemoryPointer::from_string(s)
|
144
177
|
C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
|
@@ -146,38 +179,67 @@ module Extism
|
|
146
179
|
end
|
147
180
|
|
148
181
|
# Check if a function exists
|
149
|
-
|
150
|
-
|
182
|
+
#
|
183
|
+
# @param name [String] The name of the function
|
184
|
+
# @return [Boolean] Returns true if function exists
|
185
|
+
def has_function?(name)
|
186
|
+
C.extism_plugin_function_exists(@context.pointer, @plugin, name)
|
151
187
|
end
|
152
188
|
|
153
189
|
# Call a function by name
|
190
|
+
#
|
191
|
+
# @param name [String] The function name
|
192
|
+
# @param data [String] The input data for the function
|
193
|
+
# @return [String] The output from the function in String form
|
154
194
|
def call(name, data, &block)
|
155
195
|
# If no block was passed then use Pointer::read_string
|
156
|
-
block ||= ->(buf, len){ buf.read_string(len) }
|
196
|
+
block ||= ->(buf, len) { buf.read_string(len) }
|
157
197
|
input = FFI::MemoryPointer::from_string(data)
|
158
198
|
rc = C.extism_plugin_call(@context.pointer, @plugin, name, input, data.bytesize)
|
159
|
-
if rc != 0
|
199
|
+
if rc != 0
|
160
200
|
err = C.extism_error(@context.pointer, @plugin)
|
161
|
-
if err&.empty?
|
201
|
+
if err&.empty?
|
162
202
|
raise Error.new "extism_call failed"
|
163
|
-
else
|
203
|
+
else
|
204
|
+
raise Error.new err
|
164
205
|
end
|
165
206
|
end
|
166
207
|
out_len = C.extism_plugin_output_length(@context.pointer, @plugin)
|
167
208
|
buf = C.extism_plugin_output_data(@context.pointer, @plugin)
|
168
|
-
|
209
|
+
block.call(buf, out_len)
|
169
210
|
end
|
170
211
|
|
171
212
|
# Free a plugin, this should be called when the plugin is no longer needed
|
213
|
+
#
|
214
|
+
# @return [void]
|
172
215
|
def free
|
173
|
-
if @context.pointer.nil?
|
174
|
-
return
|
175
|
-
end
|
216
|
+
return if @context.pointer.nil?
|
176
217
|
|
177
218
|
$PLUGINS.delete(self.object_id)
|
178
219
|
C.extism_plugin_free(@context.pointer, @plugin)
|
179
220
|
@plugin = -1
|
180
221
|
end
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
181
225
|
|
226
|
+
# Private module used to interface with the Extism runtime.
|
227
|
+
# *Warning*: Do not use or rely on this directly.
|
228
|
+
module C
|
229
|
+
extend FFI::Library
|
230
|
+
ffi_lib "extism"
|
231
|
+
attach_function :extism_context_new, [], :pointer
|
232
|
+
attach_function :extism_context_free, [:pointer], :void
|
233
|
+
attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :bool], :int32
|
234
|
+
attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :bool], :bool
|
235
|
+
attach_function :extism_error, [:pointer, :int32], :string
|
236
|
+
attach_function :extism_plugin_call, [:pointer, :int32, :string, :pointer, :uint64], :int32
|
237
|
+
attach_function :extism_plugin_function_exists, [:pointer, :int32, :string], :bool
|
238
|
+
attach_function :extism_plugin_output_length, [:pointer, :int32], :uint64
|
239
|
+
attach_function :extism_plugin_output_data, [:pointer, :int32], :pointer
|
240
|
+
attach_function :extism_log_file, [:string, :pointer], :void
|
241
|
+
attach_function :extism_plugin_free, [:pointer, :int32], :void
|
242
|
+
attach_function :extism_context_reset, [:pointer], :void
|
243
|
+
attach_function :extism_version, [], :string
|
182
244
|
end
|
183
245
|
end
|
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: 0.0.1.
|
4
|
+
version: 0.0.1.rc6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zach
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A library for loading and executing WASM plugins
|
14
14
|
email:
|
@@ -17,7 +17,10 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".yardopts"
|
21
|
+
- GETTING_STARTED.md
|
20
22
|
- Gemfile
|
23
|
+
- Makefile
|
21
24
|
- Rakefile
|
22
25
|
- example.rb
|
23
26
|
- lib/extism.rb
|