extism 0.0.1.rc4 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/GETTING_STARTED.md +35 -0
- data/Gemfile +6 -1
- data/Makefile +33 -0
- data/Rakefile +9 -0
- data/example.rb +9 -8
- data/lib/extism/version.rb +1 -1
- data/lib/extism.rb +124 -62
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc9f148ba2f810e78a5969f3b16802e3f2f825feb68a8aa21210384c79f30711
|
4
|
+
data.tar.gz: 512aa65c0397314755e4c6e45fa115f451aa78b052771d825e8f51448266b18d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c0718a98ec65e5e271812574371ad1202f41a8e783aaa14d8d570bfdf5052ecae793b628b474de83769d16a4e87555c652ea7b87b7b61ddc2ef1453775b93c2
|
7
|
+
data.tar.gz: 86c8567bb7cdbf5ef8ce054ce9d03383d168fb9a409733bd83801faf4129093849195a86939eeec653537260c97963e3e7af6956e3407841726a096d5a7663de
|
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,33 @@
|
|
1
|
+
RUBYGEMS_API_KEY ?=
|
2
|
+
|
3
|
+
.PHONY: prepare test
|
4
|
+
|
5
|
+
prepare:
|
6
|
+
bundle install
|
7
|
+
bundle binstubs --all
|
8
|
+
|
9
|
+
test: prepare
|
10
|
+
bundle exec rake test
|
11
|
+
|
12
|
+
clean:
|
13
|
+
rm -f extism-*.gem
|
14
|
+
|
15
|
+
publish-local: clean prepare
|
16
|
+
gem build extism.gemspec
|
17
|
+
gem push extism-*.gem
|
18
|
+
|
19
|
+
publish: clean prepare
|
20
|
+
gem build extism.gemspec
|
21
|
+
GEM_HOST_API_KEY=$(RUBYGEMS_API_KEY) gem push extism-*.gem
|
22
|
+
|
23
|
+
lint:
|
24
|
+
bundle exec rufo --check .
|
25
|
+
|
26
|
+
format:
|
27
|
+
bundle exec rufo .
|
28
|
+
|
29
|
+
docs:
|
30
|
+
bundle exec yard
|
31
|
+
|
32
|
+
show-docs: docs
|
33
|
+
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
|
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-29 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
|
@@ -42,9 +45,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
45
|
version: 2.6.0
|
43
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
47
|
requirements:
|
45
|
-
- - "
|
48
|
+
- - ">="
|
46
49
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
50
|
+
version: '0'
|
48
51
|
requirements: []
|
49
52
|
rubygems_version: 3.1.6
|
50
53
|
signing_key:
|