extism 0.0.1.rc4 → 0.0.1.rc6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9baa968de02938c07de67d56f468c482392d15c3b0d4856679ffa65ac54bcf8e
4
- data.tar.gz: 18577dac8681ec65162505ea3664ec85d39be3c7b97df203a12f89a0eeff1617
3
+ metadata.gz: da418ff834d018a2db1264a41bb5a1492ee9ed46afd23da98b7f1cd7d063daef
4
+ data.tar.gz: ff401836b2d1338d0c83941cd697c7045b4c966dabbf180fd912d785289abdb5
5
5
  SHA512:
6
- metadata.gz: ffc8f845fc8d42e6f55dc90bc56ab6e4b2ad35f1a1af88c4a5caf1c8f0353314be92e2e33157eb9c50c9a7d168d477d78c7af883f7d30f5879f27e53e4b34f0f
7
- data.tar.gz: cf4175f208e06380f761ff80dec20ac38a416e7c866cee1ccb3844a37e24369464eeb4a60bcd547b7e348fc00744d06de8cceda5cfcd87dc9675d45bdbb407f4
6
+ metadata.gz: d5b255ef462bdb062b10c91830de25a95ca0c8178427a5de3ba83d2a47baa18557023ad3782e66c3340e9b5b96fdc0045fe8929387a5ce196d6add8c3ad6594b
7
+ data.tar.gz: c47fde60afa59d51f85267ef59e6ecb07125a4252b0c9ea4e265f1b23c0a5e2e2aa32bcb19db2f445fe647d3b09d9612a4d740ab67c209bcf73be96276c2e0f6
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --readme GETTING_STARTED.md
2
+ - GETTING_STARTED.md
@@ -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
@@ -6,5 +6,10 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
- gem "ffi"
9
+ gem "ffi", "~> 1.15.5"
10
10
 
11
+ group :development do
12
+ gem "yard", "~> 0.9.28"
13
+ gem "rufo", "~> 0.13.0"
14
+ gem "minitest", "~> 5.16.3"
15
+ end
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
@@ -1,3 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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
data/example.rb CHANGED
@@ -1,16 +1,17 @@
1
- require './lib/extism'
2
- require 'json'
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
- ctx = Extism::Context.new
7
- Extism::with_context {|ctx|
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['count']
16
- }
15
+
16
+ puts res["count"]
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Extism
4
- VERSION = "0.0.1.rc4"
4
+ VERSION = "0.0.1.rc6"
5
5
  end
data/lib/extism.rb CHANGED
@@ -1,37 +1,23 @@
1
- require 'ffi'
2
- require 'json'
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
- def self.set_log_file(name, level=nil)
34
- if level then
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? then
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? then
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 used to manage plugins
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
- attr_accessor :pointer
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, $FREE_CONTEXT)
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? then
76
- return
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
- def plugin(wasm, wasi=false, config=nil)
85
- return Plugin.new(self, wasm, wasi, config)
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
- def initialize(context, wasm, wasi=false, config=nil)
102
- if wasm.class == Hash then
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 then
109
- err = C.extism_error(-1)
110
- if err&.empty? then
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 raise Error.new err
139
+ else
140
+ raise Error.new err
113
141
  end
114
142
  end
115
- @context = context
116
- $PLUGINS[self.object_id] = {:plugin => @plugin, :context => context}
117
- ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
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
- def update(wasm, wasi=false, config=nil)
127
- if wasm.class == Hash then
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 then
134
- err = C.extism_error(-1)
135
- if err&.empty? then
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 raise Error.new err
169
+ else
170
+ raise Error.new err
138
171
  end
139
172
  end
140
173
 
141
- if config != nil then
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
- def function_exists(name)
150
- return C.extism_function_exists(@context.pointer, @plugin, name)
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 then
199
+ if rc != 0
160
200
  err = C.extism_error(@context.pointer, @plugin)
161
- if err&.empty? then
201
+ if err&.empty?
162
202
  raise Error.new "extism_call failed"
163
- else raise Error.new err
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
- return block.call(buf, out_len)
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? then
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.rc4
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-10-20 00:00:00.000000000 Z
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