extism 0.5.0 → 1.0.0.pre.rc.2

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: 844243a86e28c001eceea6a44a3e73b07aea648d608b2c3e989d0c1185e6207c
4
- data.tar.gz: 13caf6cf47b8cf7640a194a3a91607392f5f5a963c8dca56ff466c92055ea17a
3
+ metadata.gz: 9de040945d856411394ee9e9631aeffdf7b89a6e1269788e823191a26c0e50a8
4
+ data.tar.gz: 9a588d3b134ee5e1d817d38bb4bd0b8abb92d48148c35d4b538870872dffafd3
5
5
  SHA512:
6
- metadata.gz: c78963cf077ac7d85710554f781b6a4e4dac2352bd89ecbc62852a33b4a1ada70f4728eea80b6b2894d3508ce164f817c70bddc537958514fb0353d63ee02252
7
- data.tar.gz: c9270b4c474c816a743fd5ba1efd9c37b765ee177d59bae93a0e17540d91ae95e618292c257e6873b198e508fb79cf051156bf397cefb812554be0cb52c0db2e
6
+ metadata.gz: a675e36bf299fbb2dc2457a35569ef491f0f6be308f7afe7e01603d83971fe6b7f8a23051bb385eb1de5a9be6948d8658128975aa3b183f02b54c9e0312abb58
7
+ data.tar.gz: '082c612825780d24869dd7a975adf67cf61f5a7e90b3c4b801cad6769ef69eb237e43621a118a95c6d376bc1d88e57fb0ba1f27519ffc31f52850a453c229865'
data/.yardopts CHANGED
@@ -1,2 +1 @@
1
- --readme GETTING_STARTED.md
2
- - GETTING_STARTED.md
1
+ --readme README.md
data/Gemfile CHANGED
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in extism.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
9
- gem "ffi", "~> 1.15.5"
8
+ gem 'ffi', '~> 1.15.5'
9
+ gem 'rake', '~> 13.0'
10
10
 
11
11
  group :development do
12
- gem "yard", "~> 0.9.28"
13
- gem "rufo", "~> 0.13.0"
14
- gem "minitest", "~> 5.19.0"
12
+ gem 'debug'
13
+ gem 'minitest', '~> 5.20.0'
14
+ gem 'rufo', '~> 0.13.0'
15
+ gem 'yard', '~> 0.9.28'
15
16
  end
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ extism (1.0.0.pre.rc.2)
5
+ ffi (>= 1.0.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ debug (1.8.0)
11
+ irb (>= 1.5.0)
12
+ reline (>= 0.3.1)
13
+ ffi (1.15.5)
14
+ io-console (0.6.0)
15
+ irb (1.8.1)
16
+ rdoc
17
+ reline (>= 0.3.8)
18
+ minitest (5.20.0)
19
+ psych (5.1.0)
20
+ stringio
21
+ rake (13.0.6)
22
+ rdoc (6.5.0)
23
+ psych (>= 4.0.0)
24
+ reline (0.3.8)
25
+ io-console (~> 0.5)
26
+ rufo (0.13.0)
27
+ stringio (3.0.8)
28
+ yard (0.9.34)
29
+
30
+ PLATFORMS
31
+ arm64-darwin-22
32
+
33
+ DEPENDENCIES
34
+ debug
35
+ extism!
36
+ ffi (~> 1.15.5)
37
+ minitest (~> 5.20.0)
38
+ rake (~> 13.0)
39
+ rufo (~> 0.13.0)
40
+ yard (~> 0.9.28)
41
+
42
+ BUNDLED WITH
43
+ 2.4.10
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2022 Dylibso, Inc.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Makefile CHANGED
@@ -1,33 +1,39 @@
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
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
34
+
35
+ seed:
36
+ curl -L https://github.com/extism/plugins/releases/latest/download/count_vowels.debug.wasm > wasm/count_vowels.wasm
37
+ curl -L https://github.com/extism/plugins/releases/latest/download/reflect.debug.wasm > wasm/reflect.wasm
38
+ curl -L https://github.com/extism/plugins/releases/latest/download/store_credit.debug.wasm > wasm/store_credit.wasm
39
+
data/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # Extism Ruby Host SDK
2
+
3
+ This repo houses the ruby gem for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host ruby applications to run Extism plug-ins.
4
+
5
+ > **Note**: This repo is 1.0 alpha version of the Ruby SDK and is a work in progress. We'd love any feedback you have on it, but consider using the supported ruby SDK in [extism/extism](https://github.com/extism/extism/tree/main/ruby) until we hit 1.0.
6
+
7
+ ## Installation
8
+
9
+ ### Install the Extism Runtime
10
+
11
+ You first need to install the Extism Runtime which is a native shared object that this library uses to load and run the Wasm code. You can [download the shared library 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 Rubygem
24
+
25
+ Add this library to your [Gemfile](https://bundler.io/):
26
+
27
+ ```ruby
28
+ gem 'extism', '1.0.0.pre.rc.2'
29
+ ```
30
+
31
+ Or if installing on the system level:
32
+
33
+ ```
34
+ gem install extism --pre
35
+ ```
36
+
37
+ ## Getting Started
38
+
39
+ > *Note*: You should be able to follow this guide by copy pasting the code into `irb`.
40
+
41
+ First you should require `"extism"`:
42
+
43
+ ```ruby
44
+ require "extism"
45
+ ```
46
+
47
+ ### Creating A Plug-in
48
+
49
+ The primary concept in Extism is the plug-in. You can think of a plug-in as a code module stored in a `.wasm` file. You can [learn more about plug-ins here](https://extism.org/concepts/plug-in).
50
+
51
+ You'll generally load the plug-in from disk, but for simplicity let's load a pre-built demo plug-in from the web:
52
+
53
+ ```ruby
54
+ manifest = {
55
+ wasm: [
56
+ { url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm" }
57
+ ]
58
+ }
59
+ plugin = Extism::Plugin.new(manifest)
60
+ ```
61
+
62
+ > **Note**: The schema for this manifest can be found here: [https://extism.org/docs/concepts/manifest/](https://extism.org/docs/concepts/manifest/)
63
+
64
+ ### Calling A Plug-in's Exports
65
+
66
+ 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):
67
+
68
+ ```ruby
69
+ plugin.call("count_vowels", "Hello, World!")
70
+ # => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
71
+ ```
72
+
73
+ All exports have a simple interface of optional bytes in, and optional bytes out. This plug-in happens to take a string and return a JSON encoded string with a report of results.
74
+
75
+ ### Plug-in State
76
+
77
+ 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:
78
+
79
+ ```ruby
80
+ plugin.call("count_vowels", "Hello, World!")
81
+ # => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
82
+ plugin.call("count_vowels", "Hello, World!")
83
+ # => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
84
+ ```
85
+
86
+ These variables will persist until this plug-in is freed or you initialize a new one.
87
+
88
+ ### Configuration
89
+
90
+ 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:
91
+
92
+ ```ruby
93
+ plugin = Extism::Plugin.new(manifest)
94
+ plugin.call("count_vowels", "Yellow, World!")
95
+ # => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
96
+
97
+ plugin = Extism::Plugin.new(manifest, config: { vowels: "aeiouyAEIOUY" })
98
+ plugin.call("count_vowels", "Yellow, World!")
99
+ # => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"}
100
+ ```
101
+
102
+ ### Host Functions
103
+
104
+ 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 to and invoked from any language inside the plug-in.
105
+
106
+ > *Note*: Host functions can be a complicated topic. Please review this [concept doc](https://extism.org/docs/concepts/host-functions) if you are unsure how they work.
107
+
108
+ ### Host Functions Example
109
+
110
+ We've created a contrived, but familiar example to illustrate this. Suppose you are a stripe-like payments platform.
111
+ When a [charge.succeeded](https://stripe.com/docs/api/events/types#event_types-charge.succeeded) event occurs, we will call the `on_charge_succeeded` function on our merchant's plug-in and let them decide what to do with it. Here our merchant has some very specific requirements, if the account has spent more than $100, their currency is USD, and they have no credits on their account, it will add $10 credit to their account and then send them an email.
112
+
113
+ > *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/store_credit/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.
114
+
115
+ First let's create the manifest for our plug-in like usual but load up the `store_credit` plug-in:
116
+
117
+ ```ruby
118
+ manifest = {
119
+ wasm: [
120
+ { url: "https://github.com/extism/plugins/releases/latest/download/store_credit.wasm" }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ But, unlike our `count_vowels` plug-in, this plug-in expects you to provide host functions that satisfy our plug-in's imports.
126
+
127
+ 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 just an object that responds to `host_functions` and returns an array of `Extism::Function`s. We want to expose two capabilities to our plugin, `add_credit(customer_id, amount)` which adds credit to an account and `send_email(customer_id, email)` which sends them an email.
128
+
129
+ ```ruby
130
+
131
+ # This is global is just for demo purposes but would in
132
+ # reality be in a database or something
133
+ CUSTOMER = {
134
+ full_name: 'John Smith',
135
+ customer_id: 'abcd1234',
136
+ total_spend: {
137
+ currency: 'USD',
138
+ amount_in_cents: 20_000
139
+ },
140
+ credit: {
141
+ currency: 'USD',
142
+ amount_in_cents: 0
143
+ }
144
+ }
145
+
146
+ class MyEnvironment
147
+ include Extism::HostEnvironment
148
+
149
+ # we need to register each import that the plug-in expects and match the Wasm signature
150
+ # register_import takes the name, the param types, and the return types
151
+ register_import :add_credit, [Extism::ValType::I64, Extism::ValType::I64], [Extism::ValType::I64]
152
+ register_import :send_email, [Extism::ValType::I64, Extism::ValType::I64], []
153
+
154
+ def add_credit(plugin, inputs, outputs, _user_data)
155
+ # add_credit takes a string `customer_id` as the first parameter
156
+ customer_id = plugin.input_as_string(inputs.first)
157
+ # it takes an object `amount` { amount_in_cents: int, currency: string } as the second parameter
158
+ amount = plugin.input_as_json(inputs[1])
159
+
160
+ # we're just going to print it out and add to the CUSTOMER global
161
+ puts "Adding Credit #{amount} to customer #{customer_id}"
162
+ CUSTOMER[:credit][:amount_in_cents] += amount['amount_in_cents']
163
+
164
+ # add_credit returns a Json object with the new customer details
165
+ plugin.return_json(outputs.first, CUSTOMER)
166
+ end
167
+
168
+ def send_email(plugin, inputs, _outputs, _user_data)
169
+ # send_email takes a string `customer_id` as the first parameter
170
+ customer_id = plugin.input_as_string(inputs.first)
171
+ # it takes an object `email` { subject: string, body: string } as the second parameter
172
+ email = plugin.input_as_json(inputs[1])
173
+
174
+ # we'll just print it but you could imagine we'd put something
175
+ # in a database or call an internal api to send this email
176
+ puts "Sending email #{email} to customer #{customer_id}"
177
+
178
+ # it doesn't return anything
179
+ end
180
+ end
181
+ ```
182
+
183
+ 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 merchant specific instance variables in there:
184
+
185
+ ```ruby
186
+ env = MyEnvironment.new
187
+ plugin = Extism::Plugin.new(manifest, environment: env)
188
+ ```
189
+
190
+ Now we can invoke the event:
191
+
192
+ ```ruby
193
+ event = {
194
+ event_type: 'charge.succeeded',
195
+ customer: CUSTOMER
196
+ }
197
+ result = plugin.call('on_charge_succeeded', JSON.generate(event))
198
+ ```
199
+
200
+ This will print:
201
+
202
+ ```
203
+ Adding Credit {"amount_in_cents"=>1000, "currency"=>"USD"} for customer abcd1234
204
+ Sending email {"subject"=>"A gift for you John Smith", "body"=>"You have received $10 in store credi
205
+ t!"} to customer abcd1234
206
+ ```
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
@@ -0,0 +1,153 @@
1
+ module Extism
2
+ # Represents a reference to a plugin in a host function
3
+ # Use this class to read and write to the memory of the plugin
4
+ # These methods allow you to get data in and out of the plugin
5
+ # in a host function
6
+ class CurrentPlugin
7
+ # let's not let people construct these since it comes from a pointer
8
+ private_class_method :new
9
+
10
+ # Initialize a CurrentPlugin given an pointer
11
+ #
12
+ # @param ptr [FFI::Pointer] the raw pointer to the plugin
13
+ def initialize(ptr)
14
+ @ptr = ptr
15
+ end
16
+
17
+ # Allocates a memory block in the plugin
18
+ #
19
+ # @example
20
+ # mem = current_plugin.alloc(1_024)
21
+ # mem.put_bytes(0, "Hello, World!")
22
+ #
23
+ # @param amount [Integer] The amount in bytes to allocate
24
+ # @return [Extism::Memory] The reference to the freshly allocated memory
25
+ def alloc(amount)
26
+ offset = LibExtism.extism_current_plugin_memory_alloc(@ptr, amount)
27
+ Memory.new(offset, amount)
28
+ end
29
+
30
+ # Frees the memory block
31
+ #
32
+ # @example
33
+ # mem = current_plugin.alloc(1_024)
34
+ # current_plugin.free(mem)
35
+ #
36
+ # @param memory [Extism::Memory] The memory object you wish to free
37
+ # @return [Extism::Memory] The reference to the freshly allocated memory
38
+ def free(memory)
39
+ LibExtism.extism_current_plugin_memory_free(@ptr, memory.offset)
40
+ end
41
+
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)
47
+ #
48
+ # @raise [Extism::Error] if memory block could not be found
49
+ #
50
+ # @param offset [Integer] The offset pointer to the memory. This is relative to the plugin not the host.
51
+ # @return [Extism::Memory] The reference to the memory block if found
52
+ def memory_at_offset(offset)
53
+ len = LibExtism.extism_current_plugin_memory_length(@ptr, offset)
54
+ raise Extism::Error, "Could not find memory block at offset #{offset}" if len.zero?
55
+
56
+ Memory.new(offset, len)
57
+ end
58
+
59
+ # Gets the input as a string
60
+ #
61
+ # @raise [Extism::Error] if memory block could not be found
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
+ #
67
+ # @param input [Extism::Val] The input val from the host function
68
+ # @return [String] raw bytes as a string
69
+ def input_as_string(input)
70
+ raise ArgumentError, 'input is not an Extism::Val' unless input.instance_of? Extism::Val
71
+
72
+ mem = memory_at_offset(input.value)
73
+ memory_ptr(mem).read_bytes(mem.len)
74
+ end
75
+
76
+ # Gets the input as a JSON parsed Hash
77
+ #
78
+ # @raise [Extism::Error] if memory block could not be found
79
+ #
80
+ # @example
81
+ # param1 = current_plugin.input_as_json(inputs.first)
82
+ # raise "Failed" unless param1 == {hello: "world"}
83
+ #
84
+ # @param input [Extism::Val] The input val from the host function
85
+ # @return [Hash] The Hash object
86
+ def input_as_json(input)
87
+ raise ArgumentError, 'input is not an Extism::Val' unless input.instance_of? Extism::Val
88
+
89
+ mem = memory_at_offset(input.value)
90
+ str = memory_ptr(mem).read_bytes(mem.len)
91
+ JSON.parse(str)
92
+ end
93
+
94
+ # Sets string to the return of the host function
95
+ #
96
+ # @example
97
+ # msg = "A string returned from the host function"
98
+ # current_plugin.output_string(outputs.first, msg)
99
+ #
100
+ # @raise [Extism::Error] if memory block could not be found
101
+ #
102
+ # @param output [Extism::Val] The output val from the host function
103
+ # @param bytes [String] The bytes to set
104
+ def output_string(output, bytes)
105
+ mem = alloc(bytes.length)
106
+ memory_ptr(mem).put_bytes(0, bytes)
107
+ set_return(output, mem.offset)
108
+ end
109
+
110
+ # Sets json to the return of the host function
111
+ #
112
+ # @example
113
+ # msg = {hello: "world"}
114
+ # current_plugin.output_json(outputs.first, msg)
115
+ #
116
+ # @raise [Extism::Error] if memory block could not be found
117
+ #
118
+ # @param output [Extism::Val] The output val from the host function
119
+ # @param obj [Hash] The hash object to turn to JSON
120
+ def output_json(output, obj)
121
+ bytes = JSON.generate(obj)
122
+ mem = alloc(bytes.length)
123
+ memory_ptr(mem).put_bytes(0, bytes)
124
+ set_return(output, mem.offset)
125
+ end
126
+
127
+ # Sets the return value parameter
128
+ #
129
+ # @raise [Extism::Error] if memory block could not be found
130
+ #
131
+ # @param output [Extism::Val] The output val from the host function
132
+ # @param value [Integer | Float] The i32 value
133
+ def set_return(output, value)
134
+ case output.type
135
+ when :i32, :i64, :f32, :f64
136
+ output.value = value
137
+ else
138
+ raise ArgumentError, "Don't know how to set output type #{output.type}"
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ # Returns a raw pointer (absolute to the host) to the given memory block
145
+ # Be careful with this. it's not exposed for a reason.
146
+ # This is a pointer in host memory so it could read outside of the plugin
147
+ # if manipulated
148
+ def memory_ptr(mem)
149
+ plugin_ptr = LibExtism.extism_current_plugin_memory(@ptr)
150
+ FFI::Pointer.new(plugin_ptr.address + mem.offset)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,44 @@
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
+ #
18
+ module HostEnvironment
19
+ def self.included(base)
20
+ base.extend ClassMethods
21
+ base.class_variable_set(:@@import_funcs, [])
22
+ end
23
+
24
+ def host_functions
25
+ import_funcs = self.class.class_variable_get(:@@import_funcs)
26
+ import_funcs.map do |f|
27
+ name, params, returns = f
28
+ Extism::Function.new(
29
+ name.to_s,
30
+ params,
31
+ returns,
32
+ method(name).to_proc
33
+ )
34
+ end
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+ def register_import(func_name, parameters, returns)
40
+ import_funcs = class_variable_get(:@@import_funcs)
41
+ import_funcs << [func_name, parameters, returns]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,77 @@
1
+ module Extism
2
+ # Private module used to interface with the Extism runtime.
3
+ # *Warning*: Do not use or rely on this directly
4
+ # improperly using this interface may enable exploits and the interface
5
+ # might change over time.
6
+ module LibExtism
7
+ extend FFI::Library
8
+ ffi_lib 'extism'
9
+
10
+ def self.from_int_array(ruby_array)
11
+ ptr = FFI::MemoryPointer.new(:int, ruby_array.length)
12
+ ptr.write_array_of_int(ruby_array)
13
+ ptr
14
+ end
15
+
16
+ typedef :uint64, :ExtismMemoryHandle
17
+ typedef :uint64, :ExtismSize
18
+
19
+ enum :ExtismValType, %i[I32 I64 F32 F64 V128 FuncRef ExternRef]
20
+
21
+ class ExtismValUnion < FFI::Union
22
+ layout :i32, :int32,
23
+ :i64, :int64,
24
+ :f32, :float,
25
+ :f64, :double
26
+ end
27
+
28
+ class ExtismVal < FFI::Struct
29
+ layout :t, :ExtismValType,
30
+ :v, ExtismValUnion
31
+ end
32
+
33
+ class ExtismFunction < FFI::Struct
34
+ layout :name, :string,
35
+ :inputs, :pointer,
36
+ :n_inputs, :uint64,
37
+ :outputs, :pointer,
38
+ :n_outputs, :uint64,
39
+ :data, :pointer
40
+ end
41
+
42
+ callback :ExtismFunctionType, [
43
+ :pointer, # plugin
44
+ :pointer, # inputs
45
+ :ExtismSize, # n_inputs
46
+ :pointer, # outputs
47
+ :ExtismSize, # n_outputs
48
+ :pointer # user_data
49
+ ], :void
50
+
51
+ callback :ExtismFreeFunctionType, [], :void
52
+
53
+ attach_function :extism_plugin_id, [:pointer], :pointer
54
+ attach_function :extism_current_plugin_memory, [:pointer], :pointer
55
+ attach_function :extism_current_plugin_memory_alloc, %i[pointer ExtismSize], :ExtismMemoryHandle
56
+ attach_function :extism_current_plugin_memory_length, %i[pointer ExtismMemoryHandle], :ExtismSize
57
+ attach_function :extism_current_plugin_memory_free, %i[pointer ExtismMemoryHandle], :void
58
+ attach_function :extism_function_new,
59
+ %i[string pointer ExtismSize pointer ExtismSize ExtismFunctionType ExtismFreeFunctionType pointer], :pointer
60
+ attach_function :extism_function_free, [:pointer], :void
61
+ attach_function :extism_function_set_namespace, %i[pointer string], :void
62
+ attach_function :extism_plugin_new, %i[pointer ExtismSize pointer ExtismSize bool pointer], :pointer
63
+ attach_function :extism_plugin_new_error_free, [:pointer], :void
64
+ attach_function :extism_plugin_free, [:pointer], :void
65
+ attach_function :extism_plugin_cancel_handle, [:pointer], :pointer
66
+ attach_function :extism_plugin_cancel, [:pointer], :bool
67
+ attach_function :extism_plugin_config, %i[pointer pointer ExtismSize], :bool
68
+ attach_function :extism_plugin_function_exists, %i[pointer string], :bool
69
+ attach_function :extism_plugin_call, %i[pointer string pointer ExtismSize], :int32
70
+ attach_function :extism_error, [:pointer], :string
71
+ attach_function :extism_plugin_error, [:pointer], :string
72
+ attach_function :extism_plugin_output_length, [:pointer], :ExtismSize
73
+ attach_function :extism_plugin_output_data, [:pointer], :pointer
74
+ attach_function :extism_log_file, %i[string string], :bool
75
+ attach_function :extism_version, [], :string
76
+ end
77
+ end
@@ -0,0 +1,99 @@
1
+ module Extism
2
+ # A Plugin represents an instance of your WASM program
3
+ # created from the given manifest.
4
+ class Plugin
5
+ # Intialize a plugin
6
+ #
7
+ # @example
8
+ # manifest = {
9
+ # wasm: [
10
+ # { url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm" }
11
+ # ]
12
+ # }
13
+ # plugin = Extism::Plugin.new(manifest)
14
+ #
15
+ # @param wasm [Hash, String] The manifest as a Hash or WASM binary as a String. See https://extism.org/docs/concepts/manifest/.
16
+ # @param wasi [Boolean] Enable WASI support
17
+ # @param config [Hash] The plugin config
18
+ def initialize(wasm, environment: nil, functions: [], wasi: false, config: nil)
19
+ wasm = JSON.generate(wasm) if wasm.instance_of?(Hash)
20
+ code = FFI::MemoryPointer.new(:char, wasm.bytesize)
21
+ errmsg = FFI::MemoryPointer.new(:pointer)
22
+ code.put_bytes(0, wasm)
23
+ if functions.empty? && environment
24
+ unless environment.respond_to?(:host_functions)
25
+ raise ArgumentError 'environment should implement host_functions method'
26
+ end
27
+
28
+ functions = environment.host_functions
29
+ end
30
+ funcs_ptr = FFI::MemoryPointer.new(LibExtism::ExtismFunction)
31
+ funcs_ptr.write_array_of_pointer(functions.map { |f| f.send(:pointer) })
32
+ @plugin = LibExtism.extism_plugin_new(code, wasm.bytesize, funcs_ptr, functions.length, wasi, errmsg)
33
+ if @plugin.null?
34
+ err = errmsg.read_pointer.read_string
35
+ LibExtism.extism_plugin_new_error_free errmsg.read_pointer
36
+ raise Error, err
37
+ end
38
+ $PLUGINS[object_id] = { plugin: @plugin }
39
+ ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
40
+ return unless !config.nil? and @plugin.null?
41
+
42
+ s = JSON.generate(config)
43
+ ptr = FFI::MemoryPointer.from_string(s)
44
+ LibExtism.extism_plugin_config(@plugin, ptr, s.bytesize)
45
+ end
46
+
47
+ # Check if a function exists
48
+ #
49
+ # @param name [String] The name of the function
50
+ # @return [Boolean] Returns true if function exists
51
+ def has_function?(name)
52
+ LibExtism.extism_plugin_function_exists(@plugin, name)
53
+ end
54
+
55
+ # Call a function by name
56
+ #
57
+ # @example
58
+ # input = JSON.generate({hello: "world"})
59
+ # result = plugin.call("my_func", input)
60
+ # output = JSON.parse(result)
61
+ #
62
+ # @param name [String] The function name
63
+ # @param data [String] The input data for the function
64
+ # @return [String] The output from the function in String form
65
+ def call(name, data, &block)
66
+ # If no block was passed then use Pointer::read_string
67
+ block ||= ->(buf, len) { buf.read_string(len) }
68
+ input = FFI::MemoryPointer.from_string(data)
69
+ rc = LibExtism.extism_plugin_call(@plugin, name, input, data.bytesize)
70
+ if rc != 0
71
+ err = LibExtism.extism_plugin_error(@plugin)
72
+ raise Error, 'extism_call failed' if err&.empty?
73
+
74
+ raise Error, err
75
+
76
+ end
77
+
78
+ out_len = LibExtism.extism_plugin_output_length(@plugin)
79
+ buf = LibExtism.extism_plugin_output_data(@plugin)
80
+ block.call(buf, out_len)
81
+ end
82
+
83
+ # Free a plugin, this should be called when the plugin is no longer needed
84
+ #
85
+ # @return [void]
86
+ def free
87
+ return if @plugin.null?
88
+
89
+ $PLUGINS.delete(object_id)
90
+ LibExtism.extism_plugin_free(@plugin)
91
+ @plugin = nil
92
+ end
93
+
94
+ # Get a CancelHandle for a plugin
95
+ def cancel_handle
96
+ CancelHandle.new(LibExtism.extism_plugin_cancel_handle(@plugin))
97
+ end
98
+ end
99
+ end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Extism
4
- VERSION = '0.5.0'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Extism
4
+ VERSION = '1.0.0-rc.2'
5
+ end
@@ -0,0 +1,102 @@
1
+ module Extism
2
+ module ValType
3
+ I32 = 0
4
+ I64 = 1
5
+ F32 = 2
6
+ F64 = 3
7
+ V128 = 4
8
+ FUNC_REF = 5
9
+ EXTERN_REF = 6
10
+ end
11
+
12
+ class Val
13
+ def initialize(ptr)
14
+ @c_val = LibExtism::ExtismVal.new(ptr)
15
+ end
16
+
17
+ def type
18
+ case @c_val[:t]
19
+ when :I32
20
+ :i32
21
+ when :I64
22
+ :i64
23
+ when :F32
24
+ :f32
25
+ when :F64
26
+ :f64
27
+ else
28
+ raise "Unsupported wasm value type #{type}"
29
+ end
30
+ end
31
+
32
+ def value
33
+ @c_val[:v][type]
34
+ end
35
+
36
+ def value=(val)
37
+ @c_val[:v][type] = val
38
+ end
39
+ end
40
+
41
+ # A CancelHandle can be used to cancel a running plugin from another thread
42
+ class CancelHandle
43
+ def initialize(handle)
44
+ @handle = handle
45
+ end
46
+
47
+ # Cancel the plugin used to generate the handle
48
+ def cancel
49
+ LibExtism.extism_plugin_cancel(@handle)
50
+ end
51
+ end
52
+
53
+ # Represents a host function
54
+ class Function
55
+ # Create a new host function
56
+ #
57
+ # @param name [String] Must match the import name in Wasm. Doesn't include namespace. All extism host functions are in the env name space
58
+ # @param params [Array[Extism::ValType]] An array of val types matching the import's params
59
+ # @param returns [Array[Extism::ValType]] An array of val types matching the import returns
60
+ # @param func_proc [Proc] A proc that will be executed when the host function is executed
61
+ # @param user_data [Object] Any reference to object you want to be passed back to you when the func is invoked
62
+ # @param on_free [Proc] A proc triggered when this function is freed by the runtime. Not guaranteed to trigger.
63
+ def initialize(name, params, returns, func_proc, user_data: nil, on_free: nil)
64
+ @name = name
65
+ @params = params
66
+ @returns = returns
67
+ @func = func_proc
68
+ @user_data = user_data
69
+ @on_free = on_free
70
+ end
71
+
72
+ private
73
+
74
+ # Gets the pointer to this function.
75
+ # Warning: This should not be used
76
+ def pointer
77
+ return @_pointer if @_pointer
78
+
79
+ free = @on_free || proc {}
80
+ args = LibExtism.from_int_array(@params)
81
+ returns = LibExtism.from_int_array(@returns)
82
+ @_pointer = LibExtism.extism_function_new(@name, args, @params.length, returns, @returns.length, c_func, free,
83
+ nil)
84
+ end
85
+
86
+ def c_func
87
+ @c_func ||= proc do |plugin_ptr, inputs_ptr, inputs_size, outputs_ptr, outputs_size, _data_ptr|
88
+ current_plugin = Extism::CurrentPlugin.send(:new, plugin_ptr)
89
+ val_struct_size = LibExtism::ExtismVal.size
90
+
91
+ inputs = (0...inputs_size).map do |i|
92
+ Val.new(inputs_ptr + i * val_struct_size)
93
+ end
94
+ outputs = (0...outputs_size).map do |i|
95
+ Val.new(outputs_ptr + i * val_struct_size)
96
+ end
97
+
98
+ @func.call(current_plugin, inputs, outputs, @user_data)
99
+ end
100
+ end
101
+ end
102
+ end
data/lib/extism.rb CHANGED
@@ -1,6 +1,11 @@
1
- require "ffi"
2
- require "json"
3
- require_relative "./extism/version"
1
+ require 'ffi'
2
+ require 'json'
3
+ require_relative './extism/version'
4
+ require_relative './extism/plugin'
5
+ require_relative './extism/current_plugin'
6
+ require_relative './extism/libextism'
7
+ require_relative './extism/wasm'
8
+ require_relative './extism/host_environment'
4
9
 
5
10
  module Extism
6
11
  class Error < StandardError
@@ -10,258 +15,24 @@ module Extism
10
15
  #
11
16
  # @return [String] The version string of the Extism runtime
12
17
  def self.extism_version
13
- C.extism_version
18
+ LibExtism.extism_version
14
19
  end
15
20
 
16
21
  # Set log file and level, this is a global configuration
17
22
  # @param name [String] The path to the logfile
18
23
  # @param level [String] The log level. One of {"debug", "error", "info", "trace" }
19
24
  def self.set_log_file(name, level = nil)
20
- if level
21
- level = FFI::MemoryPointer::from_string(level)
22
- end
23
- C.extism_log_file(name, level)
25
+ LibExtism.extism_log_file(name, level)
24
26
  end
25
27
 
26
28
  $PLUGINS = {}
27
- $FREE_PLUGIN = proc { |id|
28
- x = $PLUGINS[id]
29
- if !x.nil?
30
- C.extism_plugin_free(x[:context].pointer, x[:plugin])
31
- $PLUGINS.delete(id)
32
- end
33
- }
34
-
35
- $CONTEXTS = {}
36
- $FREE_CONTEXT = proc { |id|
37
- x = $CONTEXTS[id]
38
- if !x.nil?
39
- C.extism_context_free($CONTEXTS[id])
40
- $CONTEXTS.delete(id)
29
+ $FREE_PLUGIN = proc { |ptr|
30
+ x = $PLUGINS[ptr]
31
+ unless x.nil?
32
+ LibExtism.extism_plugin_free(x[:plugin])
33
+ $PLUGINS.delete(ptr)
41
34
  end
42
35
  }
43
36
 
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*.
61
- class Context
62
- attr_reader :pointer
63
-
64
- # Initialize a new context
65
- def initialize
66
- @pointer = C.extism_context_new()
67
- $CONTEXTS[self.object_id] = @pointer
68
- ObjectSpace.define_finalizer(self, $FREE_CONTEXT)
69
- end
70
-
71
- # Remove all registered plugins in this context
72
- # @return [void]
73
- def reset
74
- C.extism_context_reset(@pointer)
75
- end
76
-
77
- # Free the context, this should be called when it is no longer needed
78
- # @return [void]
79
- def free
80
- return if @pointer.nil?
81
-
82
- $CONTEXTS.delete(self.object_id)
83
- C.extism_context_free(@pointer)
84
- @pointer = nil
85
- end
86
-
87
- # Create a new plugin from a WASM module or JSON encoded manifest
88
- #
89
- # @see Plugin#new
90
- # @param wasm [Hash, String] The manifest for the plugin. See https://extism.org/docs/concepts/manifest/.
91
- # @param wasi [Boolean] Enable WASI support
92
- # @param config [Hash] The plugin config
93
- # @return [Plugin]
94
- def plugin(wasm, wasi = false, config = nil)
95
- Plugin.new(wasm, wasi, config, self)
96
- end
97
- end
98
-
99
- # A context manager to create contexts and ensure that they get freed.
100
- #
101
- # @example Use with_context to auto-free
102
- # Extism.with_context do |ctx|
103
- # plugin = ctx.plugin(my_manifest)
104
- # puts plugin.call("my_func", "my-input")
105
- # end # frees context after exiting this block
106
- #
107
- # @yield [ctx] Yields the created Context
108
- # @return [Object] returns whatever your block returns
109
- def self.with_context(&block)
110
- ctx = Context.new
111
- begin
112
- x = block.call(ctx)
113
- return x
114
- ensure
115
- ctx.free
116
- end
117
- end
118
-
119
- # A CancelHandle can be used to cancel a running plugin from another thread
120
- class CancelHandle
121
- def initialize(handle)
122
- @handle = handle
123
- end
124
-
125
- # Cancel the plugin used to generate the handle
126
- def cancel
127
- return C.extism_plugin_cancel(@handle)
128
- end
129
- end
130
-
131
- # A Plugin represents an instance of your WASM program from the given manifest.
132
- class Plugin
133
- # Intialize a plugin
134
- #
135
- # @param wasm [Hash, String] The manifest or WASM binary. See https://extism.org/docs/concepts/manifest/.
136
- # @param wasi [Boolean] Enable WASI support
137
- # @param config [Hash] The plugin config
138
- # @param context [Context] The context to manager this plugin
139
- def initialize(wasm, wasi = false, config = nil, context = nil)
140
- if context.nil? then
141
- context = Context.new
142
- end
143
- @context = context
144
- if wasm.class == Hash
145
- wasm = JSON.generate(wasm)
146
- end
147
- code = FFI::MemoryPointer.new(:char, wasm.bytesize)
148
- code.put_bytes(0, wasm)
149
- @plugin = C.extism_plugin_new(context.pointer, code, wasm.bytesize, nil, 0, wasi)
150
- if @plugin < 0
151
- err = C.extism_error(@context.pointer, -1)
152
- if err&.empty?
153
- raise Error.new "extism_plugin_new failed"
154
- else
155
- raise Error.new err
156
- end
157
- end
158
- $PLUGINS[self.object_id] = { :plugin => @plugin, :context => context }
159
- ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
160
- if config != nil and @plugin >= 0
161
- s = JSON.generate(config)
162
- ptr = FFI::MemoryPointer::from_string(s)
163
- C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
164
- end
165
- end
166
-
167
- # Update a plugin with new WASM module or manifest
168
- #
169
- # @param wasm [Hash, String] The manifest or WASM binary. See https://extism.org/docs/concepts/manifest/.
170
- # @param wasi [Boolean] Enable WASI support
171
- # @param config [Hash] The plugin config
172
- # @return [void]
173
- def update(wasm, wasi = false, config = nil)
174
- if wasm.class == Hash
175
- wasm = JSON.generate(wasm)
176
- end
177
- code = FFI::MemoryPointer.new(:char, wasm.bytesize)
178
- code.put_bytes(0, wasm)
179
- ok = C.extism_plugin_update(@context.pointer, @plugin, code, wasm.bytesize, nil, 0, wasi)
180
- if !ok
181
- err = C.extism_error(@context.pointer, @plugin)
182
- if err&.empty?
183
- raise Error.new "extism_plugin_update failed"
184
- else
185
- raise Error.new err
186
- end
187
- end
188
-
189
- if config != nil
190
- s = JSON.generate(config)
191
- ptr = FFI::MemoryPointer::from_string(s)
192
- C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
193
- end
194
- end
195
-
196
- # Check if a function exists
197
- #
198
- # @param name [String] The name of the function
199
- # @return [Boolean] Returns true if function exists
200
- def has_function?(name)
201
- C.extism_plugin_function_exists(@context.pointer, @plugin, name)
202
- end
203
-
204
- # Call a function by name
205
- #
206
- # @param name [String] The function name
207
- # @param data [String] The input data for the function
208
- # @return [String] The output from the function in String form
209
- def call(name, data, &block)
210
- # If no block was passed then use Pointer::read_string
211
- block ||= ->(buf, len) { buf.read_string(len) }
212
- input = FFI::MemoryPointer::from_string(data)
213
- rc = C.extism_plugin_call(@context.pointer, @plugin, name, input, data.bytesize)
214
- if rc != 0
215
- err = C.extism_error(@context.pointer, @plugin)
216
- if err&.empty?
217
- raise Error.new "extism_call failed"
218
- else
219
- raise Error.new err
220
- end
221
- end
222
- out_len = C.extism_plugin_output_length(@context.pointer, @plugin)
223
- buf = C.extism_plugin_output_data(@context.pointer, @plugin)
224
- block.call(buf, out_len)
225
- end
226
-
227
- # Free a plugin, this should be called when the plugin is no longer needed
228
- #
229
- # @return [void]
230
- def free
231
- return if @context.pointer.nil?
232
-
233
- $PLUGINS.delete(self.object_id)
234
- C.extism_plugin_free(@context.pointer, @plugin)
235
- @plugin = -1
236
- end
237
-
238
- # Get a CancelHandle for a plugin
239
- def cancel_handle
240
- return CancelHandle.new(C.extism_plugin_cancel_handle(@context.pointer, @plugin))
241
- end
242
- end
243
-
244
- private
245
-
246
- # Private module used to interface with the Extism runtime.
247
- # *Warning*: Do not use or rely on this directly.
248
- module C
249
- extend FFI::Library
250
- ffi_lib "extism"
251
- attach_function :extism_context_new, [], :pointer
252
- attach_function :extism_context_free, [:pointer], :void
253
- attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :pointer, :uint64, :bool], :int32
254
- attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :pointer, :uint64, :bool], :bool
255
- attach_function :extism_error, [:pointer, :int32], :string
256
- attach_function :extism_plugin_call, [:pointer, :int32, :string, :pointer, :uint64], :int32
257
- attach_function :extism_plugin_function_exists, [:pointer, :int32, :string], :bool
258
- attach_function :extism_plugin_output_length, [:pointer, :int32], :uint64
259
- attach_function :extism_plugin_output_data, [:pointer, :int32], :pointer
260
- attach_function :extism_log_file, [:string, :pointer], :void
261
- attach_function :extism_plugin_free, [:pointer, :int32], :void
262
- attach_function :extism_context_reset, [:pointer], :void
263
- attach_function :extism_version, [], :string
264
- attach_function :extism_plugin_cancel_handle, [:pointer, :int32], :pointer
265
- attach_function :extism_plugin_cancel, [:pointer], :bool
266
- end
37
+ Memory = Struct.new(:offset, :len)
267
38
  end
data/sig/extism.rbs CHANGED
@@ -1,4 +1,4 @@
1
- module Extism
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end
1
+ module Extism
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
Binary file
data/wasm/reflect.wasm ADDED
Binary file
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: 0.5.0
4
+ version: 1.0.0.pre.rc.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - zach
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-24 00:00:00.000000000 Z
11
+ date: 2023-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -32,15 +32,23 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - ".yardopts"
35
- - GETTING_STARTED.md
36
35
  - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE
37
38
  - Makefile
39
+ - README.md
38
40
  - Rakefile
39
- - example.rb
40
41
  - lib/extism.rb
42
+ - lib/extism/current_plugin.rb
43
+ - lib/extism/host_environment.rb
44
+ - lib/extism/libextism.rb
45
+ - lib/extism/plugin.rb
41
46
  - lib/extism/version.rb
47
+ - lib/extism/wasm.rb
42
48
  - sig/extism.rbs
43
- - user_code.wasm
49
+ - wasm/count_vowels.wasm
50
+ - wasm/reflect.wasm
51
+ - wasm/store_credit.wasm
44
52
  homepage: https://github.com/extism/extism
45
53
  licenses:
46
54
  - MIT
@@ -59,9 +67,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
67
  version: 2.6.0
60
68
  required_rubygems_version: !ruby/object:Gem::Requirement
61
69
  requirements:
62
- - - ">="
70
+ - - ">"
63
71
  - !ruby/object:Gem::Version
64
- version: '0'
72
+ version: 1.3.1
65
73
  requirements: []
66
74
  rubygems_version: 3.4.10
67
75
  signing_key:
data/GETTING_STARTED.md DELETED
@@ -1,35 +0,0 @@
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/example.rb DELETED
@@ -1,11 +0,0 @@
1
- require "./lib/extism"
2
- require "json"
3
-
4
- manifest = {
5
- :wasm => [{ :path => "../wasm/code.wasm" }],
6
- }
7
-
8
- plugin = Extism::Plugin.new(manifest)
9
- res = JSON.parse(plugin.call("count_vowels", ARGV[0] || "this is a test"))
10
-
11
- puts res["count"]
data/user_code.wasm DELETED
Binary file