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 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