mirror of
https://github.com/extism/extism.git
synced 2026-01-12 15:28:05 -05:00
Compare commits
2 Commits
fix-releas
...
add-elixir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c31215ce7 | ||
|
|
fc95a99e40 |
18
.github/actions/extism/action.yml
vendored
18
.github/actions/extism/action.yml
vendored
@@ -1,18 +0,0 @@
|
||||
on: [workflow_call]
|
||||
|
||||
name: libextism
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Download libextism
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: libextism-${{ matrix.os }}
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: |
|
||||
sudo cp libextism.* /usr/local/lib
|
||||
sudo cp runtime/extism.h /usr/local/include
|
||||
41
.github/dependabot.yml
vendored
41
.github/dependabot.yml
vendored
@@ -1,41 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "python"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "mix"
|
||||
directory: "elixir"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "node"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "bundler"
|
||||
directory: "ruby"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
307
.github/workflows/ci.yml
vendored
307
.github/workflows/ci.yml
vendored
@@ -1,103 +1,15 @@
|
||||
on: [pull_request, workflow_dispatch]
|
||||
on: [push, pull_request]
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
LIBEXTISM_CRATE: libextism
|
||||
RUST_SDK_CRATE: extism
|
||||
|
||||
jobs:
|
||||
lib:
|
||||
name: Extism runtime lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Cache libextism
|
||||
id: cache-libextism
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/release/libextism.*
|
||||
key: ${{ runner.os }}-libextism-${{ hashFiles('runtime/**') }}-${{ hashFiles('manifest/**') }}
|
||||
- name: Cache target
|
||||
id: cache-target
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
|
||||
- name: Build
|
||||
if: steps.cache-libextism.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --release -p ${{ env.LIBEXTISM_CRATE }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: libextism-${{ matrix.os }}
|
||||
path: |
|
||||
target/release/libextism.*
|
||||
lint_and_test:
|
||||
name: Extism runtime lint and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
- name: Cache target
|
||||
id: cache-target
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-target-${{ env.GITHUB_SHA }}
|
||||
- name: Format
|
||||
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
|
||||
- name: Lint
|
||||
run: cargo clippy --release --all-features --no-deps -p ${{ env.RUNTIME_CRATE }}
|
||||
- name: Test
|
||||
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
rust:
|
||||
name: Rust
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Test Rust Host SDK
|
||||
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
|
||||
|
||||
elixir:
|
||||
name: Elixir
|
||||
needs: lib
|
||||
build_and_test:
|
||||
name: Build & Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
MIX_ENV: test
|
||||
@@ -109,7 +21,32 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Cache Rust environment
|
||||
uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Format
|
||||
run: cargo fmt --check -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Lint
|
||||
run: cargo clippy --release --no-deps -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Test
|
||||
run: cargo test --all-features --release -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Install extism shared library
|
||||
shell: bash
|
||||
run: sudo make install
|
||||
|
||||
- name: Setup Elixir Host SDK
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
uses: erlef/setup-beam@v1
|
||||
@@ -124,70 +61,30 @@ jobs:
|
||||
cd elixir
|
||||
LD_LIBRARY_PATH=/usr/local/lib mix do deps.get, test
|
||||
|
||||
go:
|
||||
name: Go
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Go env
|
||||
uses: actions/setup-go@v3
|
||||
|
||||
- name: Test Go Host SDK
|
||||
run: |
|
||||
go version
|
||||
cd go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go run main.go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go test
|
||||
cd go && LD_LIBRARY_PATH=/usr/local/lib go run main.go
|
||||
|
||||
python:
|
||||
name: Python
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
|
||||
- name: Test Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../README.md .
|
||||
poetry install
|
||||
poetry run python example.py
|
||||
poetry run python -m unittest discover
|
||||
|
||||
ruby:
|
||||
name: Ruby
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@@ -200,121 +97,41 @@ jobs:
|
||||
ruby example.rb
|
||||
rake test
|
||||
|
||||
node:
|
||||
name: Node
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 16
|
||||
|
||||
- name: Test Node Host SDK
|
||||
run: |
|
||||
cd node
|
||||
npm i
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run build
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run example
|
||||
LD_LIBRARY_PATH=/usr/local/lib npm run test
|
||||
npm run build
|
||||
LD_LIBRARY_PATH=/usr/local/lib node example.js
|
||||
|
||||
- name: Test Browser Runtime
|
||||
run: |
|
||||
cd browser
|
||||
npm i
|
||||
npm run test
|
||||
- name: Test Rust Host SDK
|
||||
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --release -p ${{ env.RUST_SDK_CRATE }}
|
||||
|
||||
ocaml:
|
||||
name: OCaml
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup OCaml env
|
||||
uses: ocaml/setup-ocaml@v2
|
||||
with:
|
||||
ocaml-compiler: ocaml-base-compiler.5.0.0~beta1
|
||||
- name: Cache OCaml
|
||||
id: cache-ocaml
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: _build
|
||||
key: ${{ runner.os }}-ocaml-${{ hashFiles('ocaml/lib/**') }}-${{ hashFiles('ocaml/bin/**') }}-${{ hashFiles('dune-project') }}
|
||||
- name: Build OCaml Host SDK
|
||||
if: steps.cache-ocaml.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune build
|
||||
- name: Test OCaml Host SDK
|
||||
run: |
|
||||
opam install -y --deps-only .
|
||||
cd ocaml
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune exec ./bin/main.exe
|
||||
LD_LIBRARY_PATH=/usr/local/lib opam exec -- dune runtest
|
||||
# - name: Setup OCaml env
|
||||
# uses: ocaml/setup-ocaml@v2
|
||||
|
||||
# - name: Test OCaml Host SDK
|
||||
# run: |
|
||||
# opam install -y .
|
||||
# cd ocaml
|
||||
# opam exec -- dune exec extism
|
||||
|
||||
haskell:
|
||||
name: Haskell
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Haskell env
|
||||
uses: haskell/actions/setup@v2
|
||||
with:
|
||||
enable-stack: true
|
||||
stack-version: "latest"
|
||||
- name: Cache Haskell
|
||||
id: cache-haskell
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .stack-work
|
||||
key: ${{ runner.os }}-haskell-${{ hashFiles('haskell/**') }}
|
||||
- name: Build Haskell Host SDK
|
||||
if: steps.cache-haskell.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib stack build
|
||||
|
||||
- name: Test Haskell SDK
|
||||
run: |
|
||||
cd haskell
|
||||
LD_LIBRARY_PATH=/usr/local/lib stack test
|
||||
|
||||
php:
|
||||
name: PHP
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup PHP env
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
@@ -330,48 +147,24 @@ jobs:
|
||||
composer install
|
||||
php index.php
|
||||
|
||||
cpp:
|
||||
name: C++
|
||||
needs: lib
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
brew install jsoncpp googletest pkg-config
|
||||
- name: Install C++ SDK deps
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get install g++ libjsoncpp-dev libgtest-dev pkg-config
|
||||
- name: Run C++ tests
|
||||
run: |
|
||||
cd cpp
|
||||
LD_LIBRARY_PATH=/usr/local/lib make example
|
||||
LD_LIBRARY_PATH=/usr/local/lib make test
|
||||
|
||||
sdk_api_coverage:
|
||||
name: SDK API Coverage Report
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install ripgrep
|
||||
pip3 install pycparser
|
||||
|
||||
- name: Run coverage script
|
||||
id: coverage
|
||||
run: |
|
||||
|
||||
28
.github/workflows/release-elixir.yaml
vendored
28
.github/workflows/release-elixir.yaml
vendored
@@ -1,28 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Elixir SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-elixir
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Elixir Host SDK
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
cp ../LICENSE .
|
||||
make publish
|
||||
|
||||
30
.github/workflows/release-node.yaml
vendored
30
.github/workflows/release-node.yaml
vendored
@@ -1,30 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Node SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-node
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
|
||||
- name: Release Node Host SDK
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
run: |
|
||||
cd node
|
||||
make publish
|
||||
|
||||
37
.github/workflows/release-python.yaml
vendored
37
.github/workflows/release-python.yaml
vendored
@@ -1,37 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Python SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-python
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
- name: Run image
|
||||
uses: abatilo/actions-poetry@v2
|
||||
|
||||
- name: Build Python Host SDK
|
||||
run: |
|
||||
cd python
|
||||
cp ../LICENSE .
|
||||
cp ../README.md .
|
||||
make clean
|
||||
make prepare
|
||||
poetry build
|
||||
|
||||
- name: Release Python Host SDK
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: ${{ secrets.PYPI_API_USER }}
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: python/dist/
|
||||
|
||||
25
.github/workflows/release-ruby.yaml
vendored
25
.github/workflows/release-ruby.yaml
vendored
@@ -1,25 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Ruby SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-ruby
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.1' # Version range or exact version of a Ruby version to use, using semvers version range syntax.
|
||||
|
||||
- name: Publish Ruby Gem
|
||||
env:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
|
||||
run: |
|
||||
cd ruby
|
||||
make publish
|
||||
|
||||
35
.github/workflows/release-rust.yaml
vendored
35
.github/workflows/release-rust.yaml
vendored
@@ -1,35 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Rust SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
name: release-rust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust env
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Release Rust Host SDK
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
# order of crate publication matter: manifest, runtime, rust
|
||||
|
||||
cargo publish --manifest-path manifest/Cargo.toml
|
||||
# allow for crates.io to update so dependant crates can locate extism-manifest
|
||||
sleep 5
|
||||
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
|
||||
112
.github/workflows/release.yml
vendored
112
.github/workflows/release.yml
vendored
@@ -1,13 +1,12 @@
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release
|
||||
|
||||
env:
|
||||
RUNTIME_MANIFEST: runtime/Cargo.toml
|
||||
RUNTIME_CRATE: libextism
|
||||
RUNTIME_CRATE: extism-runtime
|
||||
RUSTFLAGS: -C target-feature=-crt-static
|
||||
ARTIFACT_DIR: release-artifacts
|
||||
|
||||
@@ -214,3 +213,112 @@ jobs:
|
||||
files: |
|
||||
*.tar.gz
|
||||
*.txt
|
||||
|
||||
release-sdks:
|
||||
needs: [release-linux, release-macos] # release-windows
|
||||
name: publish-sdks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node env
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
|
||||
- name: Release Node Host SDK
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
|
||||
CI: true
|
||||
run: |
|
||||
cd node
|
||||
npm publish
|
||||
|
||||
- name: Setup Rust env
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Release Rust Host SDK
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
# order of crate publication matter: manifest, runtime, rust
|
||||
|
||||
cargo publish --manifest-path manifest/Cargo.toml
|
||||
# allow for crates.io to update so dependant crates can locate extism-manifest
|
||||
sleep 5
|
||||
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
- name: Setup Elixir Host SDK
|
||||
uses: erlef/setup-beam@v1
|
||||
with:
|
||||
experimental-otp: true
|
||||
otp-version: '25.0.4'
|
||||
elixir-version: '1.14.0'
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
cp ../LICENSE .
|
||||
mix do deps.get, deps.compile
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.1' # Version range or exact version of a Ruby version to use, using semvers version range syntax.
|
||||
|
||||
- name: Publish Ruby Gem
|
||||
env:
|
||||
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_TOKEN }}
|
||||
run: |
|
||||
cd ruby
|
||||
gem build extism.gemspec
|
||||
gem push extism*.gem -k $RUBYGEMS_API_KEY
|
||||
|
||||
- name: Publish Elixir Host SDK to hex.pm
|
||||
env:
|
||||
HEX_API_KEY: ${{ secrets.HEX_PM_API_TOKEN }}
|
||||
run: |
|
||||
cd elixir
|
||||
mix do deps.get, deps.compile
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
- name: Setup Python env
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
check-latest: true
|
||||
|
||||
- name: Build Python Host SDK
|
||||
run: |
|
||||
pushd python
|
||||
python3 -m pip install --upgrade build
|
||||
cp ../LICENSE .
|
||||
cp ../README.md .
|
||||
python3 -m poetry build
|
||||
popd
|
||||
|
||||
- name: Release Python Host SDK
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
env:
|
||||
INPUT_VERIFY_METADATA: false
|
||||
with:
|
||||
user: ${{ secrets.PYPI_API_USER }}
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: python/dist/
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,8 +13,6 @@ __pycache__
|
||||
python/dist
|
||||
python/poetry.lock
|
||||
c/main
|
||||
cpp/test/test
|
||||
cpp/example
|
||||
go/main
|
||||
ruby/.bundle/
|
||||
ruby/.yardoc
|
||||
@@ -25,6 +23,7 @@ ruby/pkg/
|
||||
ruby/spec/reports/
|
||||
ruby/tmp/
|
||||
ruby/Gemfile.lock
|
||||
cpp/example
|
||||
rust/target
|
||||
rust/test.log
|
||||
ocaml/duniverse
|
||||
|
||||
@@ -3,8 +3,8 @@ members = [
|
||||
"manifest",
|
||||
"runtime",
|
||||
"rust",
|
||||
"libextism"
|
||||
]
|
||||
exclude = [
|
||||
"elixir/native/extism_nif"
|
||||
]
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -25,7 +25,7 @@ lint:
|
||||
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path runtime/Cargo.toml
|
||||
|
||||
install:
|
||||
install runtime/extism.h $(DEST)/include
|
||||
|
||||
11
README.md
11
README.md
@@ -10,16 +10,14 @@ The universal plug-in system. Run WebAssembly extensions inside your app. Use id
|
||||
[Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
|
||||
[Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
|
||||
[C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
|
||||
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk), [Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk), [PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk), [Elixir/Erlang](https://extism.org/docs/integrate-into-your-codebase/elixir-or-erlang-host-sdk) & more (others coming soon).
|
||||
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk), [Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk), [PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk) & more (others coming soon).
|
||||
|
||||
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk).
|
||||
|
||||
<p align="center">
|
||||
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/200043015-ddfe5833-0252-43a8-bc9e-5b3f829c37d1.png" alt="Extism embedded SDK language support"/>
|
||||
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/191437220-4030840d-1a9e-47b9-a44a-39a004885308.png"/>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
Add a flexible, secure, and _bLaZiNg FaSt_ plug-in system to your project. Server, desktop, mobile, web, database -- you name it. Enable users to write and execute safe extensions to your software in **3 easy steps:**
|
||||
|
||||
### 1. Import
|
||||
@@ -53,9 +51,8 @@ The easiest way to start would be to join the [Discord](https://discord.gg/cx3us
|
||||
Extism is an open-source product from the team at:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/198204119-5afdebb9-a5d8-4322-bd2a-46179c8d7b24.svg"/></a>
|
||||
</p>
|
||||
|
||||
<a href="https://dylib.so" _target="blanks"><img width="200px" src="https://user-images.githubusercontent.com/7517515/195408048-0f199b26-cba3-4635-b683-f43b1e610c82.svg"/></a>
|
||||
</p>![dylibso-logo-outline]
|
||||
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
|
||||
130
browser/.gitignore
vendored
130
browser/.gitignore
vendored
@@ -1,130 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
const { build } = require("esbuild");
|
||||
const { dependencies, peerDependencies } = require('./package.json')
|
||||
|
||||
const sharedConfig = {
|
||||
entryPoints: ["src/index.ts"],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
drop: [], // preseve debugger statements
|
||||
external: Object.keys(dependencies || {}).concat(Object.keys(peerDependencies || {})),
|
||||
};
|
||||
|
||||
build({
|
||||
...sharedConfig,
|
||||
platform: 'node', // for CJS
|
||||
outfile: "dist/index.js",
|
||||
});
|
||||
|
||||
build({
|
||||
...sharedConfig,
|
||||
outfile: "dist/index.esm.js",
|
||||
platform: 'neutral', // for ESM
|
||||
format: "esm",
|
||||
});
|
||||
Binary file not shown.
@@ -1,238 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
|
||||
<style>
|
||||
#main {
|
||||
width: 100%;
|
||||
}
|
||||
.manifest {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
.urlInput {
|
||||
width: 600px;
|
||||
}
|
||||
.funcName {
|
||||
width: 150px;
|
||||
}
|
||||
.textAreas {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
.inputBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.inputBox > textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputBox > textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.space {
|
||||
height: 80px;
|
||||
}
|
||||
.dragAreas {
|
||||
display: flex; /* or inline-flex */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.dragInput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-style: dotted;
|
||||
border-color: #000;
|
||||
}
|
||||
.dragOutput {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dropZone {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.outputImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/babel">
|
||||
function getBase64(file, cb) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = function () {
|
||||
cb(reader.result)
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
console.log("error")
|
||||
};
|
||||
}
|
||||
|
||||
function arrayTob64(buffer) {
|
||||
var binary = '';
|
||||
var bytes = [].slice.call(buffer);
|
||||
bytes.forEach((b) => binary += String.fromCharCode(b));
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
url: "https://raw.githubusercontent.com/extism/extism/main/wasm/code.wasm",
|
||||
input: new Uint8Array(),
|
||||
output: new Uint8Array(),
|
||||
func_name: "count_vowels",
|
||||
functions: []
|
||||
}
|
||||
|
||||
async loadFunctions(url) {
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] })
|
||||
let functions = await plugin.getExportedFunctions()
|
||||
console.log("funcs ", functions)
|
||||
this.setState({functions})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadFunctions(this.state.url)
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.extismContext = props.extismContext
|
||||
}
|
||||
|
||||
handleInputChange(e) {
|
||||
e.preventDefault();
|
||||
this.setState({ [e.target.name]: e.target.value })
|
||||
if (e.target.name === "url") {
|
||||
this.loadFunctions(e.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
onInputKeyPress(e) {
|
||||
if (e.keyCode == 13 && e.shiftKey == true) {
|
||||
e.preventDefault()
|
||||
this.handleOnRun()
|
||||
}
|
||||
}
|
||||
|
||||
async handleOnRun(e) {
|
||||
e && e.preventDefault && e.preventDefault();
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] })
|
||||
let result = await plugin.call(this.state.func_name, this.state.input)
|
||||
let output = result
|
||||
this.setState({output})
|
||||
}
|
||||
|
||||
nop = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
handleDrop = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let files = [...e.dataTransfer.files];
|
||||
if (files && files.length == 1) {
|
||||
let file = files[0]
|
||||
console.log(file)
|
||||
file.arrayBuffer().then(b => {
|
||||
this.setState({input: new Uint8Array(b)})
|
||||
this.handleOnRun()
|
||||
})
|
||||
} else {
|
||||
throw Error("Only one file please")
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const funcOptions = this.state.functions.map(f => <option value={f}>{f}</option>)
|
||||
let image = null
|
||||
if (this.state.output) {
|
||||
image = <img src={`data:image/png;base64,${arrayTob64(this.state.output)}`}/>
|
||||
}
|
||||
|
||||
return <div className="app">
|
||||
<div className="manifest">
|
||||
<div>
|
||||
<label>WASM Url: </label>
|
||||
<input type="text" name="url" className="urlInput" value={this.state.url} onChange={this.handleInputChange.bind(this)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Function: </label>
|
||||
<select type="text" name="func_name" className="funcName" value={this.state.func_name} onChange={this.handleInputChange.bind(this)}>
|
||||
{funcOptions}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={this.handleOnRun.bind(this)}>Run</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="textAreas">
|
||||
<div className="inputBox">
|
||||
<h3>Text Input</h3>
|
||||
<textarea name="input" value={this.state.input} onChange={this.handleInputChange.bind(this)} onKeyDown={this.onInputKeyPress.bind(this)}></textarea>
|
||||
</div>
|
||||
<div className="outputBox">
|
||||
<h3>Text Output</h3>
|
||||
<textarea name="output" value={new TextDecoder().decode(this.state.output)} ></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space" />
|
||||
<div className="dragAreas">
|
||||
<div className="dragInput">
|
||||
<h3>Image Input</h3>
|
||||
<div className="dropZone"
|
||||
onDrop={this.handleDrop.bind(this)}
|
||||
onDragOver={this.nop.bind(this)}
|
||||
onDragEnter={this.nop.bind(this)}
|
||||
onDragLeave={this.nop.bind(this)}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dragOutput">
|
||||
<h3>Image Output</h3>
|
||||
<div className="outputImage">
|
||||
{image}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
window.App = App
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import {ExtismContext} from './dist/index.esm.js'
|
||||
const e = React.createElement;
|
||||
|
||||
window.onload = () => {
|
||||
const domContainer = document.getElementById('main');
|
||||
console.log(domContainer)
|
||||
const root = ReactDOM.createRoot(domContainer);
|
||||
const extismContext = new ExtismContext()
|
||||
root.render(e(App, {extismContext}));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
10569
browser/package-lock.json
generated
10569
browser/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "@extism/runtime-browser",
|
||||
"version": "0.0.1",
|
||||
"description": "Extism runtime in the browser",
|
||||
"scripts": {
|
||||
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"lint": "tslint -p tsconfig.json",
|
||||
"test": "jest --config jest.config.js"
|
||||
},
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"module": "dist/index.esm.js",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"author": "The Extism Authors <oss@extism.org>",
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.2",
|
||||
"esbuild": "^0.15.13",
|
||||
"jest": "^29.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.23.20",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
type MemoryBlock = { offset: bigint; length: bigint };
|
||||
|
||||
export default class Allocator {
|
||||
currentIndex: bigint;
|
||||
active: Record<number, MemoryBlock>;
|
||||
freed: MemoryBlock[];
|
||||
memory: Uint8Array;
|
||||
|
||||
constructor(n: number) {
|
||||
this.currentIndex = BigInt(1);
|
||||
this.active = {};
|
||||
this.freed = [];
|
||||
this.memory = new Uint8Array(n);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.currentIndex = BigInt(1);
|
||||
this.active = {};
|
||||
this.freed = [];
|
||||
}
|
||||
|
||||
alloc(length: bigint): bigint {
|
||||
for (var i = 0; i < this.freed.length; i++) {
|
||||
let block = this.freed[i];
|
||||
if (block.length === length) {
|
||||
this.active[Number(block.offset)] = block;
|
||||
this.freed.splice(i, 1);
|
||||
return block.offset;
|
||||
} else if (block.length > length + BigInt(64)) {
|
||||
const newBlock = { offset: block.offset, length };
|
||||
block.offset += length;
|
||||
block.length -= length;
|
||||
return newBlock.offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize memory if needed
|
||||
// TODO: put a limit on the memory size
|
||||
if (BigInt(this.memory.length) < this.currentIndex + length) {
|
||||
const tmp = new Uint8Array(Number(this.currentIndex + length + BigInt(64)));
|
||||
tmp.set(this.memory);
|
||||
this.memory = tmp;
|
||||
}
|
||||
|
||||
const offset = this.currentIndex;
|
||||
this.currentIndex += length;
|
||||
this.active[Number(offset)] = { offset, length };
|
||||
return offset;
|
||||
}
|
||||
|
||||
getBytes(offset: bigint): Uint8Array | null {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Uint8Array(this.memory.buffer, Number(offset), Number(block.length));
|
||||
}
|
||||
|
||||
getString(offset: bigint): string | null {
|
||||
const bytes = this.getBytes(offset);
|
||||
if (bytes === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
allocBytes(data: Uint8Array): bigint {
|
||||
const offs = this.alloc(BigInt(data.length));
|
||||
const bytes = this.getBytes(offs);
|
||||
if (bytes === null) {
|
||||
this.free(offs);
|
||||
return BigInt(0);
|
||||
}
|
||||
|
||||
bytes.set(data);
|
||||
return offs;
|
||||
}
|
||||
|
||||
allocString(data: string): bigint {
|
||||
const bytes = new TextEncoder().encode(data);
|
||||
return this.allocBytes(bytes);
|
||||
}
|
||||
|
||||
getLength(offset: bigint): bigint {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return BigInt(0);
|
||||
}
|
||||
|
||||
return block.length;
|
||||
}
|
||||
|
||||
free(offset: bigint) {
|
||||
const block = this.active[Number(offset)];
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.active[Number(offset)];
|
||||
this.freed.push(block);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Manifest, PluginConfig, ManifestWasmFile, ManifestWasmData } from './manifest';
|
||||
import ExtismPlugin from './plugin';
|
||||
|
||||
/**
|
||||
* Can be a {@link Manifest} or just the raw bytes of the WASM module as an ArrayBuffer.
|
||||
* We recommend using {@link Manifest}
|
||||
*/
|
||||
type ManifestData = Manifest | ArrayBuffer;
|
||||
|
||||
/**
|
||||
* A Context is needed to create plugins. The Context
|
||||
* is where your plugins live. Freeing the context
|
||||
* frees all of the plugins in its scope.
|
||||
*/
|
||||
export default class ExtismContext {
|
||||
/**
|
||||
* Create a plugin managed by this context
|
||||
*
|
||||
* @param manifest - The {@link ManifestData} describing the plugin code and config
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
async newPlugin(manifest: ManifestData, config?: PluginConfig) {
|
||||
let moduleData: ArrayBuffer | null = null;
|
||||
if (manifest instanceof ArrayBuffer) {
|
||||
moduleData = manifest;
|
||||
} else if ((manifest as Manifest).wasm) {
|
||||
const wasmData = (manifest as Manifest).wasm;
|
||||
if (wasmData.length > 1) throw Error('This runtime only supports one module in Manifest.wasm');
|
||||
const wasm = wasmData[0];
|
||||
if ((wasm as ManifestWasmData).data) {
|
||||
moduleData = (wasm as ManifestWasmData).data;
|
||||
} else if ((wasm as ManifestWasmFile).path) {
|
||||
const response = await fetch((wasm as ManifestWasmFile).path);
|
||||
moduleData = await response.arrayBuffer();
|
||||
console.dir(moduleData);
|
||||
}
|
||||
}
|
||||
if (!moduleData) {
|
||||
throw Error(`Unsure how to interpret manifest ${manifest}`);
|
||||
}
|
||||
|
||||
return new ExtismPlugin(moduleData, config);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ExtismContext } from './';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function parse(bytes: Uint8Array): any {
|
||||
return JSON.parse(new TextDecoder().decode(bytes));
|
||||
}
|
||||
|
||||
describe('', () => {
|
||||
it('can load and call a plugin', async () => {
|
||||
const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
|
||||
const ctx = new ExtismContext();
|
||||
const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
|
||||
const functions = await plugin.getExports();
|
||||
expect(Object.keys(functions).filter(x => !x.startsWith("__") && x !== "memory")).toEqual(['count_vowels']);
|
||||
let output = await plugin.call('count_vowels', 'this is a test');
|
||||
expect(parse(output)).toEqual({ count: 4 });
|
||||
output = await plugin.call('count_vowels', 'this is a test again');
|
||||
expect(parse(output)).toEqual({ count: 7 });
|
||||
output = await plugin.call('count_vowels', 'this is a test thrice');
|
||||
expect(parse(output)).toEqual({ count: 6 });
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
import ExtismContext from './context';
|
||||
|
||||
export { ExtismContext };
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Represents a path or url to a WASM module
|
||||
*/
|
||||
export type ManifestWasmFile = {
|
||||
path: string;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the raw bytes of a WASM file loaded into memory
|
||||
*/
|
||||
export type ManifestWasmData = {
|
||||
data: Uint8Array;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link ExtismPlugin} Config
|
||||
*/
|
||||
export type PluginConfig = Map<string, string>;
|
||||
|
||||
/**
|
||||
* The WASM to load as bytes or a path
|
||||
*/
|
||||
export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
|
||||
|
||||
/**
|
||||
* The manifest which describes the {@link ExtismPlugin} code and
|
||||
* runtime constraints.
|
||||
*
|
||||
* @see [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest)
|
||||
*/
|
||||
export type Manifest = {
|
||||
wasm: Array<ManifestWasm>;
|
||||
//memory?: ManifestMemory;
|
||||
config?: PluginConfig;
|
||||
allowed_hosts?: Array<string>;
|
||||
};
|
||||
@@ -1,170 +0,0 @@
|
||||
import Allocator from './allocator';
|
||||
import { PluginConfig } from './manifest';
|
||||
|
||||
export default class ExtismPlugin {
|
||||
moduleData: ArrayBuffer;
|
||||
allocator: Allocator;
|
||||
config?: PluginConfig;
|
||||
vars: Record<string, Uint8Array>;
|
||||
input: Uint8Array;
|
||||
output: Uint8Array;
|
||||
module?: WebAssembly.WebAssemblyInstantiatedSource;
|
||||
|
||||
constructor(moduleData: ArrayBuffer, config?: PluginConfig) {
|
||||
this.moduleData = moduleData;
|
||||
this.allocator = new Allocator(1024 * 1024);
|
||||
this.config = config;
|
||||
this.vars = {};
|
||||
this.input = new Uint8Array();
|
||||
this.output = new Uint8Array();
|
||||
}
|
||||
|
||||
async getExports(): Promise<WebAssembly.Exports> {
|
||||
const module = await this._instantiateModule();
|
||||
return module.instance.exports;
|
||||
}
|
||||
|
||||
async getImports(): Promise<WebAssembly.ModuleImportDescriptor[]> {
|
||||
const module = await this._instantiateModule();
|
||||
return WebAssembly.Module.imports(module.module);
|
||||
}
|
||||
|
||||
async getInstance(): Promise<WebAssembly.Instance> {
|
||||
const module = await this._instantiateModule();
|
||||
return module.instance;
|
||||
}
|
||||
|
||||
async call(func_name: string, input: Uint8Array | string): Promise<Uint8Array> {
|
||||
const module = await this._instantiateModule();
|
||||
|
||||
if (typeof input === 'string') {
|
||||
this.input = new TextEncoder().encode(input);
|
||||
} else if (input instanceof Uint8Array) {
|
||||
this.input = input;
|
||||
} else {
|
||||
throw new Error('input should be string or Uint8Array');
|
||||
}
|
||||
|
||||
this.allocator.reset();
|
||||
|
||||
let func = module.instance.exports[func_name];
|
||||
if (!func) {
|
||||
throw Error(`function does not exist ${func_name}`);
|
||||
}
|
||||
//@ts-ignore
|
||||
func();
|
||||
return this.output;
|
||||
}
|
||||
|
||||
async _instantiateModule(): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
|
||||
if (this.module) {
|
||||
return this.module;
|
||||
}
|
||||
const environment = this.makeEnv();
|
||||
this.module = await WebAssembly.instantiate(this.moduleData, { env: environment });
|
||||
return this.module;
|
||||
}
|
||||
|
||||
makeEnv(): any {
|
||||
const plugin = this;
|
||||
return {
|
||||
extism_alloc(n: bigint): bigint {
|
||||
return plugin.allocator.alloc(n);
|
||||
},
|
||||
extism_free(n: bigint) {
|
||||
plugin.allocator.free(n);
|
||||
},
|
||||
extism_load_u8(n: bigint): number {
|
||||
return plugin.allocator.memory[Number(n)];
|
||||
},
|
||||
extism_load_u64(n: bigint): bigint {
|
||||
let cast = new DataView(plugin.allocator.memory.buffer, Number(n));
|
||||
return cast.getBigUint64(0, true);
|
||||
},
|
||||
extism_store_u8(offset: bigint, n: number) {
|
||||
plugin.allocator.memory[Number(offset)] = Number(n);
|
||||
},
|
||||
extism_store_u64(offset: bigint, n: bigint) {
|
||||
const tmp = new DataView(plugin.allocator.memory.buffer, Number(offset));
|
||||
tmp.setBigUint64(0, n, true);
|
||||
},
|
||||
extism_input_length(): bigint {
|
||||
return BigInt(plugin.input.length);
|
||||
},
|
||||
extism_input_load_u8(i: bigint): number {
|
||||
return plugin.input[Number(i)];
|
||||
},
|
||||
extism_input_load_u64(idx: bigint): bigint {
|
||||
let cast = new DataView(plugin.input.buffer, Number(idx));
|
||||
return cast.getBigUint64(0, true);
|
||||
},
|
||||
extism_output_set(offset: bigint, length: bigint) {
|
||||
const offs = Number(offset);
|
||||
const len = Number(length);
|
||||
plugin.output = plugin.allocator.memory.slice(offs, offs + len);
|
||||
},
|
||||
extism_error_set(i: bigint) {
|
||||
throw plugin.allocator.getString(i);
|
||||
},
|
||||
extism_config_get(i: bigint): bigint {
|
||||
if (typeof plugin.config === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
const key = plugin.allocator.getString(i);
|
||||
if (key === null) {
|
||||
return BigInt(0);
|
||||
}
|
||||
const value = plugin.config.get(key);
|
||||
if (typeof value === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
return plugin.allocator.allocString(value);
|
||||
},
|
||||
extism_var_get(i: bigint): bigint {
|
||||
const key = plugin.allocator.getString(i);
|
||||
if (key === null) {
|
||||
return BigInt(0);
|
||||
}
|
||||
const value = plugin.vars[key];
|
||||
if (typeof value === 'undefined') {
|
||||
return BigInt(0);
|
||||
}
|
||||
return plugin.allocator.allocBytes(value);
|
||||
},
|
||||
extism_var_set(n: bigint, i: bigint) {
|
||||
const key = plugin.allocator.getString(n);
|
||||
if (key === null) {
|
||||
return;
|
||||
}
|
||||
const value = plugin.allocator.getBytes(i);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
plugin.vars[key] = value;
|
||||
},
|
||||
extism_http_request(n: bigint, i: bigint): number {
|
||||
debugger;
|
||||
return 0;
|
||||
},
|
||||
extism_length(i: bigint): bigint {
|
||||
return plugin.allocator.getLength(i);
|
||||
},
|
||||
extism_log_warn(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.warn(s);
|
||||
},
|
||||
extism_log_info(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.log(s);
|
||||
},
|
||||
extism_log_debug(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.debug(s);
|
||||
},
|
||||
extism_log_error(i: bigint) {
|
||||
const s = plugin.allocator.getString(i);
|
||||
console.error(s);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:recommended",
|
||||
"tslint-config-prettier"
|
||||
]
|
||||
}
|
||||
@@ -1,50 +1,50 @@
|
||||
{
|
||||
"name": "extism/extism",
|
||||
"description": "Make your software programmable. Run WebAssembly extensions in your app using the first off-the-shelf, universal plug-in system.",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"WebAssembly",
|
||||
"plugin-system",
|
||||
"runtime",
|
||||
"plug-in"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org",
|
||||
"homepage": "https://extism.org"
|
||||
"name": "extism/extism",
|
||||
"description": "Make your software programmable. Run WebAssembly extensions in your app using the first off-the-shelf, universal plug-in system.",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"WebAssembly",
|
||||
"plugin-system",
|
||||
"runtime",
|
||||
"plug-in"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org",
|
||||
"homepage": "https://extism.org"
|
||||
},
|
||||
{
|
||||
"name": "Dylibso, Inc.",
|
||||
"email": "oss@dylib.so",
|
||||
"homepage": "https://dylib.so"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8",
|
||||
"ircmaxell/ffime": "dev-master"
|
||||
},
|
||||
{
|
||||
"name": "Dylibso, Inc.",
|
||||
"email": "oss@dylib.so",
|
||||
"homepage": "https://dylib.so"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8",
|
||||
"ircmaxell/ffime": "dev-master"
|
||||
},
|
||||
"suggest": {},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Extism\\": "php/src/"
|
||||
"suggest": {},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Extism\\": "php/src/"
|
||||
},
|
||||
"files": [
|
||||
"php/src/Plugin.php",
|
||||
"php/src/generate.php",
|
||||
"php/src/extism.h"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"php/src/Context.php",
|
||||
"php/src/Plugin.php",
|
||||
"php/src/extism.h"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {},
|
||||
"scripts": {},
|
||||
"scripts-descriptions": {}
|
||||
"autoload-dev": {
|
||||
"psr-4": {}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {},
|
||||
"scripts": {},
|
||||
"scripts-descriptions": {}
|
||||
}
|
||||
16
cpp/Makefile
16
cpp/Makefile
@@ -1,15 +1,3 @@
|
||||
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
|
||||
build:
|
||||
clang++ -std=c++11 -o example example.cpp -lextism -L .
|
||||
|
||||
build-example:
|
||||
$(CXX) -std=c++11 -o example -I. example.cpp $(FLAGS)
|
||||
|
||||
.PHONY: example
|
||||
example: build-example
|
||||
./example
|
||||
|
||||
build-test:
|
||||
$(CXX) -std=c++11 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
|
||||
.PHONY: test
|
||||
test: build-test
|
||||
cd test && ./test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#define EXTISM_NO_JSON
|
||||
#include "extism.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
203
cpp/extism.hpp
203
cpp/extism.hpp
@@ -1,123 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
#if __has_include(<jsoncpp/json/json.h>)
|
||||
#include <jsoncpp/json/json.h>
|
||||
#else
|
||||
#include <json/json.h>
|
||||
#endif
|
||||
#endif // EXTISM_NO_JSON
|
||||
|
||||
extern "C" {
|
||||
#include <extism.h>
|
||||
}
|
||||
|
||||
namespace extism {
|
||||
|
||||
typedef std::map<std::string, std::string> Config;
|
||||
class Wasm {
|
||||
public:
|
||||
std::string path;
|
||||
std::string url;
|
||||
// TODO: add base64 encoded raw data
|
||||
std::string hash;
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Json::Value json() const {
|
||||
Json::Value doc;
|
||||
|
||||
if (!this->path.empty()) {
|
||||
doc["path"] = this->path;
|
||||
}
|
||||
|
||||
if (!this->url.empty()) {
|
||||
doc["url"] = this->url;
|
||||
}
|
||||
|
||||
if (!this->hash.empty()) {
|
||||
doc["hash"] = this->hash;
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class Manifest {
|
||||
public:
|
||||
Config config;
|
||||
std::vector<Wasm> wasm;
|
||||
std::vector<std::string> allowed_hosts;
|
||||
|
||||
static Manifest path(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_path(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
static Manifest url(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_url(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
std::string json() const {
|
||||
Json::Value doc;
|
||||
Json::Value wasm;
|
||||
for (auto w : this->wasm) {
|
||||
wasm.append(w.json());
|
||||
}
|
||||
|
||||
doc["wasm"] = wasm;
|
||||
|
||||
if (!this->config.empty()) {
|
||||
Json::Value conf;
|
||||
|
||||
for (auto k : this->config) {
|
||||
conf[k.first] = k.second;
|
||||
}
|
||||
doc["config"] = conf;
|
||||
}
|
||||
|
||||
if (!this->allowed_hosts.empty()) {
|
||||
Json::Value h;
|
||||
|
||||
for (auto s : this->allowed_hosts) {
|
||||
h.append(s);
|
||||
}
|
||||
doc["allowed_hosts"] = h;
|
||||
}
|
||||
|
||||
Json::FastWriter writer;
|
||||
return writer.write(doc);
|
||||
}
|
||||
#endif
|
||||
|
||||
void add_wasm_path(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w.path = s;
|
||||
w.hash = hash;
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
void add_wasm_url(std::string u, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w.url = u;
|
||||
w.hash = hash;
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
void allow_host(std::string host) { this->allowed_hosts.push_back(host); }
|
||||
|
||||
void set_config(std::string k, std::string v) { this->config[k] = v; }
|
||||
};
|
||||
|
||||
class Error : public std::exception {
|
||||
private:
|
||||
std::string message;
|
||||
@@ -133,10 +24,6 @@ public:
|
||||
const uint8_t *data;
|
||||
ExtismSize length;
|
||||
|
||||
std::string string() { return (std::string)(*this); }
|
||||
|
||||
std::vector<uint8_t> vector() { return (std::vector<uint8_t>)(*this); }
|
||||
|
||||
operator std::string() { return std::string((const char *)data, length); }
|
||||
operator std::vector<uint8_t>() {
|
||||
return std::vector<uint8_t>(data, data + length);
|
||||
@@ -158,29 +45,11 @@ public:
|
||||
this->context = ctx;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Plugin(std::shared_ptr<ExtismContext> ctx, const Manifest &manifest,
|
||||
bool with_wasi = false) {
|
||||
auto buffer = manifest.json();
|
||||
this->plugin = extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), with_wasi);
|
||||
if (this->plugin < 0) {
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin from manifest" : err);
|
||||
}
|
||||
this->context = ctx;
|
||||
}
|
||||
#endif
|
||||
|
||||
~Plugin() {
|
||||
extism_plugin_free(this->context.get(), this->plugin);
|
||||
this->plugin = -1;
|
||||
}
|
||||
|
||||
ExtismPlugin id() const { return this->plugin; }
|
||||
|
||||
ExtismContext *get_context() const { return this->context.get(); }
|
||||
|
||||
void update(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
|
||||
length, with_wasi);
|
||||
@@ -190,46 +59,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
void update(const Manifest &manifest, bool with_wasi = false) {
|
||||
auto buffer = manifest.json();
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin,
|
||||
(const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
}
|
||||
}
|
||||
|
||||
void config(const Config &data) {
|
||||
Json::Value conf;
|
||||
|
||||
for (auto k : data) {
|
||||
conf[k.first] = k.second;
|
||||
}
|
||||
|
||||
Json::FastWriter writer;
|
||||
auto s = writer.write(conf);
|
||||
this->config(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
void config(const char *json, size_t length) {
|
||||
bool b = extism_plugin_config(this->context.get(), this->plugin,
|
||||
(const uint8_t *)json, length);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), this->plugin);
|
||||
throw Error(err == nullptr ? "Unable to update plugin config" : err);
|
||||
}
|
||||
}
|
||||
|
||||
void config(const std::string &json) {
|
||||
this->config(json.c_str(), json.size());
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const uint8_t *input,
|
||||
ExtismSize input_length) const {
|
||||
ExtismSize input_length) {
|
||||
|
||||
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
|
||||
func.c_str(), input, input_length);
|
||||
if (rc != 0) {
|
||||
@@ -248,19 +80,13 @@ public:
|
||||
return Buffer(ptr, length);
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func,
|
||||
const std::vector<uint8_t> &input) const {
|
||||
Buffer call(const std::string &func, const std::vector<uint8_t> &input) {
|
||||
return this->call(func, input.data(), input.size());
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const std::string &input) const {
|
||||
Buffer call(const std::string &func, const std::string &input) {
|
||||
return this->call(func, (const uint8_t *)input.c_str(), input.size());
|
||||
}
|
||||
|
||||
bool function_exists(const std::string &func) const {
|
||||
return extism_plugin_function_exists(this->context.get(), this->plugin,
|
||||
func.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
class Context {
|
||||
@@ -271,33 +97,18 @@ public:
|
||||
extism_context_free);
|
||||
}
|
||||
|
||||
Plugin plugin(const uint8_t *wasm, size_t length,
|
||||
bool with_wasi = false) const {
|
||||
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, wasm, length, with_wasi);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) const {
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
|
||||
with_wasi);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::vector<uint8_t> &data,
|
||||
bool with_wasi = false) const {
|
||||
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Plugin plugin(const Manifest &manifest, bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, manifest, with_wasi);
|
||||
}
|
||||
#endif
|
||||
|
||||
void reset() { extism_context_reset(this->pointer.get()); }
|
||||
};
|
||||
|
||||
inline bool set_log_file(const char *filename, const char *level) {
|
||||
return extism_log_file(filename, level);
|
||||
}
|
||||
|
||||
inline std::string version() { return std::string(extism_version()); }
|
||||
} // namespace extism
|
||||
|
||||
Binary file not shown.
@@ -1,73 +0,0 @@
|
||||
#include "../extism.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
std::vector<uint8_t> read(const char *filename) {
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
return std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
namespace {
|
||||
using namespace extism;
|
||||
|
||||
TEST(Context, Basic) {
|
||||
Context context;
|
||||
ASSERT_NE(context.pointer, nullptr);
|
||||
}
|
||||
|
||||
TEST(Plugin, Manifest) {
|
||||
Context context;
|
||||
Manifest manifest = Manifest::path("code.wasm");
|
||||
manifest.set_config("a", "1");
|
||||
|
||||
ASSERT_NO_THROW(Plugin plugin = context.plugin(manifest));
|
||||
Plugin plugin = context.plugin(manifest);
|
||||
|
||||
Buffer buf = plugin.call("count_vowels", "this is a test");
|
||||
ASSERT_EQ((std::string)buf, "{\"count\": 4}");
|
||||
}
|
||||
|
||||
TEST(Plugin, BadManifest) {
|
||||
Context context;
|
||||
Manifest manifest;
|
||||
ASSERT_THROW(Plugin plugin = context.plugin(manifest), Error);
|
||||
}
|
||||
|
||||
TEST(Plugin, Bytes) {
|
||||
Context context;
|
||||
auto wasm = read("code.wasm");
|
||||
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
Buffer buf = plugin.call("count_vowels", "this is another test");
|
||||
ASSERT_EQ(buf.string(), "{\"count\": 6}");
|
||||
}
|
||||
|
||||
TEST(Plugin, UpdateConfig) {
|
||||
Context context;
|
||||
auto wasm = read("code.wasm");
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
Config config;
|
||||
config["abc"] = "123";
|
||||
ASSERT_NO_THROW(plugin.config(config));
|
||||
}
|
||||
|
||||
TEST(Plugin, FunctionExists) {
|
||||
Context context;
|
||||
auto wasm = read("code.wasm");
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
ASSERT_FALSE(plugin.function_exists("bad_function"));
|
||||
ASSERT_TRUE(plugin.function_exists("count_vowels"));
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -19,6 +19,6 @@
|
||||
(name extism)
|
||||
(synopsis "Extism bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64 ppx_inline_test)
|
||||
(depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64)
|
||||
(tags
|
||||
(topics wasm plugin)))
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
mix deps.get
|
||||
mix compile
|
||||
|
||||
test: prepare
|
||||
mix test
|
||||
|
||||
clean:
|
||||
mix clean
|
||||
|
||||
publish: clean prepare
|
||||
mix hex.build
|
||||
mix hex.publish --yes
|
||||
|
||||
format:
|
||||
mix format
|
||||
|
||||
lint:
|
||||
mix format --check-formatted
|
||||
|
||||
docs:
|
||||
mix docs
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
Extism Host SDK for Elixir and Erlang
|
||||
|
||||
## Docs
|
||||
|
||||
Read the [docs on hexdocs.pm](https://hexdocs.pm/extism/).
|
||||
|
||||
## Installation
|
||||
|
||||
You can find this package on [hex.pm](https://hex.pm/packages/extism).
|
||||
@@ -13,14 +9,12 @@ You can find this package on [hex.pm](https://hex.pm/packages/extism).
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:extism, "~> 0.0.1-rc.6"}
|
||||
{:extism, "~> 0.0.1-rc.5"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Example
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
# Create a context for which plugins can be allocated and cleaned
|
||||
@@ -42,32 +36,3 @@ manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
|
||||
# free up the context and any plugins we allocated
|
||||
Extism.Context.free(ctx)
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
The two primary modules you should learn are:
|
||||
|
||||
* [Extism.Context](Extism.Context.html)
|
||||
* [Extism.Plugin](Extism.Plugin.html)
|
||||
|
||||
#### Context
|
||||
|
||||
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. It's important to free up your context and plugins when you are done with them.
|
||||
|
||||
```elixir
|
||||
ctx = Extism.Context.new()
|
||||
# frees all the plugins
|
||||
Extism.Context.reset(ctx)
|
||||
# frees the context and all its plugins
|
||||
Extism.Context.free(ctx)
|
||||
```
|
||||
|
||||
#### Plugin
|
||||
|
||||
The [Plugin](Extism.Plugin.html) represents an instance of your WASM program from the given manifest.
|
||||
The key method to know here is [Extism.Plugin#call](Extism.Plugin.html#call/3) which takes a function name to invoke and some input data, and returns the results from the plugin.
|
||||
|
||||
```elixir
|
||||
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
```
|
||||
@@ -1,9 +1,4 @@
|
||||
defmodule Extism.Context do
|
||||
@moduledoc """
|
||||
A Context is needed to create plugins. The Context is where your plugins
|
||||
live. Freeing the context frees all of the plugins in its scope.
|
||||
"""
|
||||
|
||||
defstruct [
|
||||
# The actual NIF Resource. A pointer in this case
|
||||
ptr: nil
|
||||
@@ -15,47 +10,21 @@ defmodule Extism.Context do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new context.
|
||||
"""
|
||||
def new() do
|
||||
ptr = Extism.Native.context_new()
|
||||
Extism.Context.wrap_resource(ptr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resets the context. This has the effect of freeing all the plugins created so far.
|
||||
"""
|
||||
def reset(ctx) do
|
||||
Extism.Native.context_reset(ctx.ptr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Frees the context from memory and all of its plugins.
|
||||
"""
|
||||
def free(ctx) do
|
||||
Extism.Native.context_free(ctx.ptr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a new plugin from a WASM module or manifest
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> ctx = Extism.Context.new()
|
||||
iex> manifest = %{ wasm: [ %{ path: "/Users/ben/code/extism/wasm/code.wasm" } ]}
|
||||
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
|
||||
## Parameters
|
||||
|
||||
- ctx: The Context to manage this plugin
|
||||
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
|
||||
- wasi: A bool you set to true if you want WASI support
|
||||
|
||||
"""
|
||||
def new_plugin(ctx, manifest, wasi \\ false) do
|
||||
def new_plugin(ctx, manifest, wasi) do
|
||||
{:ok, manifest_payload} = JSON.encode(manifest)
|
||||
|
||||
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
|
||||
{:error, err} -> {:error, err}
|
||||
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
defmodule Extism.Native do
|
||||
@moduledoc """
|
||||
This module represents the Native Extism runtime API and is for internal use.
|
||||
Do not use or rely on this this module.
|
||||
"""
|
||||
use Rustler,
|
||||
otp_app: :extism,
|
||||
crate: :extism_nif
|
||||
otp_app: :extism,
|
||||
crate: :extism_nif
|
||||
|
||||
def context_new(), do: error()
|
||||
def context_reset(_ctx), do: error()
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
defmodule Extism.Plugin do
|
||||
@moduledoc """
|
||||
A Plugin represents an instance of your WASM program from the given manifest.
|
||||
"""
|
||||
defstruct [
|
||||
# The actual NIF Resource. PluginIndex and the context
|
||||
plugin_id: nil,
|
||||
@@ -15,26 +12,6 @@ defmodule Extism.Plugin do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Call a plugin's function by name
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
iex> {:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
# {:ok, "{\"count\": 4}"}
|
||||
|
||||
## Parameters
|
||||
|
||||
- plugin: The plugin
|
||||
- name: The name of the function as a string
|
||||
- input: The input data as a string
|
||||
|
||||
## Returns
|
||||
|
||||
A string representation of the functions output
|
||||
|
||||
"""
|
||||
def call(plugin, name, input) do
|
||||
case Extism.Native.plugin_call(plugin.ctx.ptr, plugin.plugin_id, name, input) do
|
||||
{:error, err} -> {:error, err}
|
||||
@@ -42,44 +19,21 @@ defmodule Extism.Plugin do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the manifest of the given plugin
|
||||
def update(plugin, manifest, wasi) when is_map(manifest) do
|
||||
{:ok, manifest_payload} = JSON.encode(manifest)
|
||||
case Extism.Native.plugin_update_manifest(plugin.ctx.ptr, plugin.plugin_id, manifest_payload, wasi) do
|
||||
{:error, err} -> {:error, err}
|
||||
res -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
## Parameters
|
||||
def free(plugin) do
|
||||
Extism.Native.plugin_free(plugin.ctx.ptr, plugin.plugin_id)
|
||||
end
|
||||
|
||||
- ctx: The Context to manage this plugin
|
||||
- manifest: The String or Map of the WASM module or [manifest](https://extism.org/docs/concepts/manifest)
|
||||
- wasi: A bool you set to true if you want WASI support
|
||||
|
||||
|
||||
"""
|
||||
def update(plugin, manifest, wasi) when is_map(manifest) do
|
||||
{:ok, manifest_payload} = JSON.encode(manifest)
|
||||
|
||||
case Extism.Native.plugin_update_manifest(
|
||||
plugin.ctx.ptr,
|
||||
plugin.plugin_id,
|
||||
manifest_payload,
|
||||
wasi
|
||||
) do
|
||||
{:error, err} -> {:error, err}
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Frees the plugin
|
||||
"""
|
||||
def free(plugin) do
|
||||
Extism.Native.plugin_free(plugin.ctx.ptr, plugin.plugin_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given plugin responds to the given function name
|
||||
"""
|
||||
def has_function(plugin, function_name) do
|
||||
Extism.Native.plugin_has_function(plugin.ctx.ptr, plugin.plugin_id, function_name)
|
||||
end
|
||||
def has_function(plugin, function_name) do
|
||||
Extism.Native.plugin_has_function(plugin.ctx.ptr, plugin.plugin_id, function_name)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Extim.Plugin do
|
||||
|
||||
BIN
elixir/logo.png
BIN
elixir/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -4,13 +4,12 @@ defmodule Extism.MixProject do
|
||||
def project do
|
||||
[
|
||||
app: :extism,
|
||||
version: "0.0.1",
|
||||
version: "0.0.1-rc.5",
|
||||
elixir: "~> 1.14",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
package: package(),
|
||||
aliases: aliases(),
|
||||
docs: docs()
|
||||
aliases: aliases()
|
||||
]
|
||||
end
|
||||
|
||||
@@ -25,7 +24,7 @@ defmodule Extism.MixProject do
|
||||
[
|
||||
{:rustler, "~> 0.26.0"},
|
||||
{:json, "~> 1.4"},
|
||||
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
|
||||
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
|
||||
]
|
||||
end
|
||||
|
||||
@@ -44,16 +43,7 @@ defmodule Extism.MixProject do
|
||||
description: "Extism Host SDK for Elixir and Erlang",
|
||||
name: "extism",
|
||||
files: ~w(lib native priv .formatter.exs mix.exs README.md LICENSE),
|
||||
links: %{"GitHub" => "https://github.com/extism/extism"}
|
||||
]
|
||||
end
|
||||
|
||||
defp docs do
|
||||
[
|
||||
main: "Extism",
|
||||
logo: "./logo.png",
|
||||
main: "readme",
|
||||
extras: ["README.md"]
|
||||
links: %{ "GitHub" => "https://github.com/extism/extism" },
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%{
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"},
|
||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||
"json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "extism_nif"
|
||||
version = "0.0.1-rc.6"
|
||||
version = "0.0.1-rc.5"
|
||||
edition = "2021"
|
||||
authors = ["Benjamin Eckel <bhelx@simst.im>"]
|
||||
|
||||
@@ -11,5 +11,5 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
rustler = "0.26.0"
|
||||
extism = { version = "0.0.1-rc.6" }
|
||||
extism = { version = "0.0.1-rc.5" }
|
||||
log = "0.4"
|
||||
|
||||
@@ -115,11 +115,8 @@ fn set_log_file(filename: String, log_level: String) -> Result<Atom, rustler::Er
|
||||
match log::Level::from_str(&log_level) {
|
||||
Err(_e) => Err(rustler::Error::Term(Box::new(format!("{} not a valid log level", log_level)))),
|
||||
Ok(level) => {
|
||||
if extism::set_log_file(path, Some(level)) {
|
||||
Ok(atoms::ok())
|
||||
} else {
|
||||
Err(rustler::Error::Term(Box::new("Did not set log file, received false from the API.")))
|
||||
}
|
||||
extism::set_log_file(path, Some(level));
|
||||
Ok(atoms::ok())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ defmodule ExtismTest do
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 7}}
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test thrice")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 6}}
|
||||
{:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "🌎hello🌎world🌎")
|
||||
assert JSON.decode(output) == {:ok, %{"count" => 3}}
|
||||
Extism.Context.free(ctx)
|
||||
end
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ func (plugin Plugin) SetConfig(data map[string][]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FunctionExists returns true when the named function is present in the plugin
|
||||
/// FunctionExists returns true when the name function is present in the plugin
|
||||
func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
name := C.CString(functionName)
|
||||
b := C.extism_plugin_function_exists(plugin.ctx.pointer, C.int(plugin.id), name)
|
||||
@@ -202,7 +202,7 @@ func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
return bool(b)
|
||||
}
|
||||
|
||||
// Call a function by name with the given input, returning the output
|
||||
/// Call a function by name with the given input, returning the output
|
||||
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
ptr := makePointer(input)
|
||||
name := C.CString(functionName)
|
||||
|
||||
@@ -16,7 +16,6 @@ depends: [
|
||||
"bigstringaf"
|
||||
"ppx_yojson_conv"
|
||||
"base64"
|
||||
"ppx_inline_test"
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
|
||||
148
extism_test.go
148
extism_test.go
@@ -1,148 +0,0 @@
|
||||
package extism
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func manifest() Manifest {
|
||||
return Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: "./wasm/code.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectVowelCount(plugin Plugin, input string, count int) error {
|
||||
out, err := plugin.Call("count_vowels", []byte(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result map[string]int
|
||||
json.Unmarshal(out, &result)
|
||||
if result["count"] != count {
|
||||
return fmt.Errorf("Got count %d but expected %d", result["count"], count)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateAndFreeContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
ctx.Free()
|
||||
}
|
||||
|
||||
func TestCallPlugin(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test again", 7); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test thrice", 6); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreePlugin(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// free this specific plugin
|
||||
plugin.Free()
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
|
||||
t.Fatal("Expected an error after plugin was freed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextReset(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// reset the context dropping all plugins
|
||||
ctx.Reset()
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err == nil {
|
||||
t.Fatal("Expected an error after plugin was freed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanUpdateAManifest(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
plugin.UpdateManifest(manifest(), false)
|
||||
|
||||
// can still call the plugin
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionExists(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !plugin.FunctionExists("count_vowels") {
|
||||
t.Fatal("Was expecting to find the function count_vowels")
|
||||
}
|
||||
if plugin.FunctionExists("i_dont_exist") {
|
||||
t.Fatal("Was not expecting to find the function i_dont_exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = plugin.Call("i_dont_exist", []byte("someinput"))
|
||||
if err == nil {
|
||||
t.Fatal("Was expecting call to unknown function to fail")
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import Extism
|
||||
import Extism.Manifest
|
||||
|
||||
try f (Right x) = f x
|
||||
try f (Left (ErrorMessage msg)) = do
|
||||
try f (Left (Error msg)) = do
|
||||
_ <- putStrLn msg
|
||||
exitFailure
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cabal-version: 2.4
|
||||
name: extism
|
||||
version: 0.0.1
|
||||
version: 0.0.1.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
synopsis: Extism bindings
|
||||
@@ -19,11 +19,11 @@ maintainer: oss@extism.org
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Plugins, WebAssembly
|
||||
category: Plugins
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism Extism.Manifest
|
||||
exposed-modules: Extism Extism.Manifest
|
||||
|
||||
-- Modules included in this library but not exported.
|
||||
other-modules:
|
||||
@@ -45,10 +45,3 @@ Test-Suite extism-example
|
||||
main-is: Example.hs
|
||||
build-depends: base, extism, bytestring
|
||||
default-language: Haskell2010
|
||||
|
||||
Test-Suite extism-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Test.hs
|
||||
hs-source-dirs: test
|
||||
build-depends: base, extism, bytestring, HUnit
|
||||
default-language: Haskell2010
|
||||
|
||||
@@ -11,8 +11,7 @@ import Control.Monad (void)
|
||||
import Data.ByteString as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import Data.ByteString.Unsafe (unsafeUseAsCString)
|
||||
import Data.Bifunctor (second)
|
||||
import Text.JSON (JSON, toJSObject, toJSString, encode, JSValue(JSNull, JSString))
|
||||
import Text.JSON (JSON, toJSObject, encode)
|
||||
import Extism.Manifest (Manifest, toString)
|
||||
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
@@ -38,11 +37,8 @@ newtype Context = Context (ForeignPtr ExtismContext)
|
||||
-- Plugins can be used to call WASM function
|
||||
data Plugin = Plugin Context Int32
|
||||
|
||||
-- Log level
|
||||
data LogLevel = Error | Warn | Info | Debug | Trace deriving (Show)
|
||||
|
||||
-- Extism error
|
||||
newtype Error = ErrorMessage String deriving Show
|
||||
newtype Error = Error String deriving Show
|
||||
|
||||
-- Helper function to convert a string to a bytestring
|
||||
toByteString :: String -> ByteString
|
||||
@@ -61,7 +57,8 @@ extismVersion () = do
|
||||
-- Remove all registered plugins in a Context
|
||||
reset :: Context -> IO ()
|
||||
reset (Context ctx) =
|
||||
withForeignPtr ctx extism_context_reset
|
||||
withForeignPtr ctx (\ctx ->
|
||||
extism_context_reset ctx)
|
||||
|
||||
-- Create a new context
|
||||
newContext :: () -> IO Context
|
||||
@@ -69,12 +66,6 @@ newContext () = do
|
||||
ptr <- extism_context_new
|
||||
fptr <- newForeignPtr extism_context_free ptr
|
||||
return (Context fptr)
|
||||
|
||||
-- Execute a function with a new context that is destroyed when it returns
|
||||
withContext :: (Context -> IO a) -> IO a
|
||||
withContext f = do
|
||||
ctx <- newContext ()
|
||||
f ctx
|
||||
|
||||
-- Create a plugin from a WASM module, `useWasi` determines if WASI should
|
||||
-- be linked
|
||||
@@ -90,7 +81,7 @@ plugin c wasm useWasi =
|
||||
if p < 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ErrorMessage e)
|
||||
return $ Left (Error e)
|
||||
else
|
||||
return $ Right (Plugin c p))
|
||||
|
||||
@@ -112,7 +103,7 @@ update (Plugin (Context ctx) id) wasm useWasi =
|
||||
if b <= 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ErrorMessage e)
|
||||
return $ Left (Error e)
|
||||
else
|
||||
return (Right ()))
|
||||
|
||||
@@ -126,37 +117,25 @@ updateManifest plugin manifest useWasi =
|
||||
isValid :: Plugin -> Bool
|
||||
isValid (Plugin _ p) = p >= 0
|
||||
|
||||
convertMaybeString Nothing = JSNull
|
||||
convertMaybeString (Just s) = JSString (toJSString s)
|
||||
|
||||
-- Set configuration values for a plugin
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO ()
|
||||
setConfig (Plugin (Context ctx) plugin) x =
|
||||
if plugin < 0
|
||||
then return False
|
||||
then return ()
|
||||
else
|
||||
let obj = toJSObject [(k, convertMaybeString v) | (k, v) <- x] in
|
||||
let obj = toJSObject x in
|
||||
let bs = toByteString (encode obj) in
|
||||
let length = fromIntegral (B.length bs) in
|
||||
unsafeUseAsCString bs (\s -> do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- extism_plugin_config ctx plugin (castPtr s) length
|
||||
return $ b /= 0))
|
||||
|
||||
levelStr Error = "error"
|
||||
levelStr Debug = "debug"
|
||||
levelStr Warn = "warn"
|
||||
levelStr Trace = "trace"
|
||||
levelStr Info = "info"
|
||||
withForeignPtr ctx (\ctx ->
|
||||
void $ extism_plugin_config ctx plugin (castPtr s) length))
|
||||
|
||||
-- Set the log file and level, this is a global configuration
|
||||
setLogFile :: String -> LogLevel -> IO Bool
|
||||
setLogFile :: String -> String -> IO ()
|
||||
setLogFile filename level =
|
||||
let s = levelStr level in
|
||||
withCString filename (\f ->
|
||||
withCString s (\l -> do
|
||||
b <- extism_log_file f l
|
||||
return $ b /= 0))
|
||||
withCString level (\l -> do
|
||||
void $ extism_log_file f l))
|
||||
|
||||
-- Check if a function exists in the given plugin
|
||||
functionExists :: Plugin -> String -> IO Bool
|
||||
@@ -177,16 +156,17 @@ call (Plugin (Context ctx) plugin) name input =
|
||||
err <- extism_error ctx plugin
|
||||
if err /= nullPtr
|
||||
then do e <- peekCString err
|
||||
return $ Left (ErrorMessage e)
|
||||
return $ Left (Error e)
|
||||
else if rc == 0
|
||||
then do
|
||||
length <- extism_plugin_output_length ctx plugin
|
||||
ptr <- extism_plugin_output_data ctx plugin
|
||||
buf <- packCStringLen (castPtr ptr, fromIntegral length)
|
||||
return $ Right buf
|
||||
else return $ Left (ErrorMessage "Call failed"))
|
||||
else return $ Left (Error "Call failed"))
|
||||
|
||||
-- Free a plugin
|
||||
free :: Plugin -> IO ()
|
||||
free (Plugin (Context ctx) plugin) =
|
||||
withForeignPtr ctx (`extism_plugin_free` plugin)
|
||||
withForeignPtr ctx (\ctx ->
|
||||
extism_plugin_free ctx plugin)
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import Test.HUnit
|
||||
import Extism
|
||||
import Extism.Manifest
|
||||
|
||||
|
||||
unwrap' (Right x) = return x
|
||||
unwrap' (Left (ErrorMessage msg)) =
|
||||
assertFailure msg
|
||||
|
||||
unwrap io = do
|
||||
x <- io
|
||||
unwrap' x
|
||||
|
||||
defaultManifest = manifest [wasmFile "test/code.wasm"]
|
||||
|
||||
initPlugin context =
|
||||
unwrap (Extism.pluginFromManifest context defaultManifest False)
|
||||
|
||||
pluginFunctionExists = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
exists <- functionExists p "count_vowels"
|
||||
assertBool "function exists" exists
|
||||
exists' <- functionExists p "function_doesnt_exist"
|
||||
assertBool "function doesn't exist" (not exists'))
|
||||
|
||||
checkCallResult p = do
|
||||
res <- unwrap (call p "count_vowels" (toByteString "this is a test"))
|
||||
assertEqual "count vowels output" "{\"count\": 4}" (fromByteString res)
|
||||
|
||||
pluginCall = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
checkCallResult p)
|
||||
|
||||
pluginMultiple = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
checkCallResult p
|
||||
q <- initPlugin ctx
|
||||
r <- initPlugin ctx
|
||||
checkCallResult q
|
||||
checkCallResult r)
|
||||
|
||||
pluginUpdate = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
unwrap (updateManifest p defaultManifest True)
|
||||
checkCallResult p)
|
||||
|
||||
pluginConfig = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
b <- setConfig p [("a", Just "1"), ("b", Just "2"), ("c", Just "3"), ("d", Nothing)]
|
||||
assertBool "set config" b)
|
||||
|
||||
testSetLogFile = do
|
||||
b <- setLogFile "stderr" Error
|
||||
assertBool "set log file" b
|
||||
|
||||
t name f = TestLabel name (TestCase f)
|
||||
|
||||
main = do
|
||||
runTestTT (TestList
|
||||
[
|
||||
t "Plugin.FunctionExists" pluginFunctionExists
|
||||
, t "Plugin.Call" pluginCall
|
||||
, t "Plugin.Multiple" pluginMultiple
|
||||
, t "Plugin.Update" pluginUpdate
|
||||
, t "Plugin.Config" pluginConfig
|
||||
, t "SetLogFile" testSetLogFile
|
||||
])
|
||||
|
||||
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "libextism"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
authors = ["The Extism Authors", "oss@extism.org"]
|
||||
license = "BSD-3-Clause"
|
||||
homepage = "https://extism.org"
|
||||
repository = "https://github.com/extism/extism"
|
||||
description = "libextism"
|
||||
|
||||
[lib]
|
||||
name = "extism"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
extism-runtime = {path = "../runtime"}
|
||||
|
||||
[features]
|
||||
default = ["http", "register-http", "register-filesystem"]
|
||||
nn = ["extism-runtime/nn"]
|
||||
register-http = ["extism-runtime/register-http"] # enables wasm to be downloaded using http
|
||||
register-filesystem = ["extism-runtime/register-filesystem"] # enables wasm to be loaded from disk
|
||||
http = ["extism-runtime/http"] # enables extism_http_request
|
||||
@@ -1,10 +0,0 @@
|
||||
//! This crate is used to generate `libextism` using `extism-runtime`
|
||||
|
||||
pub use extism_runtime::sdk::*;
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_version() {
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(extism_version()) };
|
||||
assert!(s.to_bytes() != b"0.0.0");
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "extism-manifest"
|
||||
version = "0.0.1"
|
||||
version = "0.0.1-rc.5"
|
||||
edition = "2021"
|
||||
authors = ["The Extism Authors", "oss@extism.org"]
|
||||
license = "BSD-3-Clause"
|
||||
@@ -9,7 +9,7 @@ repository = "https://github.com/extism/extism"
|
||||
description = "Extism plug-in manifest crate"
|
||||
|
||||
[dependencies]
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde = {version = "1", features=["derive"]}
|
||||
base64 = "0.20.0-alpha"
|
||||
schemars = {version = "0.8", optional=true}
|
||||
|
||||
|
||||
2
node/.gitignore
vendored
2
node/.gitignore
vendored
@@ -1,3 +1 @@
|
||||
dist/
|
||||
coverage/
|
||||
doc/
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
coverage
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,26 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
npm install
|
||||
|
||||
test: prepare
|
||||
npm run example
|
||||
npm run test
|
||||
|
||||
clean:
|
||||
echo "No clean implemented"
|
||||
|
||||
publish: clean prepare
|
||||
npm publish
|
||||
|
||||
format:
|
||||
npx prettier --write .
|
||||
|
||||
lint:
|
||||
npx prettier --check .
|
||||
|
||||
docs:
|
||||
npx typedoc --out doc src
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
22
node/example.js
Normal file
22
node/example.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { withContext, Context } from './dist/index.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
withContext(async function (context) {
|
||||
let wasm = readFileSync('../wasm/code.wasm');
|
||||
let p = context.plugin(wasm);
|
||||
|
||||
if (!p.functionExists('count_vowels')) {
|
||||
console.log("no function 'count_vowels' in wasm");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let buf = await p.call('count_vowels', process.argv[2] || 'this is a test');
|
||||
console.log(JSON.parse(buf.toString())['count']);
|
||||
p.free();
|
||||
});
|
||||
|
||||
// or, use a context like this:
|
||||
let ctx = new Context();
|
||||
let wasm = readFileSync('../wasm/code.wasm');
|
||||
let p = ctx.plugin(wasm);
|
||||
// ... where the context can be passed around to various functions etc.
|
||||
@@ -1,22 +0,0 @@
|
||||
import { withContext, Context } from "./dist/index.js";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
withContext(async function (context) {
|
||||
let wasm = readFileSync("../wasm/code.wasm");
|
||||
let p = context.plugin(wasm);
|
||||
|
||||
if (!p.functionExists("count_vowels")) {
|
||||
console.log("no function 'count_vowels' in wasm");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let buf = await p.call("count_vowels", process.argv[2] || "this is a test");
|
||||
console.log(JSON.parse(buf.toString())["count"]);
|
||||
p.free();
|
||||
});
|
||||
|
||||
// or, use a context like this:
|
||||
let ctx = new Context();
|
||||
let wasm = readFileSync("../wasm/code.wasm");
|
||||
let p = ctx.plugin(wasm);
|
||||
// ... where the context can be passed around to various functions etc.
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
};
|
||||
6520
node/package-lock.json
generated
6520
node/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@extism/extism",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.1-rc.5",
|
||||
"description": "Extism Host SDK for Node",
|
||||
"keywords": [
|
||||
"extism",
|
||||
@@ -14,16 +14,16 @@
|
||||
"private": false,
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"homepage": "https://extism.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/extism/extism.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"example": "node example.mjs",
|
||||
"build": "tsc",
|
||||
"test": "jest --coverage"
|
||||
"prepare" : "npm run build",
|
||||
"example": "node example.js",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"ffi-napi": "^4.0.3"
|
||||
@@ -33,13 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ffi-napi": "^4.0.6",
|
||||
"@types/jest": "^29.2.0",
|
||||
"@types/node": "^18.11.4",
|
||||
"jest": "^29.2.2",
|
||||
"prettier": "2.8.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.23.18",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,434 +1,253 @@
|
||||
import ffi from "ffi-napi";
|
||||
import path from "path";
|
||||
import ffi from 'ffi-napi';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
|
||||
const context = "void*";
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
const context = 'void*';
|
||||
const _functions = {
|
||||
extism_context_new: [context, []],
|
||||
extism_context_free: ["void", [context]],
|
||||
extism_plugin_new: ["int32", [context, "string", "uint64", "bool"]],
|
||||
extism_plugin_update: [
|
||||
"bool",
|
||||
[context, "int32", "string", "uint64", "bool"],
|
||||
],
|
||||
extism_error: ["char*", [context, "int32"]],
|
||||
extism_plugin_call: [
|
||||
"int32",
|
||||
[context, "int32", "string", "string", "uint64"],
|
||||
],
|
||||
extism_plugin_output_length: ["uint64", [context, "int32"]],
|
||||
extism_plugin_output_data: ["uint8*", [context, "int32"]],
|
||||
extism_log_file: ["bool", ["string", "char*"]],
|
||||
extism_plugin_function_exists: ["bool", [context, "int32", "string"]],
|
||||
extism_plugin_config: ["void", [context, "int32", "char*", "uint64"]],
|
||||
extism_plugin_free: ["void", [context, "int32"]],
|
||||
extism_context_reset: ["void", [context]],
|
||||
extism_version: ["char*", []],
|
||||
extism_context_new: [context, []],
|
||||
extism_context_free: ['void', [context]],
|
||||
extism_plugin_new: ['int32', [context, 'string', 'uint64', 'bool']],
|
||||
extism_plugin_update: ['bool', [context, 'int32', 'string', 'uint64', 'bool']],
|
||||
extism_error: ['char*', [context, 'int32']],
|
||||
extism_plugin_call: ['int32', [context, 'int32', 'string', 'string', 'uint64']],
|
||||
extism_plugin_output_length: ['uint64', [context, 'int32']],
|
||||
extism_plugin_output_data: ['uint8*', [context, 'int32']],
|
||||
extism_log_file: ['bool', ['string', 'char*']],
|
||||
extism_plugin_function_exists: ['bool', [context, 'int32', 'string']],
|
||||
extism_plugin_config: ['void', [context, 'int32', 'char*', 'uint64']],
|
||||
extism_plugin_free: ['void', [context, 'int32']],
|
||||
extism_context_reset: ['void', [context]],
|
||||
extism_version: ['char*', []],
|
||||
};
|
||||
|
||||
interface LibExtism {
|
||||
extism_context_new: () => Buffer;
|
||||
extism_context_free: (ctx: Buffer) => void;
|
||||
extism_plugin_new: (
|
||||
ctx: Buffer,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
wasi: boolean
|
||||
) => number;
|
||||
extism_plugin_update: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
wasi: boolean
|
||||
) => boolean;
|
||||
extism_error: (ctx: Buffer, plugin_id: number) => Buffer;
|
||||
extism_plugin_call: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
func: string,
|
||||
input: string,
|
||||
input_len: number
|
||||
) => number;
|
||||
extism_plugin_output_length: (ctx: Buffer, plugin_id: number) => number;
|
||||
extism_plugin_output_data: (ctx: Buffer, plugin_id: Number) => Uint8Array;
|
||||
extism_log_file: (file: string, level: string) => boolean;
|
||||
extism_plugin_function_exists: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
func: string
|
||||
) => boolean;
|
||||
extism_plugin_config: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
data: string | Buffer,
|
||||
data_len: number
|
||||
) => void;
|
||||
extism_plugin_free: (ctx: Buffer, plugin_id: number) => void;
|
||||
extism_context_reset: (ctx: Buffer) => void;
|
||||
extism_version: () => Buffer;
|
||||
extism_context_new: () => Buffer;
|
||||
extism_context_free: (ctx: Buffer) => void;
|
||||
extism_plugin_new: (ctx: Buffer, data: string | Buffer, data_len: number, wasi: boolean) => number;
|
||||
extism_plugin_update: (ctx: Buffer, plugin_id: number, data: string | Buffer, data_len: number, wasi: boolean) => boolean;
|
||||
extism_error: (ctx: Buffer, plugin_id: number) => string;
|
||||
extism_plugin_call: (ctx: Buffer, plugin_id: number, func: string, input: string, input_len: number) => number;
|
||||
extism_plugin_output_length: (ctx: Buffer, plugin_id: number) => number;
|
||||
extism_plugin_output_data: (ctx: Buffer, plugin_id: Number) => Uint8Array;
|
||||
extism_log_file: (file: string, level: string) => boolean;
|
||||
extism_plugin_function_exists: (ctx: Buffer, plugin_id: number, func: string) => boolean;
|
||||
extism_plugin_config: (ctx: Buffer, plugin_id: number, data: string | Buffer, data_len: number) => void;
|
||||
extism_plugin_free: (ctx: Buffer, plugin_id: number) => void;
|
||||
extism_context_reset: (ctx: Buffer) => void;
|
||||
extism_version: () => string;
|
||||
}
|
||||
|
||||
function locate(paths: string[]): LibExtism {
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return ffi.Library(path.join(paths[i], "libextism"), _functions);
|
||||
} catch (exn) {
|
||||
continue;
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return ffi.Library(path.join(paths[i], 'libextism'), _functions);
|
||||
} catch (exn) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw "Unable to locate libextism";
|
||||
throw 'Unable to locate libextism';
|
||||
}
|
||||
|
||||
const searchPath = [
|
||||
__dirname,
|
||||
"/usr/local/lib",
|
||||
"/usr/lib",
|
||||
path.join(process.env.HOME as string, ".local", "lib"),
|
||||
__dirname,
|
||||
'/usr/local/lib',
|
||||
'/usr/lib',
|
||||
path.join(process.env.HOME as string, '.local', 'lib'),
|
||||
];
|
||||
|
||||
if (process.env.EXTISM_PATH) {
|
||||
searchPath.unshift(path.join(process.env.EXTISM_PATH, "lib"));
|
||||
searchPath.unshift(path.join(process.env.EXTISM_PATH, 'lib'));
|
||||
}
|
||||
|
||||
const lib = locate(searchPath);
|
||||
|
||||
/**
|
||||
* Sets the logfile and level of the Extism runtime
|
||||
*
|
||||
* @param filename - The path to the logfile
|
||||
* @param level - The level, one of ('debug', 'error', 'info', 'trace')
|
||||
*/
|
||||
// Set the log file and level
|
||||
export function setLogFile(filename: string, level?: string) {
|
||||
lib.extism_log_file(filename, level || "info");
|
||||
lib.extism_log_file(filename, level || "info");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of Extism
|
||||
*
|
||||
* @returns The version string of the Extism runtime
|
||||
*/
|
||||
// Get the version of Extism
|
||||
export function extismVersion(): string {
|
||||
return lib.extism_version().toString();
|
||||
return lib.extism_version().toString();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const pluginRegistry = new FinalizationRegistry(({ id, pointer }) => {
|
||||
if (id && pointer) lib.extism_plugin_free(pointer, id);
|
||||
lib.extism_plugin_free(pointer, id);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const contextRegistry = new FinalizationRegistry((pointer) => {
|
||||
if (pointer) lib.extism_context_free(pointer);
|
||||
lib.extism_context_free(pointer);
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a path or url to a WASM module
|
||||
*/
|
||||
export type ManifestWasmFile = {
|
||||
path: string;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
path: string;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the raw bytes of a WASM file loaded into memory
|
||||
*/
|
||||
export type ManifestWasmData = {
|
||||
data: Uint8Array;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
};
|
||||
data: Uint8Array;
|
||||
name?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory options for the {@link Plugin}
|
||||
*/
|
||||
export type ManifestMemory = {
|
||||
max?: number;
|
||||
};
|
||||
max?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Plugin} Config
|
||||
*/
|
||||
export type PluginConfig = Map<string, string>;
|
||||
|
||||
/**
|
||||
* The WASM to load as bytes or a path
|
||||
*/
|
||||
export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
|
||||
|
||||
/**
|
||||
* The manifest which describes the {@link Plugin} code and
|
||||
* runtime constraints.
|
||||
*
|
||||
* @see [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest)
|
||||
*/
|
||||
export type Manifest = {
|
||||
wasm: Array<ManifestWasm>;
|
||||
memory?: ManifestMemory;
|
||||
config?: PluginConfig;
|
||||
allowed_hosts?: Array<string>;
|
||||
};
|
||||
wasm: Array<ManifestWasm>;
|
||||
memory: ManifestMemory;
|
||||
config?: PluginConfig;
|
||||
allowed_hosts?: Array<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be a {@link Manifest} or just the raw bytes of the WASM module.
|
||||
* We recommend using {@link Manifest}
|
||||
*/
|
||||
type ManifestData = Manifest | Buffer | string;
|
||||
|
||||
/**
|
||||
* A Context is needed to create plugins. The Context
|
||||
* is where your plugins live. Freeing the context
|
||||
* frees all of the plugins in its scope. We recommand managing
|
||||
* the context with {@link withContext}
|
||||
*
|
||||
* @see {@link withContext}
|
||||
*
|
||||
* @example
|
||||
* Use withContext to ensure your memory is cleaned up
|
||||
* ```
|
||||
* const output = await withContext(async (ctx) => {
|
||||
* const plugin = ctx.plugin(manifest)
|
||||
* return await plugin.call("func", "my-input")
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* You can manage manually if you need a long-lived context
|
||||
* ```
|
||||
* const ctx = Context()
|
||||
* // free all the plugins and reset
|
||||
* ctx.reset()
|
||||
* // free everything
|
||||
* ctx.free()
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Context manages plugins
|
||||
export class Context {
|
||||
pointer: Buffer | null;
|
||||
pointer: Buffer | null;
|
||||
|
||||
/**
|
||||
* Construct a context
|
||||
*/
|
||||
constructor() {
|
||||
this.pointer = lib.extism_context_new();
|
||||
contextRegistry.register(this, this.pointer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plugin managed by this context
|
||||
*
|
||||
* @param manifest - The {@link Manifest} describing the plugin code and config
|
||||
* @param wasi - Set to `true` to enable WASI
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
plugin(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
return new Plugin(this, manifest, wasi, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the context. Should be called after the context is not needed to reclaim the memory.
|
||||
*/
|
||||
free() {
|
||||
if (this.pointer) {
|
||||
contextRegistry.unregister(this);
|
||||
lib.extism_context_free(this.pointer);
|
||||
}
|
||||
this.pointer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the context. This clears all the plugins but keeps the context alive.
|
||||
*/
|
||||
reset() {
|
||||
if (this.pointer) lib.extism_context_reset(this.pointer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a context and gives you a scope to use it. This will ensure the context
|
||||
* and all its plugins are cleaned up for you when you are done.
|
||||
*
|
||||
* @param f - The callback function with the context
|
||||
* @returns Whatever your callback returns
|
||||
*/
|
||||
export async function withContext(f: (ctx: Context) => Promise<any>) {
|
||||
const ctx = new Context();
|
||||
|
||||
try {
|
||||
const x = await f(ctx);
|
||||
ctx.free();
|
||||
return x;
|
||||
} catch (err) {
|
||||
ctx.free();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Plugin represents an instance of your WASM program from the given manifest.
|
||||
*/
|
||||
export class Plugin {
|
||||
id: number;
|
||||
ctx: Context;
|
||||
|
||||
/**
|
||||
* Constructor for a plugin. @see {@link Context#plugin}.
|
||||
*
|
||||
* @param ctx - The context to manage this plugin
|
||||
* @param manifest - The {@link Manifest}
|
||||
* @param wasi - Set to true to enable WASI support
|
||||
* @param config - The plugin config
|
||||
*/
|
||||
constructor(
|
||||
ctx: Context,
|
||||
manifest: ManifestData,
|
||||
wasi: boolean = false,
|
||||
config?: PluginConfig
|
||||
) {
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(manifest) || typeof manifest === "string") {
|
||||
dataRaw = manifest;
|
||||
} else if (typeof manifest === "object" && manifest.wasm) {
|
||||
dataRaw = JSON.stringify(manifest);
|
||||
} else {
|
||||
throw Error(`Unknown manifest type ${typeof manifest}`);
|
||||
}
|
||||
if (!ctx.pointer) throw Error("No Context set");
|
||||
let plugin = lib.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
dataRaw,
|
||||
Buffer.byteLength(dataRaw, 'utf-8'),
|
||||
wasi
|
||||
);
|
||||
if (plugin < 0) {
|
||||
var err = lib.extism_error(ctx.pointer, -1);
|
||||
if (err.length === 0) {
|
||||
throw "extism_context_plugin failed";
|
||||
}
|
||||
throw `Unable to load plugin: ${err.toString()}`;
|
||||
}
|
||||
this.id = plugin;
|
||||
this.ctx = ctx;
|
||||
pluginRegistry.register(
|
||||
this,
|
||||
{ id: this.id, pointer: this.ctx.pointer },
|
||||
this
|
||||
);
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(ctx.pointer, this.id, s, Buffer.byteLength(s, 'utf-8'),);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing plugin with new WASM or manifest
|
||||
*
|
||||
* @param manifest - The new {@link Manifest} data
|
||||
* @param wasi - Set to true to enable WASI support
|
||||
* @param config - The new plugin config
|
||||
*/
|
||||
update(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(manifest) || typeof manifest === "string") {
|
||||
dataRaw = manifest;
|
||||
} else if (typeof manifest === "object" && manifest.wasm) {
|
||||
dataRaw = JSON.stringify(manifest);
|
||||
} else {
|
||||
throw Error("Unknown manifest type type");
|
||||
}
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
const ok = lib.extism_plugin_update(
|
||||
this.ctx.pointer,
|
||||
this.id,
|
||||
dataRaw,
|
||||
Buffer.byteLength(dataRaw, 'utf-8'),
|
||||
wasi
|
||||
);
|
||||
if (!ok) {
|
||||
var err = lib.extism_error(this.ctx.pointer, -1);
|
||||
if (err.length === 0) {
|
||||
throw "extism_plugin_update failed";
|
||||
}
|
||||
throw `Unable to update plugin: ${err.toString()}`;
|
||||
constructor() {
|
||||
this.pointer = lib.extism_context_new();
|
||||
contextRegistry.register(this, this.pointer, this);
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(this.ctx.pointer, this.id, s, Buffer.byteLength(s, 'utf-8'),);
|
||||
// Create a new plugin, optionally enabling WASI
|
||||
plugin(data: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
return new Plugin(this, data, wasi, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a function exists by name
|
||||
*
|
||||
* @param functionName - The name of the function
|
||||
* @returns true if the function exists, false if not
|
||||
*/
|
||||
|
||||
functionExists(functionName: string) {
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
return lib.extism_plugin_function_exists(
|
||||
this.ctx.pointer,
|
||||
this.id,
|
||||
functionName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a plugin function with given name and input.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* const manifest = { wasm: [{ path: "/tmp/code.wasm" }] }
|
||||
* const plugin = ctx.plugin(manifest)
|
||||
* const output = await plugin.call("my_function", "some-input")
|
||||
* output.toString()
|
||||
* // => "output from the function"
|
||||
* ```
|
||||
*
|
||||
* @param functionName - The name of the function
|
||||
* @param input - The input data
|
||||
* @returns A Buffer repreesentation of the output
|
||||
*/
|
||||
async call(functionName: string, input: string | Buffer): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
var rc = lib.extism_plugin_call(
|
||||
this.ctx.pointer,
|
||||
this.id,
|
||||
functionName,
|
||||
input.toString(),
|
||||
Buffer.byteLength(input, 'utf-8'),
|
||||
);
|
||||
if (rc !== 0) {
|
||||
var err = lib.extism_error(this.ctx.pointer, this.id);
|
||||
if (err.length === 0) {
|
||||
reject(`extism_plugin_call: "${functionName}" failed`);
|
||||
// Free a context, this should be called when it is
|
||||
// no longer needed
|
||||
free() {
|
||||
if (this.pointer) {
|
||||
contextRegistry.unregister(this);
|
||||
lib.extism_context_free(this.pointer);
|
||||
}
|
||||
this.pointer = null;
|
||||
}
|
||||
|
||||
// Remove all registered plugins
|
||||
reset() {
|
||||
if (this.pointer)
|
||||
lib.extism_context_reset(this.pointer);
|
||||
}
|
||||
}
|
||||
|
||||
export async function withContext(f: (ctx: Context) => Promise<any>) {
|
||||
let ctx = new Context();
|
||||
|
||||
try {
|
||||
let x = await f(ctx);
|
||||
ctx.free();
|
||||
return x;
|
||||
} catch (err) {
|
||||
ctx.free();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin provides an interface for calling WASM functions
|
||||
export class Plugin {
|
||||
id: number;
|
||||
ctx: Context;
|
||||
|
||||
constructor(ctx: Context, data: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(data) || typeof data === 'string') {
|
||||
dataRaw = data
|
||||
} else if (typeof data === 'object' && data.wasm) {
|
||||
dataRaw = JSON.stringify(data)
|
||||
} else {
|
||||
throw Error(`Unknown manifest type ${typeof data}`);
|
||||
}
|
||||
if (!ctx.pointer) throw Error("No Context set");
|
||||
let plugin = lib.extism_plugin_new(ctx.pointer, dataRaw, dataRaw.length, wasi);
|
||||
if (plugin < 0) {
|
||||
var err = lib.extism_error(ctx.pointer, -1);
|
||||
if (err.length === 0) {
|
||||
throw "extism_context_plugin failed";
|
||||
}
|
||||
throw `Unable to load plugin: ${err.toString()}`;
|
||||
}
|
||||
this.id = plugin;
|
||||
this.ctx = ctx;
|
||||
pluginRegistry.register(this, { id: this.id, pointer: this.ctx.pointer }, this);
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(ctx.pointer, this.id, s, s.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Update an existing plugin with new WASM or manifest
|
||||
update(data: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(data) || typeof data === 'string') {
|
||||
dataRaw = data
|
||||
} else if (typeof data === 'object' && data.wasm) {
|
||||
dataRaw = JSON.stringify(data)
|
||||
} else {
|
||||
throw Error("Unknown manifest type type");
|
||||
}
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
const ok = lib.extism_plugin_update(this.ctx.pointer, this.id, dataRaw, dataRaw.length, wasi);
|
||||
if (!ok) {
|
||||
var err = lib.extism_error(this.ctx.pointer, -1);
|
||||
if (err.length === 0) {
|
||||
throw "extism_plugin_update failed";
|
||||
}
|
||||
throw `Unable to update plugin: ${err.toString()}`;
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(this.ctx.pointer, this.id, s, s.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a function exists
|
||||
functionExists(name: string) {
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
return lib.extism_plugin_function_exists(this.ctx.pointer, this.id, name);
|
||||
}
|
||||
|
||||
// Call a function by name with the given input
|
||||
async call(name: string, input: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
var rc = lib.extism_plugin_call(this.ctx.pointer, this.id, name, input, input.length);
|
||||
if (rc !== 0) {
|
||||
var err = lib.extism_error(this.ctx.pointer, this.id);
|
||||
if (err.length === 0) {
|
||||
reject(`extism_plugin_call: "${name}" failed`);
|
||||
}
|
||||
reject(`Plugin error: ${err.toString()}, code: ${rc}`);
|
||||
}
|
||||
|
||||
var out_len = lib.extism_plugin_output_length(this.ctx.pointer, this.id);
|
||||
var buf = Buffer.from(lib.extism_plugin_output_data(this.ctx.pointer, this.id).buffer, 0, out_len);
|
||||
resolve(buf);
|
||||
});
|
||||
}
|
||||
|
||||
// Free a plugin, this should be called when the plugin is no longer needed
|
||||
free() {
|
||||
if (this.ctx.pointer && this.id !== -1) {
|
||||
pluginRegistry.unregister(this);
|
||||
lib.extism_plugin_free(this.ctx.pointer, this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
reject(`Plugin error: ${err.toString()}, code: ${rc}`);
|
||||
}
|
||||
|
||||
var out_len = lib.extism_plugin_output_length(this.ctx.pointer, this.id);
|
||||
var buf = Buffer.from(
|
||||
lib.extism_plugin_output_data(this.ctx.pointer, this.id).buffer,
|
||||
0,
|
||||
out_len
|
||||
);
|
||||
resolve(buf);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a plugin, this should be called when the plugin is no longer needed
|
||||
*/
|
||||
free() {
|
||||
if (this.ctx.pointer && this.id !== -1) {
|
||||
pluginRegistry.unregister(this);
|
||||
lib.extism_plugin_free(this.ctx.pointer, this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,105 +0,0 @@
|
||||
import * as extism from "../src/index";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
function manifest(): extism.Manifest {
|
||||
return {
|
||||
wasm: [{ path: join(__dirname, "/code.wasm") }],
|
||||
};
|
||||
}
|
||||
|
||||
function wasmBuffer(): Buffer {
|
||||
return readFileSync(join(__dirname, "/code.wasm"));
|
||||
}
|
||||
|
||||
describe("test extism", () => {
|
||||
test("can create new context", () => {
|
||||
let ctx = new extism.Context();
|
||||
expect(ctx).toBeTruthy();
|
||||
ctx.free();
|
||||
});
|
||||
|
||||
test("can create and call a plugin", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
let output = await plugin.call("count_vowels", "this is a test");
|
||||
let result = JSON.parse(output.toString());
|
||||
expect(result["count"]).toBe(4);
|
||||
output = await plugin.call("count_vowels", "this is a test again");
|
||||
result = JSON.parse(output.toString());
|
||||
expect(result["count"]).toBe(7);
|
||||
output = await plugin.call("count_vowels", "this is a test thrice");
|
||||
result = JSON.parse(output.toString());
|
||||
expect(result["count"]).toBe(6);
|
||||
output = await plugin.call("count_vowels", "🌎hello🌎world🌎");
|
||||
result = JSON.parse(output.toString());
|
||||
expect(result["count"]).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
test("can free a plugin", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
let output = await plugin.call("count_vowels", "this is a test");
|
||||
plugin.free();
|
||||
await expect(() =>
|
||||
plugin.call("count_vowels", "this is a test")
|
||||
).rejects.toMatch(/Plugin error/);
|
||||
});
|
||||
});
|
||||
|
||||
test("can update the manifest", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
let output = await plugin.call("count_vowels", "this is a test");
|
||||
let result = JSON.parse(output.toString());
|
||||
expect(result["count"]).toBe(4);
|
||||
// let's update the plugin with a manifest of raw module bytes
|
||||
plugin.update(wasmBuffer());
|
||||
// can still call it
|
||||
output = await plugin.call("count_vowels", "this is a test");
|
||||
result = JSON.parse(output.toString());
|
||||
expect(result["count"]).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
test("can detect if function exists or not", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
expect(plugin.functionExists("count_vowels")).toBe(true);
|
||||
expect(plugin.functionExists("i_dont_extist")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test("withContext returns results", async () => {
|
||||
const count = await extism.withContext(
|
||||
async (ctx: extism.Context): Promise<number> => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
let output = await plugin.call("count_vowels", "this is a test");
|
||||
let result = JSON.parse(output.toString());
|
||||
return result["count"];
|
||||
}
|
||||
);
|
||||
expect(count).toBe(4);
|
||||
});
|
||||
|
||||
test("errors when function is not known", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
await expect(() =>
|
||||
plugin.call("i_dont_exist", "example-input")
|
||||
).rejects.toMatch(/Plugin error/);
|
||||
});
|
||||
});
|
||||
|
||||
test("can result context", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest());
|
||||
await plugin.call("count_vowels", "this is a test");
|
||||
ctx.reset();
|
||||
await expect(() =>
|
||||
plugin.call("i_dont_exist", "example-input")
|
||||
).rejects.toMatch(/Plugin error/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,32 +1,107 @@
|
||||
{
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
"lib": [
|
||||
"es6"
|
||||
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||
"target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"lib": ["es6"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "CommonJS" /* Specify what module code is generated. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"resolveJsonModule": true /* Enable importing .json files. */,
|
||||
"module": "NodeNext", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
(executable
|
||||
(public_name extism)
|
||||
(name main)
|
||||
(libraries extism))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
(library
|
||||
(name extism)
|
||||
(public_name extism)
|
||||
(inline_tests
|
||||
(deps test/code.wasm))
|
||||
(libraries ctypes.foreign bigstringaf base64)
|
||||
(preprocess
|
||||
(pps ppx_yojson_conv ppx_inline_test)))
|
||||
(preprocess (pps ppx_yojson_conv)))
|
||||
|
||||
@@ -77,7 +77,8 @@ module Bindings = struct
|
||||
let extism_log_file =
|
||||
fn "extism_log_file" (string @-> string_opt @-> returning bool)
|
||||
|
||||
let extism_version = fn "extism_version" (void @-> returning string)
|
||||
let extism_version =
|
||||
fn "extism_version" (void @-> returning string)
|
||||
|
||||
let extism_plugin_free =
|
||||
fn "extism_plugin_free" (context @-> int32_t @-> returning void)
|
||||
@@ -123,7 +124,7 @@ module Manifest = struct
|
||||
}
|
||||
[@@deriving yojson]
|
||||
|
||||
type config = (string * string option) list
|
||||
type config = (string * string) list
|
||||
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url
|
||||
|
||||
let yojson_of_wasm = function
|
||||
@@ -136,38 +137,25 @@ module Manifest = struct
|
||||
with _ -> (
|
||||
try Data (wasm_data_of_yojson x) with _ -> Url (wasm_url_of_yojson x))
|
||||
|
||||
let is_null = function `Null -> true | _ -> false
|
||||
|
||||
let config_of_yojson j =
|
||||
let assoc = Yojson.Safe.Util.to_assoc j in
|
||||
List.map
|
||||
(fun (k, v) ->
|
||||
(k, if is_null v then None else Some (Yojson.Safe.Util.to_string v)))
|
||||
assoc
|
||||
List.map (fun (k, v) -> (k, Yojson.Safe.Util.to_string v)) assoc
|
||||
|
||||
let yojson_of_config c =
|
||||
`Assoc
|
||||
(List.map
|
||||
(fun (k, v) -> (k, match v with None -> `Null | Some v -> `String v))
|
||||
c)
|
||||
let yojson_of_config c = `Assoc (List.map (fun (k, v) -> (k, `String v)) c)
|
||||
|
||||
type t = {
|
||||
wasm : wasm list;
|
||||
memory : memory option; [@yojson.option]
|
||||
config : config option; [@yojson.option]
|
||||
allowed_hosts : string list option; [@yojson.option]
|
||||
allowed_hosts: string list option; [@yojson.option]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
|
||||
let file ?name ?hash path = File { path; name; hash }
|
||||
let data ?name ?hash data = Data { data; name; hash }
|
||||
let url ?header ?name ?meth ?hash url = Url { header; name; meth; hash; url }
|
||||
|
||||
let v ?config ?memory ?allowed_hosts wasm =
|
||||
{ config; wasm; memory; allowed_hosts }
|
||||
|
||||
let v ?config ?memory ?allowed_hosts wasm = { config; wasm; memory; allowed_hosts }
|
||||
let json t = yojson_of_t t |> Yojson.Safe.to_string
|
||||
let with_config t config = { t with config = Some config }
|
||||
end
|
||||
|
||||
module Context = struct
|
||||
@@ -184,12 +172,6 @@ module Context = struct
|
||||
ctx.pointer <- Ctypes.null
|
||||
|
||||
let reset ctx = Bindings.extism_context_reset ctx.pointer
|
||||
|
||||
let%test "test context" =
|
||||
let ctx = create () in
|
||||
reset ctx;
|
||||
free ctx;
|
||||
true
|
||||
end
|
||||
|
||||
type t = { id : int32; ctx : Context.t }
|
||||
@@ -205,12 +187,16 @@ let with_context f =
|
||||
Context.free ctx;
|
||||
x
|
||||
|
||||
let set_config plugin = function
|
||||
| None -> true
|
||||
let set_config plugin config =
|
||||
match config with
|
||||
| Some config ->
|
||||
let config = Manifest.yojson_of_config config |> Yojson.Safe.to_string in
|
||||
Bindings.extism_plugin_config plugin.ctx.pointer plugin.id config
|
||||
(Unsigned.UInt64.of_int (String.length config))
|
||||
let _ =
|
||||
Bindings.extism_plugin_config plugin.ctx.pointer plugin.id config
|
||||
(Unsigned.UInt64.of_int (String.length config))
|
||||
in
|
||||
()
|
||||
| None -> ()
|
||||
|
||||
let free t =
|
||||
if not (Ctypes.is_null t.ctx.pointer) then
|
||||
@@ -228,21 +214,13 @@ let plugin ?config ?(wasi = false) ctx wasm =
|
||||
| Some msg -> Error (`Msg msg)
|
||||
else
|
||||
let t = { id; ctx } in
|
||||
if not (set_config t config) then Error (`Msg "call to set_config failed")
|
||||
else
|
||||
let () = Gc.finalise free t in
|
||||
Ok t
|
||||
let () = set_config t config in
|
||||
let () = Gc.finalise free t in
|
||||
Ok t
|
||||
|
||||
let of_manifest ?wasi ctx manifest =
|
||||
let of_manifest ?config ?wasi ctx manifest =
|
||||
let data = Manifest.json manifest in
|
||||
plugin ctx ?wasi data
|
||||
|
||||
let%test "free plugin" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
free plugin;
|
||||
true)
|
||||
plugin ctx ?config ?wasi data
|
||||
|
||||
let update plugin ?config ?(wasi = false) wasm =
|
||||
let { id; ctx } = plugin in
|
||||
@@ -255,21 +233,13 @@ let update plugin ?config ?(wasi = false) wasm =
|
||||
match Bindings.extism_error ctx.pointer (-1l) with
|
||||
| None -> Error (`Msg "extism_plugin_update failed")
|
||||
| Some msg -> Error (`Msg msg)
|
||||
else if not (set_config plugin config) then
|
||||
Error (`Msg "call to set_config failed")
|
||||
else Ok ()
|
||||
else
|
||||
let () = set_config plugin config in
|
||||
Ok ()
|
||||
|
||||
let update_manifest plugin ?wasi manifest =
|
||||
let update_manifest plugin ?config ?wasi manifest =
|
||||
let data = Manifest.json manifest in
|
||||
update plugin ?wasi data
|
||||
|
||||
let%test "update plugin manifest and config" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let config = [ ("a", Some "1") ] in
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
let manifest = Manifest.with_config manifest config in
|
||||
update_manifest plugin manifest |> Result.is_ok)
|
||||
update plugin ?config ?wasi data
|
||||
|
||||
let call' f { id; ctx } ~name input len =
|
||||
let rc = f ctx.pointer id name input len in
|
||||
@@ -292,51 +262,14 @@ let call_bigstring (t : t) ~name input =
|
||||
let ptr = Ctypes.bigarray_start Ctypes.array1 input in
|
||||
call' Bindings.extism_plugin_call t ~name ptr len
|
||||
|
||||
let%test "call_bigstring" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
call_bigstring plugin ~name:"count_vowels"
|
||||
(Bigstringaf.of_string ~off:0 ~len:14 "this is a test")
|
||||
|> Result.get_ok |> Bigstringaf.to_string = "{\"count\": 4}")
|
||||
|
||||
let call (t : t) ~name input =
|
||||
let len = String.length input in
|
||||
call' Bindings.extism_plugin_call_s t ~name input (Unsigned.UInt64.of_int len)
|
||||
|> Result.map Bigstringaf.to_string
|
||||
|
||||
let%test "call" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
call plugin ~name:"count_vowels" "this is a test"
|
||||
|> Result.get_ok = "{\"count\": 4}")
|
||||
|
||||
let function_exists { id; ctx } name =
|
||||
Bindings.extism_plugin_function_exists ctx.pointer id name
|
||||
|
||||
let%test "function exists" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
function_exists plugin "count_vowels"
|
||||
&& not (function_exists plugin "function_does_not_exist"))
|
||||
|
||||
let set_log_file ?level filename =
|
||||
let level =
|
||||
Option.map
|
||||
(function
|
||||
| `Error -> "error"
|
||||
| `Warn -> "warn"
|
||||
| `Info -> "info"
|
||||
| `Debug -> "debug"
|
||||
| `Trace -> "trace")
|
||||
level
|
||||
in
|
||||
Bindings.extism_log_file filename level
|
||||
|
||||
let%test _ = set_log_file ~level:`Trace "stderr"
|
||||
let set_log_file ?level filename = Bindings.extism_log_file filename level
|
||||
|
||||
let extism_version = Bindings.extism_version
|
||||
|
||||
let%test _ = String.length (extism_version ()) > 0
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
type t
|
||||
type error = [ `Msg of string ]
|
||||
type error = [`Msg of string]
|
||||
|
||||
val extism_version : unit -> string
|
||||
val extism_version: unit -> string
|
||||
|
||||
module Manifest : sig
|
||||
type memory = { max : int option } [@@deriving yojson]
|
||||
|
||||
type wasm_file = {
|
||||
path : string;
|
||||
name : string option; [@yojson.option]
|
||||
@@ -27,79 +26,42 @@ module Manifest : sig
|
||||
}
|
||||
|
||||
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url
|
||||
type config = (string * (string option)) list
|
||||
|
||||
type config = (string * string) list
|
||||
|
||||
type t = {
|
||||
wasm : wasm list;
|
||||
memory : memory option;
|
||||
config : config option;
|
||||
allowed_hosts : string list option;
|
||||
config: config option;
|
||||
allowed_hosts: string list option;
|
||||
}
|
||||
|
||||
val file : ?name:string -> ?hash:string -> string -> wasm
|
||||
val data : ?name:string -> ?hash:string -> string -> wasm
|
||||
|
||||
val url :
|
||||
?header:(string * string) list ->
|
||||
?name:string ->
|
||||
?meth:string ->
|
||||
?hash:string ->
|
||||
string ->
|
||||
wasm
|
||||
|
||||
val v :
|
||||
val file: ?name:string -> ?hash:string -> string -> wasm
|
||||
val data: ?name:string -> ?hash:string -> string -> wasm
|
||||
val url: ?header:(string * string) list -> ?name:string -> ?meth:string -> ?hash:string -> string -> wasm
|
||||
val v:
|
||||
?config:config ->
|
||||
?memory:memory ->
|
||||
?allowed_hosts:string list ->
|
||||
wasm list ->
|
||||
t
|
||||
|
||||
val json : t -> string
|
||||
val with_config : t -> config -> t
|
||||
?allowed_hosts: string list ->
|
||||
wasm list -> t
|
||||
val json: t -> string
|
||||
end
|
||||
|
||||
module Context : sig
|
||||
type t
|
||||
|
||||
val create : unit -> t
|
||||
val free : t -> unit
|
||||
val reset : t -> unit
|
||||
val create: unit -> t
|
||||
val free: t -> unit
|
||||
val reset: t -> unit
|
||||
end
|
||||
|
||||
val with_context : (Context.t -> 'a) -> 'a
|
||||
|
||||
val set_log_file :
|
||||
?level:[ `Error | `Warn | `Info | `Debug | `Trace ] -> string -> bool
|
||||
|
||||
val plugin :
|
||||
?config:(string * string option) list ->
|
||||
?wasi:bool ->
|
||||
Context.t ->
|
||||
string ->
|
||||
(t, [ `Msg of string ]) result
|
||||
|
||||
val of_manifest :
|
||||
?wasi:bool ->
|
||||
Context.t ->
|
||||
Manifest.t ->
|
||||
(t, [ `Msg of string ]) result
|
||||
|
||||
val update :
|
||||
t ->
|
||||
?config:(string * string option) list ->
|
||||
?wasi:bool ->
|
||||
string ->
|
||||
(unit, [ `Msg of string ]) result
|
||||
|
||||
val update_manifest :
|
||||
t ->
|
||||
?wasi:bool ->
|
||||
Manifest.t ->
|
||||
(unit, [ `Msg of string ]) result
|
||||
|
||||
val call_bigstring :
|
||||
t -> name:string -> Bigstringaf.t -> (Bigstringaf.t, error) result
|
||||
|
||||
val call : t -> name:string -> string -> (string, error) result
|
||||
val free : t -> unit
|
||||
val function_exists : t -> string -> bool
|
||||
val set_log_file: ?level:string -> string -> bool
|
||||
val plugin: ?config:(string * string) list -> ?wasi:bool -> Context.t -> string -> (t, [`Msg of string]) result
|
||||
val of_manifest: ?config:(string * string) list -> ?wasi:bool -> Context.t -> Manifest.t -> (t, [`Msg of string]) result
|
||||
val update: t -> ?config:(string * string) list -> ?wasi:bool -> string -> (unit, [`Msg of string]) result
|
||||
val update_manifest: t -> ?config:(string * string) list -> ?wasi:bool -> Manifest.t -> (unit, [`Msg of string]) result
|
||||
val call_bigstring: t -> name:string -> Bigstringaf.t -> (Bigstringaf.t, error) result
|
||||
val call: t -> name:string -> string -> (string, error) result
|
||||
val free: t -> unit
|
||||
val function_exists: t -> string -> bool
|
||||
|
||||
Binary file not shown.
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "extism/example",
|
||||
"description": "Example running the Extism PHP Host SDK",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "project",
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"extism/extism": "*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "../../"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev"
|
||||
"name": "extism/example",
|
||||
"description": "Example running the Extism PHP Host SDK",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "project",
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Extism Authors",
|
||||
"email": "oss@extism.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"extism/extism": "*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "../../"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
35
php/example/composer.lock
generated
35
php/example/composer.lock
generated
@@ -8,11 +8,11 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "extism/extism",
|
||||
"version": "dev-php-sdk-fix",
|
||||
"version": "dev-feat-reuse-plugins",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "../..",
|
||||
"reference": "6119eae37bcb2e02f7c7b5b203ab9f959819e83d"
|
||||
"reference": "9e5f576acff0aa9c8720fdf6d1103b7c1996bf14"
|
||||
},
|
||||
"require": {
|
||||
"ircmaxell/ffime": "dev-master",
|
||||
@@ -22,12 +22,7 @@
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Extism\\": "php/src/"
|
||||
},
|
||||
"files": [
|
||||
"php/src/Context.php",
|
||||
"php/src/Plugin.php",
|
||||
"php/src/extism.h"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": []
|
||||
@@ -64,12 +59,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ircmaxell/FFIMe.git",
|
||||
"reference": "f6911d7a6a7090a9782a21a946819a2efa9a2ff7"
|
||||
"reference": "7b9e0bf23adceddd5fde3130d30275a45cfc1867"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ircmaxell/FFIMe/zipball/f6911d7a6a7090a9782a21a946819a2efa9a2ff7",
|
||||
"reference": "f6911d7a6a7090a9782a21a946819a2efa9a2ff7",
|
||||
"url": "https://api.github.com/repos/ircmaxell/FFIMe/zipball/7b9e0bf23adceddd5fde3130d30275a45cfc1867",
|
||||
"reference": "7b9e0bf23adceddd5fde3130d30275a45cfc1867",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -102,7 +97,7 @@
|
||||
"issues": "https://github.com/ircmaxell/FFIMe/issues",
|
||||
"source": "https://github.com/ircmaxell/FFIMe/tree/master"
|
||||
},
|
||||
"time": "2022-09-25T18:13:59+00:00"
|
||||
"time": "2022-09-07T19:50:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ircmaxell/php-c-parser",
|
||||
@@ -110,12 +105,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ircmaxell/php-c-parser.git",
|
||||
"reference": "fd8f5efefd0fcc6c5119d945694acaa3a6790ada"
|
||||
"reference": "55e0a4fdf88d6e955d928860e1e107a68492c1cf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ircmaxell/php-c-parser/zipball/fd8f5efefd0fcc6c5119d945694acaa3a6790ada",
|
||||
"reference": "fd8f5efefd0fcc6c5119d945694acaa3a6790ada",
|
||||
"url": "https://api.github.com/repos/ircmaxell/php-c-parser/zipball/55e0a4fdf88d6e955d928860e1e107a68492c1cf",
|
||||
"reference": "55e0a4fdf88d6e955d928860e1e107a68492c1cf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -151,7 +146,7 @@
|
||||
"issues": "https://github.com/ircmaxell/php-c-parser/issues",
|
||||
"source": "https://github.com/ircmaxell/php-c-parser/tree/master"
|
||||
},
|
||||
"time": "2022-09-23T19:39:35+00:00"
|
||||
"time": "2022-08-27T17:37:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ircmaxell/php-object-symbolresolver",
|
||||
@@ -159,12 +154,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ircmaxell/php-object-symbolresolver.git",
|
||||
"reference": "dfe1b1aa6c15b198bdef50fff8485e98e89f2a09"
|
||||
"reference": "88c918a0f4621ef59dc4a4c21ead7f39bd720337"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ircmaxell/php-object-symbolresolver/zipball/dfe1b1aa6c15b198bdef50fff8485e98e89f2a09",
|
||||
"reference": "dfe1b1aa6c15b198bdef50fff8485e98e89f2a09",
|
||||
"url": "https://api.github.com/repos/ircmaxell/php-object-symbolresolver/zipball/88c918a0f4621ef59dc4a4c21ead7f39bd720337",
|
||||
"reference": "88c918a0f4621ef59dc4a4c21ead7f39bd720337",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -196,7 +191,7 @@
|
||||
"issues": "https://github.com/ircmaxell/php-object-symbolresolver/issues",
|
||||
"source": "https://github.com/ircmaxell/php-object-symbolresolver/tree/master"
|
||||
},
|
||||
"time": "2022-09-15T18:21:50+00:00"
|
||||
"time": "2022-09-07T19:47:04+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
@@ -3,37 +3,12 @@ declare(strict_types=1);
|
||||
namespace Extism;
|
||||
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
function generate_extism_lib() {
|
||||
return (new \FFIMe\FFIMe("libextism.".soext()))
|
||||
->include("extism.h")
|
||||
->showWarnings(false)
|
||||
->codeGen('ExtismLib', __DIR__.'/ExtismLib.php');
|
||||
}
|
||||
|
||||
function soext() {
|
||||
$platform = php_uname("s");
|
||||
switch ($platform) {
|
||||
case "Darwin":
|
||||
return "dylib";
|
||||
case "Linux":
|
||||
return "so";
|
||||
case "Windows":
|
||||
return "dll";
|
||||
default:
|
||||
throw new \Exception("Extism: unsupported platform ".$platform);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__."/ExtismLib.php")) {
|
||||
generate_extism_lib();
|
||||
}
|
||||
|
||||
require_once "generate.php";
|
||||
require_once "ExtismLib.php";
|
||||
|
||||
$lib = new \ExtismLib(\ExtismLib::SOFILE);
|
||||
if ($lib == null) {
|
||||
throw new \Exception("Extism: failed to create new runtime instance");
|
||||
throw new Exception("Extism: failed to create new runtime instance");
|
||||
}
|
||||
|
||||
class Context
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
declare(strict_types=1);
|
||||
namespace Extism;
|
||||
|
||||
require_once "vendor/autoload.php";
|
||||
require_once "generate.php";
|
||||
require_once "ExtismLib.php";
|
||||
|
||||
class Plugin
|
||||
@@ -32,13 +34,13 @@ class Plugin
|
||||
$id = $this->lib->extism_plugin_new($ctx->pointer, $data, count($data), (int)$wasi);
|
||||
if ($id < 0) {
|
||||
$err = $this->lib->extism_error($ctx->pointer, -1);
|
||||
throw new \Exception("Extism: unable to load plugin: " . $err);
|
||||
throw new Exception("Extism: unable to load plugin: " . $err);
|
||||
}
|
||||
$this->id = $id;
|
||||
$this->context = $ctx;
|
||||
|
||||
if ($this->config != null) {
|
||||
$cfg = string_to_bytes(json_encode($config));
|
||||
if ($config != null) {
|
||||
$cfg = string_to_bytes(json_encode(config));
|
||||
$this->lib->extism_plugin_config($ctx->pointer, $this->id, $cfg, count($cfg));
|
||||
}
|
||||
}
|
||||
@@ -71,14 +73,14 @@ class Plugin
|
||||
if ($err) {
|
||||
$msg = $msg . ", error = " . $err;
|
||||
}
|
||||
throw new \Exception("Extism: call to '".$name."' failed with " . $msg);
|
||||
throw new Execption("Extism: call to '".$name."' failed with " . $msg);
|
||||
}
|
||||
|
||||
$length = $this->lib->extism_plugin_output_length($this->context->pointer, $this->id);
|
||||
|
||||
$buf = $this->lib->extism_plugin_output_data($this->context->pointer, $this->id);
|
||||
|
||||
$output = [];
|
||||
$ouput = [];
|
||||
$data = $buf->getData();
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$output[$i] = $data[$i];
|
||||
@@ -99,7 +101,7 @@ class Plugin
|
||||
$ok = $this->lib->extism_plugin_update($this->context->pointer, $this->id, $data, count($data), (int)$wasi);
|
||||
if (!$ok) {
|
||||
$err = $this->lib->extism_error($this->context->pointer, -1);
|
||||
throw new \Exception("Extism: unable to update plugin: " . $err);
|
||||
throw new Exception("Extism: unable to update plugin: " . $err);
|
||||
}
|
||||
|
||||
if ($config != null) {
|
||||
|
||||
29
php/src/generate.php
Normal file
29
php/src/generate.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
|
||||
function generate() {
|
||||
return (new FFIMe\FFIMe("libextism.".soext()))
|
||||
->include("extism.h")
|
||||
->showWarnings(false)
|
||||
->codeGen('ExtismLib', __DIR__.'/ExtismLib.php');
|
||||
}
|
||||
|
||||
function soext() {
|
||||
$platform = php_uname("s");
|
||||
switch ($platform) {
|
||||
case "Darwin":
|
||||
return "dylib";
|
||||
case "Linux":
|
||||
return "so";
|
||||
case "Windows":
|
||||
return "dll";
|
||||
default:
|
||||
throw new Exeception("Extism: unsupported platform ".$platform);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__."/ExtismLib.php")) {
|
||||
generate();
|
||||
}
|
||||
|
||||
1
python/.gitignore
vendored
1
python/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
html
|
||||
@@ -1,26 +0,0 @@
|
||||
.PHONY: test
|
||||
|
||||
prepare:
|
||||
poetry install
|
||||
|
||||
test: prepare
|
||||
poetry run python -m unittest discover
|
||||
|
||||
clean:
|
||||
rm -rf dist/*
|
||||
|
||||
publish: clean prepare
|
||||
poetry build
|
||||
poetry run twine upload dist/extism-*.tar.gz
|
||||
|
||||
format:
|
||||
poetry run black extism/ tests/ example.py
|
||||
|
||||
lint:
|
||||
poetry run black --check extism/ tests/ example.py
|
||||
|
||||
docs:
|
||||
poetry run pdoc --force --html extism
|
||||
|
||||
show-docs: docs
|
||||
open html/extism/index.html
|
||||
@@ -1,9 +1,10 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
|
||||
sys.path.append(".")
|
||||
from extism import Context
|
||||
from extism import Plugin, Context
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
data = sys.argv[1].encode()
|
||||
@@ -13,7 +14,7 @@ else:
|
||||
# a Context provides a scope for plugins to be managed within. creating multiple contexts
|
||||
# is expected and groups plugins based on source/tenant/lifetime etc.
|
||||
with Context() as context:
|
||||
wasm = open("../wasm/code.wasm", "rb").read()
|
||||
wasm = open("../wasm/code.wasm", 'rb').read()
|
||||
hash = hashlib.sha256(wasm).hexdigest()
|
||||
config = {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max": 5}}
|
||||
|
||||
@@ -27,9 +28,9 @@ with Context() as context:
|
||||
def count_vowels(data):
|
||||
count = 0
|
||||
for c in data:
|
||||
if c in b"AaEeIiOoUu":
|
||||
if c in b'AaEeIiOoUu':
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
assert j["count"] == count_vowels(data)
|
||||
assert (j["count"] == count_vowels(data))
|
||||
|
||||
@@ -1,61 +1,66 @@
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from base64 import b64encode
|
||||
|
||||
from cffi import FFI
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Extism error type"""
|
||||
|
||||
'''Extism error type'''
|
||||
pass
|
||||
|
||||
|
||||
_search_dirs = ["/usr/local", "/usr", os.path.join(os.getenv("HOME"), ".local"), "."]
|
||||
search_dirs = [
|
||||
"/usr/local", "/usr",
|
||||
os.path.join(os.getenv("HOME"), ".local"), "."
|
||||
]
|
||||
|
||||
|
||||
def _check_for_header_and_lib(p):
|
||||
def _exists(a, *b):
|
||||
return os.path.exists(os.path.join(a, *b))
|
||||
def exists(a, *b):
|
||||
return os.path.exists(os.path.join(a, *b))
|
||||
|
||||
if _exists(p, "extism.h"):
|
||||
if _exists(p, "libextism.so"):
|
||||
|
||||
def check_for_header_and_lib(p):
|
||||
if exists(p, "extism.h"):
|
||||
if exists(p, "libextism.so"):
|
||||
return os.path.join(p, "extism.h"), os.path.join(p, "libextism.so")
|
||||
|
||||
if _exists(p, "libextism.dylib"):
|
||||
return os.path.join(p, "extism.h"), os.path.join(p, "libextism.dylib")
|
||||
if exists(p, "libextism.dylib"):
|
||||
return os.path.join(p, "extism.h"), os.path.join(
|
||||
p, "libextism.dylib")
|
||||
|
||||
if _exists(p, "include", "extism.h"):
|
||||
if _exists(p, "lib", "libextism.so"):
|
||||
if exists(p, "include", "extism.h"):
|
||||
if exists(p, "lib", "libextism.so"):
|
||||
return os.path.join(p, "include", "extism.h"), os.path.join(
|
||||
p, "lib", "libextism.so"
|
||||
)
|
||||
p, "lib", "libextism.so")
|
||||
|
||||
if _exists(p, "lib", "libextism.dylib"):
|
||||
if exists(p, "lib", "libextism.dylib"):
|
||||
return os.path.join(p, "include", "extism.h"), os.path.join(
|
||||
p, "lib", "libextism.dylib"
|
||||
)
|
||||
p, "lib", "libextism.dylib")
|
||||
|
||||
|
||||
def _locate():
|
||||
"""Locate extism library and header"""
|
||||
def locate():
|
||||
'''Locate extism library and header'''
|
||||
script_dir = os.path.dirname(__file__)
|
||||
env = os.getenv("EXTISM_PATH")
|
||||
if env is not None:
|
||||
r = _check_for_header_and_lib(env)
|
||||
r = check_for_header_and_lib(env)
|
||||
if r is not None:
|
||||
return r
|
||||
|
||||
r = _check_for_header_and_lib(script_dir)
|
||||
r = check_for_header_and_lib(script_dir)
|
||||
if r is not None:
|
||||
return r
|
||||
|
||||
r = _check_for_header_and_lib(".")
|
||||
r = check_for_header_and_lib(".")
|
||||
if r is not None:
|
||||
return r
|
||||
|
||||
for d in _search_dirs:
|
||||
r = _check_for_header_and_lib(d)
|
||||
for d in search_dirs:
|
||||
r = check_for_header_and_lib(d)
|
||||
if r is not None:
|
||||
return r
|
||||
|
||||
@@ -63,18 +68,18 @@ def _locate():
|
||||
|
||||
|
||||
# Initialize the C library
|
||||
_ffi = FFI()
|
||||
_header, _lib = _locate()
|
||||
with open(_header) as f:
|
||||
ffi = FFI()
|
||||
header, lib = locate()
|
||||
with open(header) as f:
|
||||
lines = []
|
||||
for line in f.readlines():
|
||||
if line[0] != "#":
|
||||
if line[0] != '#':
|
||||
lines.append(line)
|
||||
_ffi.cdef("".join(lines))
|
||||
_lib = _ffi.dlopen(_lib)
|
||||
ffi.cdef(''.join(lines))
|
||||
lib = ffi.dlopen(lib)
|
||||
|
||||
|
||||
class _Base64Encoder(json.JSONEncoder):
|
||||
class Base64Encoder(json.JSONEncoder):
|
||||
# pylint: disable=method-hidden
|
||||
def default(self, o):
|
||||
if isinstance(o, bytes):
|
||||
@@ -82,74 +87,38 @@ class _Base64Encoder(json.JSONEncoder):
|
||||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
def set_log_file(file, level=None):
|
||||
"""
|
||||
Sets the log file and level, this is a global configuration
|
||||
|
||||
Parameters
|
||||
----------
|
||||
file : str
|
||||
The path to the logfile
|
||||
level : str
|
||||
The debug level, one of ('debug', 'error', 'trace', 'warn')
|
||||
"""
|
||||
level = level or _ffi.NULL
|
||||
def set_log_file(file, level=ffi.NULL):
|
||||
'''Sets the log file and level, this is a global configuration'''
|
||||
if isinstance(level, str):
|
||||
level = level.encode()
|
||||
_lib.extism_log_file(file.encode(), level)
|
||||
|
||||
lib.extism_log_file(file.encode(), level)
|
||||
|
||||
def extism_version():
|
||||
"""
|
||||
Gets the Extism version string
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The Extism runtime version string
|
||||
"""
|
||||
return _ffi.string(_lib.extism_version()).decode()
|
||||
|
||||
'''Gets the Extism version string'''
|
||||
return ffi.string(lib.extism_version()).decode()
|
||||
|
||||
def _wasm(plugin):
|
||||
if isinstance(plugin, str) and os.path.exists(plugin):
|
||||
with open(plugin, "rb") as f:
|
||||
with open(plugin, 'rb') as f:
|
||||
wasm = f.read()
|
||||
elif isinstance(plugin, str):
|
||||
wasm = plugin.encode()
|
||||
elif isinstance(plugin, dict):
|
||||
wasm = json.dumps(plugin, cls=_Base64Encoder).encode()
|
||||
wasm = json.dumps(plugin, cls=Base64Encoder).encode()
|
||||
else:
|
||||
wasm = plugin
|
||||
return wasm
|
||||
|
||||
|
||||
class Context:
|
||||
"""
|
||||
Context is used to store and manage plugins. You need a context to create
|
||||
or call plugins. The best way to interact with the Context is
|
||||
as a context manager as it can ensure that resources are cleaned up.
|
||||
|
||||
Example
|
||||
-------
|
||||
with Context() as ctx:
|
||||
plugin = ctx.plugin(manifest)
|
||||
print(plugin.call("my_function", "some-input"))
|
||||
|
||||
If you need a long lived context, you can use the constructor and the `del` keyword to free.
|
||||
|
||||
Example
|
||||
-------
|
||||
ctx = Context()
|
||||
del ctx
|
||||
"""
|
||||
'''Context is used to store and manage plugins'''
|
||||
|
||||
def __init__(self):
|
||||
self.pointer = _lib.extism_context_new()
|
||||
self.pointer = lib.extism_context_new()
|
||||
|
||||
def __del__(self):
|
||||
_lib.extism_context_free(self.pointer)
|
||||
self.pointer = _ffi.NULL
|
||||
lib.extism_context_free(self.pointer)
|
||||
self.pointer = ffi.NULL
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -158,151 +127,94 @@ class Context:
|
||||
self.__del__()
|
||||
|
||||
def reset(self):
|
||||
"""Remove all registered plugins"""
|
||||
_lib.extism_context_reset(self.pointer)
|
||||
'''Remove all registered plugins'''
|
||||
lib.extism_context_reset(self.pointer)
|
||||
|
||||
def plugin(self, manifest: Union[str, bytes, dict], wasi=False, config=None):
|
||||
"""
|
||||
Register a new plugin from a WASM module or JSON encoded manifest
|
||||
|
||||
Parameters
|
||||
----------
|
||||
manifest : Union[str, bytes, dict]
|
||||
A manifest dictionary describing the plugin or the raw bytes for a module. See [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest/).
|
||||
wasi : bool
|
||||
Set to `True` to enable WASI support
|
||||
config : dict
|
||||
The plugin config dictionary
|
||||
|
||||
Returns
|
||||
-------
|
||||
Plugin
|
||||
The created plugin
|
||||
"""
|
||||
return Plugin(self, manifest, wasi, config)
|
||||
def plugin(self, plugin: Union[str, bytes, dict], wasi=False, config=None):
|
||||
'''Register a new plugin from a WASM module or JSON encoded manifest'''
|
||||
return Plugin(self, plugin, wasi, config)
|
||||
|
||||
|
||||
class Plugin:
|
||||
"""
|
||||
Plugin is used to call WASM functions.
|
||||
Plugins can kept in a context for as long as you need
|
||||
or be freed with the `del` keyword.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, context: Context, plugin: Union[str, bytes, dict], wasi=False, config=None
|
||||
):
|
||||
"""
|
||||
Construct a Plugin. Please use Context#plugin instead.
|
||||
"""
|
||||
'''Plugin is used to call WASM functions'''
|
||||
|
||||
def __init__(self,
|
||||
context: Context,
|
||||
plugin: Union[str, bytes, dict],
|
||||
wasi=False,
|
||||
config=None):
|
||||
wasm = _wasm(plugin)
|
||||
|
||||
# Register plugin
|
||||
self.plugin = _lib.extism_plugin_new(context.pointer, wasm, len(wasm), wasi)
|
||||
self.plugin = lib.extism_plugin_new(context.pointer, wasm, len(wasm),
|
||||
wasi)
|
||||
|
||||
self.ctx = context
|
||||
|
||||
if self.plugin < 0:
|
||||
error = _lib.extism_error(self.ctx.pointer, -1)
|
||||
if error != _ffi.NULL:
|
||||
raise Error(_ffi.string(error).decode())
|
||||
error = lib.extism_error(self.ctx.pointer, -1)
|
||||
if error != ffi.NULL:
|
||||
raise Error(ffi.string(error).decode())
|
||||
raise Error("Unable to register plugin")
|
||||
|
||||
if config is not None:
|
||||
s = json.dumps(config).encode()
|
||||
_lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
|
||||
lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
|
||||
|
||||
def update(self, manifest: Union[str, bytes, dict], wasi=False, config=None):
|
||||
"""
|
||||
Update a plugin with a new WASM module or manifest
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plugin : Union[str, bytes, dict]
|
||||
A manifest dictionary describing the plugin or the raw bytes for a module. See [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest/).
|
||||
wasi : bool
|
||||
Set to `True` to enable WASI support
|
||||
config : dict
|
||||
The plugin config dictionary
|
||||
"""
|
||||
wasm = _wasm(manifest)
|
||||
ok = _lib.extism_plugin_update(
|
||||
self.ctx.pointer, self.plugin, wasm, len(wasm), wasi
|
||||
)
|
||||
def update(self, plugin: Union[str, bytes, dict], wasi=False, config=None):
|
||||
'''Update a plugin with a new WASM module or manifest'''
|
||||
wasm = _wasm(plugin)
|
||||
ok = lib.extism_plugin_update(self.ctx.pointer, self.plugin, wasm,
|
||||
len(wasm), wasi)
|
||||
if not ok:
|
||||
error = _lib.extism_error(self.ctx.pointer, -1)
|
||||
if error != _ffi.NULL:
|
||||
raise Error(_ffi.string(error).decode())
|
||||
error = lib.extism_error(self.ctx.pointer, -1)
|
||||
if error != ffi.NULL:
|
||||
raise Error(ffi.string(error).decode())
|
||||
raise Error("Unable to update plugin")
|
||||
|
||||
if config is not None:
|
||||
s = json.dumps(config).encode()
|
||||
_lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
|
||||
lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
|
||||
|
||||
def _check_error(self, rc):
|
||||
if rc != 0:
|
||||
error = _lib.extism_error(self.ctx.pointer, self.plugin)
|
||||
if error != _ffi.NULL:
|
||||
raise Error(_ffi.string(error).decode())
|
||||
error = lib.extism_error(self.ctx.pointer, self.plugin)
|
||||
if error != ffi.NULL:
|
||||
raise Error(ffi.string(error).decode())
|
||||
raise Error(f"Error code: {rc}")
|
||||
|
||||
def function_exists(self, name: str) -> bool:
|
||||
"""
|
||||
Returns true if the given function exists
|
||||
'''Returns true if the given function exists'''
|
||||
return lib.extism_plugin_function_exists(self.ctx.pointer, self.plugin,
|
||||
name.encode())
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The function name to check for
|
||||
|
||||
Returns
|
||||
-------
|
||||
True if the function exists in the plugin, False otherwise
|
||||
"""
|
||||
return _lib.extism_plugin_function_exists(
|
||||
self.ctx.pointer, self.plugin, name.encode()
|
||||
)
|
||||
|
||||
def call(self, function_name: str, data: Union[str, bytes], parse=bytes):
|
||||
"""
|
||||
def call(self, name: str, data: Union[str, bytes], parse=bytes):
|
||||
'''
|
||||
Call a function by name with the provided input data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the function to invoke
|
||||
data : Union[str, bytes]
|
||||
The input data to the function, can be bytes or a string
|
||||
parse : Func
|
||||
Can be used to transform the output buffer into
|
||||
your expected type. It expects a function that takes a buffer as the
|
||||
only argument.
|
||||
|
||||
Return
|
||||
------
|
||||
The bytes or parsed data from the plugin function
|
||||
"""
|
||||
The `parse` argument can be used to transform the output buffer into
|
||||
your expected type. It expects a function that takes a buffer as the
|
||||
only argument
|
||||
'''
|
||||
if isinstance(data, str):
|
||||
data = data.encode()
|
||||
self._check_error(
|
||||
_lib.extism_plugin_call(
|
||||
self.ctx.pointer, self.plugin, function_name.encode(), data, len(data)
|
||||
)
|
||||
)
|
||||
out_len = _lib.extism_plugin_output_length(self.ctx.pointer, self.plugin)
|
||||
out_buf = _lib.extism_plugin_output_data(self.ctx.pointer, self.plugin)
|
||||
buf = _ffi.buffer(out_buf, out_len)
|
||||
lib.extism_plugin_call(self.ctx.pointer, self.plugin,
|
||||
name.encode(), data, len(data)))
|
||||
out_len = lib.extism_plugin_output_length(self.ctx.pointer,
|
||||
self.plugin)
|
||||
out_buf = lib.extism_plugin_output_data(self.ctx.pointer, self.plugin)
|
||||
buf = ffi.buffer(out_buf, out_len)
|
||||
if parse is None:
|
||||
return buf
|
||||
return parse(buf)
|
||||
|
||||
def __del__(self):
|
||||
if not hasattr(self, "ctx"):
|
||||
if not hasattr(self, 'ctx'):
|
||||
return
|
||||
if self.ctx.pointer == _ffi.NULL:
|
||||
if self.ctx.pointer == ffi.NULL:
|
||||
return
|
||||
_lib.extism_plugin_free(self.ctx.pointer, self.plugin)
|
||||
lib.extism_plugin_free(self.ctx.pointer, self.plugin)
|
||||
self.plugin = -1
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
[tool.poetry]
|
||||
name = "extism"
|
||||
version = "0.0.1"
|
||||
description = "Extism Host SDK for python"
|
||||
version = "0.0.1-rc.5"
|
||||
description = ""
|
||||
authors = ["The Extism Authors <oss@extism.org>"]
|
||||
license = "BSD-3-Clause"
|
||||
readme = "../README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
cffi = "^1.10.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = "^22.10.0"
|
||||
pdoc3 = "^0.10.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
@@ -4,7 +4,6 @@ import hashlib
|
||||
import json
|
||||
from os.path import join, dirname
|
||||
|
||||
|
||||
class TestExtism(unittest.TestCase):
|
||||
def test_context_new(self):
|
||||
ctx = extism.Context()
|
||||
@@ -15,13 +14,11 @@ class TestExtism(unittest.TestCase):
|
||||
with extism.Context() as ctx:
|
||||
plugin = ctx.plugin(self._manifest())
|
||||
j = json.loads(plugin.call("count_vowels", "this is a test"))
|
||||
self.assertEqual(j["count"], 4)
|
||||
self.assertEqual(j['count'], 4)
|
||||
j = json.loads(plugin.call("count_vowels", "this is a test again"))
|
||||
self.assertEqual(j["count"], 7)
|
||||
self.assertEqual(j['count'], 7)
|
||||
j = json.loads(plugin.call("count_vowels", "this is a test thrice"))
|
||||
self.assertEqual(j["count"], 6)
|
||||
j = json.loads(plugin.call("count_vowels", "🌎hello🌎world🌎"))
|
||||
self.assertEqual(j["count"], 3)
|
||||
self.assertEqual(j['count'], 6)
|
||||
|
||||
def test_update_plugin_manifest(self):
|
||||
with extism.Context() as ctx:
|
||||
@@ -30,7 +27,7 @@ class TestExtism(unittest.TestCase):
|
||||
plugin.update(self._count_vowels_wasm())
|
||||
# should still work
|
||||
j = json.loads(plugin.call("count_vowels", "this is a test"))
|
||||
self.assertEqual(j["count"], 4)
|
||||
self.assertEqual(j['count'], 4)
|
||||
|
||||
def test_function_exists(self):
|
||||
with extism.Context() as ctx:
|
||||
@@ -41,8 +38,8 @@ class TestExtism(unittest.TestCase):
|
||||
def test_errors_on_unknown_function(self):
|
||||
with extism.Context() as ctx:
|
||||
plugin = ctx.plugin(self._manifest())
|
||||
self.assertRaises(
|
||||
extism.Error, lambda: plugin.call("i_dont_exist", "someinput")
|
||||
self.assertRaises(extism.Error,
|
||||
lambda: plugin.call("i_dont_exist", "someinput")
|
||||
)
|
||||
|
||||
def test_can_free_plugin(self):
|
||||
@@ -52,12 +49,12 @@ class TestExtism(unittest.TestCase):
|
||||
|
||||
def test_errors_on_bad_manifest(self):
|
||||
with extism.Context() as ctx:
|
||||
self.assertRaises(
|
||||
extism.Error, lambda: ctx.plugin({"invalid_manifest": True})
|
||||
self.assertRaises(extism.Error,
|
||||
lambda: ctx.plugin({"invalid_manifest": True})
|
||||
)
|
||||
plugin = ctx.plugin(self._manifest())
|
||||
self.assertRaises(
|
||||
extism.Error, lambda: plugin.update({"invalid_manifest": True})
|
||||
self.assertRaises(extism.Error,
|
||||
lambda: plugin.update({"invalid_manifest": True})
|
||||
)
|
||||
|
||||
def test_extism_version(self):
|
||||
@@ -69,6 +66,6 @@ class TestExtism(unittest.TestCase):
|
||||
return {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max": 5}}
|
||||
|
||||
def _count_vowels_wasm(self):
|
||||
path = join(dirname(__file__), "code.wasm")
|
||||
with open(path, "rb") as wasm_file:
|
||||
path = join(dirname(__file__), 'code.wasm')
|
||||
with open(path, 'rb') as wasm_file:
|
||||
return wasm_file.read()
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
--readme GETTING_STARTED.md
|
||||
- GETTING_STARTED.md
|
||||
@@ -1,35 +0,0 @@
|
||||
# Extism
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Example
|
||||
|
||||
```ruby
|
||||
require "extism"
|
||||
require "json"
|
||||
|
||||
Extism.with_context do |ctx|
|
||||
manifest = {
|
||||
:wasm => [{ :path => "../wasm/code.wasm" }],
|
||||
}
|
||||
plugin = ctx.plugin(manifest)
|
||||
res = JSON.parse(plugin.call("count_vowels", "this is a test"))
|
||||
puts res["count"] # => 4
|
||||
end
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
There are two primary classes you need to understand:
|
||||
|
||||
* [Context](Extism/Context.html)
|
||||
* [Plugin](Extism/Plugin.html)
|
||||
|
||||
#### Context
|
||||
|
||||
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).
|
||||
|
||||
#### Plugin
|
||||
|
||||
The [Plugin](Extism/Plugin.html) represents an instance of your WASM program from the given manifest.
|
||||
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.
|
||||
@@ -6,10 +6,4 @@ source "https://rubygems.org"
|
||||
gemspec
|
||||
|
||||
gem "rake", "~> 13.0"
|
||||
gem "ffi", "~> 1.15.5"
|
||||
|
||||
group :development do
|
||||
gem "yard", "~> 0.9.28"
|
||||
gem "rufo", "~> 0.13.0"
|
||||
gem "minitest", "~> 5.16.3"
|
||||
end
|
||||
gem "ffi"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
.PHONY: prepare test
|
||||
|
||||
prepare:
|
||||
bundle install
|
||||
bundle binstubs --all
|
||||
|
||||
test: prepare
|
||||
bundle exec rake test
|
||||
|
||||
clean:
|
||||
rm extism-*.gem
|
||||
|
||||
publish: clean prepare
|
||||
gem build extism.gemspec
|
||||
gem push extism-*.gem
|
||||
|
||||
lint:
|
||||
bundle exec rufo --check .
|
||||
|
||||
format:
|
||||
bundle exec rufo .
|
||||
|
||||
docs:
|
||||
bundle exec yard
|
||||
|
||||
show-docs: docs
|
||||
open doc/index.html
|
||||
114
ruby/bin/bundle
114
ruby/bin/bundle
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'bundle' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "rubygems"
|
||||
|
||||
m = Module.new do
|
||||
module_function
|
||||
|
||||
def invoked_as_script?
|
||||
File.expand_path($0) == File.expand_path(__FILE__)
|
||||
end
|
||||
|
||||
def env_var_version
|
||||
ENV["BUNDLER_VERSION"]
|
||||
end
|
||||
|
||||
def cli_arg_version
|
||||
return unless invoked_as_script? # don't want to hijack other binstubs
|
||||
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
||||
bundler_version = nil
|
||||
update_index = nil
|
||||
ARGV.each_with_index do |a, i|
|
||||
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
||||
bundler_version = a
|
||||
end
|
||||
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
||||
bundler_version = $1
|
||||
update_index = i
|
||||
end
|
||||
bundler_version
|
||||
end
|
||||
|
||||
def gemfile
|
||||
gemfile = ENV["BUNDLE_GEMFILE"]
|
||||
return gemfile if gemfile && !gemfile.empty?
|
||||
|
||||
File.expand_path("../Gemfile", __dir__)
|
||||
end
|
||||
|
||||
def lockfile
|
||||
lockfile =
|
||||
case File.basename(gemfile)
|
||||
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
||||
else "#{gemfile}.lock"
|
||||
end
|
||||
File.expand_path(lockfile)
|
||||
end
|
||||
|
||||
def lockfile_version
|
||||
return unless File.file?(lockfile)
|
||||
lockfile_contents = File.read(lockfile)
|
||||
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
||||
Regexp.last_match(1)
|
||||
end
|
||||
|
||||
def bundler_requirement
|
||||
@bundler_requirement ||=
|
||||
env_var_version || cli_arg_version ||
|
||||
bundler_requirement_for(lockfile_version)
|
||||
end
|
||||
|
||||
def bundler_requirement_for(version)
|
||||
return "#{Gem::Requirement.default}.a" unless version
|
||||
|
||||
bundler_gem_version = Gem::Version.new(version)
|
||||
|
||||
requirement = bundler_gem_version.approximate_recommendation
|
||||
|
||||
return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
|
||||
|
||||
requirement += ".a" if bundler_gem_version.prerelease?
|
||||
|
||||
requirement
|
||||
end
|
||||
|
||||
def load_bundler!
|
||||
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
||||
|
||||
activate_bundler
|
||||
end
|
||||
|
||||
def activate_bundler
|
||||
gem_error = activation_error_handling do
|
||||
gem "bundler", bundler_requirement
|
||||
end
|
||||
return if gem_error.nil?
|
||||
require_error = activation_error_handling do
|
||||
require "bundler/version"
|
||||
end
|
||||
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
||||
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
||||
exit 42
|
||||
end
|
||||
|
||||
def activation_error_handling
|
||||
yield
|
||||
nil
|
||||
rescue StandardError, LoadError => e
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
m.load_bundler!
|
||||
|
||||
if m.invoked_as_script?
|
||||
load Gem.bin_path("bundler", "bundle")
|
||||
end
|
||||
15
ruby/bin/console
Executable file
15
ruby/bin/console
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/setup"
|
||||
require "extism"
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||
# require "pry"
|
||||
# Pry.start
|
||||
|
||||
require "irb"
|
||||
IRB.start(__FILE__)
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'rake' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("rake", "rake")
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'rufo' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("rufo", "rufo")
|
||||
8
ruby/bin/setup
Executable file
8
ruby/bin/setup
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
set -vx
|
||||
|
||||
bundle install
|
||||
|
||||
# Do any other automated setup that you need to do here
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'yard' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("yard", "yard")
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'yardoc' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("yard", "yardoc")
|
||||
27
ruby/bin/yri
27
ruby/bin/yri
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'yri' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("yard", "yri")
|
||||
@@ -1,17 +1,18 @@
|
||||
require "./lib/extism"
|
||||
require "json"
|
||||
require './lib/extism'
|
||||
require 'json'
|
||||
|
||||
# a Context provides a scope for plugins to be managed within. creating multiple contexts
|
||||
# is expected and groups plugins based on source/tenant/lifetime etc.
|
||||
# We recommend you use `Extism.with_context` unless you have a reason to keep your context around.
|
||||
# If you do you can create a context with `Extism#new`, example: `ctx = Extism.new`
|
||||
Extism.with_context do |ctx|
|
||||
Extism.with_context do |ctx|
|
||||
manifest = {
|
||||
:wasm => [{ :path => "../wasm/code.wasm" }],
|
||||
:wasm => [{:path => "../wasm/code.wasm"}]
|
||||
}
|
||||
|
||||
plugin = ctx.plugin(manifest)
|
||||
res = JSON.parse(plugin.call("count_vowels", ARGV[0] || "this is a test"))
|
||||
|
||||
puts res["count"]
|
||||
|
||||
puts res['count']
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user