coreutils-wasm 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a271d6f528a786685d21a08bd7f54d8004a98ddc879a1aa238e0a3cec7abe470
4
- data.tar.gz: e4e9b233ee13a2d2ab4bb771a638fb08d633a017ec9aab87f9b329d7025c9da2
3
+ metadata.gz: d65dbb6cb28564275a6808bc394d13e2d5003bc0845f08759da2ced0f7f057c0
4
+ data.tar.gz: d46ae0ebf88487d167b5e19269b342787c2264860393f603b1160de5a92ffbbf
5
5
  SHA512:
6
- metadata.gz: b4f1c045e53050c8a445fd30320e77ed0ebfb058d10a633e42ed2a2d67f8ed9dd887a0f3f29e2a9194e1f71a7f532dace6c37e21b4c9ac3937b7e7c6b301115a
7
- data.tar.gz: b46ed7493b0e048c46482fdea77d413fd35e2b9af7cba6719982cafcf1af60146ebc84274aa8b184c7afaa1e7f62ec72f486f8fd861011dbe46cb7c884fc440f
6
+ metadata.gz: 2c5554269a3c5d9dca5425705077fb0e763398afc516cc00b38d31a2b07816c774e07ca42e8d3e3270247b5524abf8d887c7fc28adf41beba482603261fc0b89
7
+ data.tar.gz: e69fb35837ebd9c810517698156784e2b2980a4346bf500f3598b0d6199f5cfa389a85be75b61f246f120f334fdfc738f917bb90ad9e8e636d24d934e04e9acc
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :development do
8
+ gem "rake", "~> 13.0"
9
+ gem "minitest", "~> 5.0"
10
+ end
data/README.md CHANGED
@@ -1,10 +1,20 @@
1
1
  # CoreutilsWasm
2
2
 
3
- GNU Coreutils compiled to WebAssembly.
3
+ GNU Coreutils compiled to WebAssembly, wrapped as a Ruby gem.
4
4
 
5
- This gem provides the WASM binary for GNU coreutils. It includes 100+ commands like `ls`, `cat`, `head`, `tail`, `wc`, `cp`, `mv`, `rm`, etc.
5
+ This gem downloads and runs a WASM binary containing 100+ GNU coreutils commands (`ls`, `cat`, `head`, `tail`, `wc`, `cp`, `mv`, `rm`, etc.) via a WASI runtime like [wasmtime](https://wasmtime.dev/) or [wasmer](https://wasmer.io/).
6
6
 
7
- **This gem only provides the WASM binary.** You choose how to run it with your preferred WebAssembly runtime.
7
+ ## Prerequisites
8
+
9
+ You need a WASI-compatible runtime installed on your system. The gem defaults to `wasmtime`.
10
+
11
+ ```bash
12
+ # Option 1: Install wasmtime (default)
13
+ curl https://wasmtime.dev/install.sh -sSf | bash
14
+
15
+ # Option 2: Install wasmer
16
+ curl https://get.wasmer.io -sSfL | sh
17
+ ```
8
18
 
9
19
  ## Installation
10
20
 
@@ -14,7 +24,7 @@ Add this line to your application's Gemfile:
14
24
  gem 'coreutils-wasm'
15
25
  ```
16
26
 
17
- And then execute:
27
+ Then execute:
18
28
 
19
29
  ```bash
20
30
  bundle install
@@ -28,94 +38,118 @@ gem install coreutils-wasm
28
38
 
29
39
  ## Usage
30
40
 
31
- ### Get the WASM binary path or bytes
41
+ ### Quick start
32
42
 
33
43
  ```ruby
34
44
  require 'coreutils_wasm'
35
45
 
36
- # Get the file path (useful for CLI runtimes)
37
- wasm_path = CoreutilsWasm.wasm_path
38
- # => "/path/to/gems/coreutils-wasm-1.0.0/wasm/coreutils.wasm"
46
+ # 1. Download the WASM binary (only needed once)
47
+ CoreutilsWasm.download_to_binary_path!
48
+
49
+ # 2. Run a command
50
+ result = CoreutilsWasm.run('ls', '-la', wasm_dir: '.')
51
+ puts result[:stdout]
52
+ ```
39
53
 
40
- # Get as binary string
41
- wasm_bytes = CoreutilsWasm.wasm_bytes
54
+ ### Download the binary
42
55
 
43
- # Get file size
44
- wasm_size = CoreutilsWasm.wasm_size
45
- # => 4705003
56
+ The `.wasm` binary is not bundled with the gem. It is downloaded from GitHub Releases on first use.
57
+
58
+ ```ruby
59
+ # Download to the default location (inside the gem directory)
60
+ CoreutilsWasm.download_to_binary_path!
46
61
 
47
- # Check if file exists
48
- CoreutilsWasm.wasm_exists?
62
+ # Check if the binary is available
63
+ CoreutilsWasm.available?
49
64
  # => true
50
65
  ```
51
66
 
52
- ### List available commands
67
+ ### Run commands
68
+
69
+ Once the binary is downloaded, use `run` to execute any coreutils command. Arguments are passed through to the WASM binary.
53
70
 
54
71
  ```ruby
55
- CoreutilsWasm.commands
56
- # => ["arch", "base32", "base64", "basename", "cat", "chmod", "cp", "ls", ...]
57
- ```
72
+ # List files
73
+ result = CoreutilsWasm.run('ls', '-la', wasm_dir: '.')
74
+ puts result[:stdout]
58
75
 
59
- ## Running with different runtimes
76
+ # Read a file
77
+ result = CoreutilsWasm.run('cat', 'Gemfile', wasm_dir: '.')
78
+ puts result[:stdout]
60
79
 
61
- ### With Wasmer CLI
80
+ # Word count
81
+ result = CoreutilsWasm.run('wc', '-l', 'README.md', wasm_dir: '.')
82
+ puts result[:stdout]
62
83
 
63
- ```bash
64
- # Install wasmer
65
- curl https://get.wasmer.io -sSfL | sh
66
-
67
- # Run a command
68
- wasmer run $(ruby -r coreutils_wasm -e "puts CoreutilsWasm.wasm_path") -- ls -la
84
+ # Sort input (via file)
85
+ result = CoreutilsWasm.run('sort', 'names.txt', wasm_dir: '/path/to/data')
86
+ puts result[:stdout]
69
87
  ```
70
88
 
71
- ### With Wasmtime CLI
72
-
73
- ```bash
74
- # Install wasmtime
75
- curl https://wasmtime.dev/install.sh -sSf | bash
89
+ The `run` method returns a hash:
76
90
 
77
- # Run a command
78
- wasmtime $(ruby -r coreutils_wasm -e "puts CoreutilsWasm.wasm_path") -- ls -la
91
+ ```ruby
92
+ {
93
+ stdout: "...", # Standard output
94
+ stderr: "...", # Standard error
95
+ success: true # Whether the command succeeded
96
+ }
79
97
  ```
80
98
 
81
- ### With wasmer gem (Ruby runtime)
99
+ The `wasm_dir` parameter controls which host directory is mounted into the WASI sandbox (passed as `--dir` to the runtime). It defaults to `"."`.
100
+
101
+ ### Configuration
82
102
 
83
103
  ```ruby
84
- require 'wasmer'
85
- require 'coreutils_wasm'
104
+ # Change the path where the .wasm binary is stored
105
+ CoreutilsWasm.binary_path = '/opt/wasm/coreutils.wasm'
86
106
 
87
- # Load the WASM binary
88
- wasm_bytes = CoreutilsWasm.wasm_bytes
89
- store = Wasmer::Store.new
90
- module_ = Wasmer::Module.new store, wasm_bytes
107
+ # Change the WASI runtime (default: "wasmtime")
108
+ CoreutilsWasm.runtime = 'wasmer'
91
109
 
92
- # Create WASI environment
93
- wasi_env = Wasmer::Wasi::StateBuilder.new("ls")
94
- .argument("-la")
95
- .preopen_directory(".")
96
- .finalize
110
+ # Check the current binary path
111
+ CoreutilsWasm.binary_path
112
+ # => "/opt/wasm/coreutils.wasm"
97
113
 
98
- # Instantiate with WASI imports
99
- import_object = wasi_env.generate_import_object(store, module_)
100
- instance = Wasmer::Instance.new(module_, import_object)
114
+ # Check the current runtime
115
+ CoreutilsWasm.runtime
116
+ # => "wasmer"
117
+ ```
118
+
119
+ ### List available commands
101
120
 
102
- # Run
103
- wasi_env.start(instance)
121
+ ```ruby
122
+ CoreutilsWasm.commands
123
+ # => ["arch", "base32", "base64", "basename", "cat", "chmod", "cp", "ls", ...]
104
124
  ```
105
125
 
106
- ### With shell execution
126
+ ### Error handling
107
127
 
108
128
  ```ruby
109
- require 'coreutils_wasm'
129
+ # BinaryNotFound -- raised when running before downloading
130
+ begin
131
+ CoreutilsWasm.run('ls')
132
+ rescue CoreutilsWasm::BinaryNotFound => e
133
+ puts "Binary missing: #{e.message}"
134
+ CoreutilsWasm.download_to_binary_path!
135
+ retry
136
+ end
110
137
 
111
- # Simple shell execution with wasmer
112
- def run_coreutils(command, *args)
113
- wasm = CoreutilsWasm.wasm_path
114
- `wasmer run #{wasm} -- #{command} #{args.join(' ')}`
138
+ # ExecutionError -- raised on non-zero exit code
139
+ begin
140
+ CoreutilsWasm.run('cat', 'nonexistent_file', wasm_dir: '.')
141
+ rescue CoreutilsWasm::ExecutionError => e
142
+ puts "Command failed: #{e.message}"
115
143
  end
144
+ ```
145
+
146
+ All errors inherit from `CoreutilsWasm::Error`:
116
147
 
117
- puts run_coreutils('ls', '-la')
118
- puts run_coreutils('cat', 'Gemfile')
148
+ ```
149
+ StandardError
150
+ └── CoreutilsWasm::Error
151
+ ├── CoreutilsWasm::BinaryNotFound
152
+ └── CoreutilsWasm::ExecutionError
119
153
  ```
120
154
 
121
155
  ## Available Commands
@@ -128,7 +162,18 @@ This binary includes all GNU coreutils commands:
128
162
 
129
163
  ## Development
130
164
 
131
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
165
+ After checking out the repo, install dependencies:
166
+
167
+ ```bash
168
+ cd ruby-gem
169
+ bundle install
170
+ ```
171
+
172
+ Run the test suite:
173
+
174
+ ```bash
175
+ bundle exec rake test
176
+ ```
132
177
 
133
178
  ## Contributing
134
179
 
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'test'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/coreutils_wasm/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "coreutils-wasm"
7
+ spec.version = CoreutilsWasm::VERSION
8
+ spec.authors = ["Nathan Himpens"]
9
+ spec.email = ["nathan@example.com"]
10
+
11
+ spec.summary = "GNU Coreutils compiled to WebAssembly for Ruby"
12
+ spec.description = "Provides GNU coreutils commands (ls, cat, head, tail, wc, etc.) " \
13
+ "running in a WebAssembly sandbox via a WASI runtime (wasmtime, wasmer, etc.)."
14
+ spec.homepage = "https://github.com/NathanHimpens/coreutils-wasm"
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 2.7.0"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match?(%r{\A(test|spec|features|\.github|patches|agents)/}) ||
24
+ f.match?(%r{\A(\.gitignore|\.rubocop|AGENTS\.md)})
25
+ end
26
+ spec.require_paths = ["lib"]
27
+
28
+ # No external runtime dependencies — only Ruby stdlib.
29
+
30
+ spec.post_install_message = <<~MSG
31
+ ============================================================
32
+ coreutils-wasm installed!
33
+
34
+ The .wasm binary is NOT bundled with this gem.
35
+ Download it on first use:
36
+
37
+ require 'coreutils_wasm'
38
+ CoreutilsWasm.download_to_binary_path!
39
+
40
+ Or it will be downloaded automatically when needed.
41
+ A WASI runtime (wasmtime, wasmer, etc.) must be installed.
42
+ ============================================================
43
+ MSG
44
+
45
+ # Development dependencies
46
+ spec.add_development_dependency "bundler", "~> 2.0"
47
+ spec.add_development_dependency "minitest", "~> 5.0"
48
+ spec.add_development_dependency "rake", "~> 13.0"
49
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'fileutils'
6
+ require 'uri'
7
+
8
+ module CoreutilsWasm
9
+ class Downloader
10
+ REPO_OWNER = 'NathanHimpens'
11
+ REPO_NAME = 'coreutils-wasm'
12
+ ASSET_NAME = 'coreutils.wasm'
13
+
14
+ class << self
15
+ def download(to:)
16
+ target = File.expand_path(to)
17
+ FileUtils.mkdir_p(File.dirname(target))
18
+
19
+ begin
20
+ tag = get_latest_release_tag
21
+ download_asset(tag, target)
22
+ FileUtils.chmod(0o755, target)
23
+ true
24
+ rescue StandardError => e
25
+ FileUtils.rm_f(target)
26
+ warn "Download failed: #{e.message}"
27
+ raise
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def get_latest_release_tag
34
+ uri = URI("https://api.github.com/repos/#{REPO_OWNER}/#{REPO_NAME}/releases/latest")
35
+ response = Net::HTTP.get(uri)
36
+ data = JSON.parse(response)
37
+ data['tag_name']
38
+ end
39
+
40
+ def download_asset(tag, target)
41
+ uri = URI("https://github.com/#{REPO_OWNER}/#{REPO_NAME}/releases/download/#{tag}/#{ASSET_NAME}")
42
+
43
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
44
+ request = Net::HTTP::Get.new(uri)
45
+ http.request(request) do |response|
46
+ if response.is_a?(Net::HTTPRedirection)
47
+ return download_from_uri(URI(response['location']), target)
48
+ end
49
+
50
+ File.open(target, 'wb') do |file|
51
+ response.read_body do |chunk|
52
+ file.write(chunk)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def download_from_uri(uri, target)
60
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
61
+ request = Net::HTTP::Get.new(uri)
62
+ http.request(request) do |response|
63
+ File.open(target, 'wb') do |file|
64
+ response.read_body do |chunk|
65
+ file.write(chunk)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module CoreutilsWasm
6
+ class Runner
7
+ class << self
8
+ def run(*args, wasm_dir: '.')
9
+ binary = CoreutilsWasm.binary_path
10
+
11
+ raise CoreutilsWasm::BinaryNotFound, "WASM binary not found at #{binary}" unless File.exist?(binary)
12
+
13
+ cmd = [
14
+ CoreutilsWasm.runtime,
15
+ 'run',
16
+ '--dir', wasm_dir,
17
+ binary,
18
+ *args
19
+ ]
20
+
21
+ stdout, stderr, status = Open3.capture3(*cmd)
22
+
23
+ unless status.success?
24
+ raise CoreutilsWasm::ExecutionError,
25
+ "Command exited with status #{status.exitstatus}: #{stderr}"
26
+ end
27
+
28
+ { stdout: stdout, stderr: stderr, success: true }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CoreutilsWasm
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -1,41 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "coreutils_wasm/version"
4
- require_relative "coreutils_wasm/commands"
3
+ require_relative 'coreutils_wasm/version'
4
+ require_relative 'coreutils_wasm/downloader'
5
+ require_relative 'coreutils_wasm/runner'
6
+ require_relative 'coreutils_wasm/commands'
5
7
 
6
- # CoreutilsWasm provides GNU coreutils WASM binaries
7
- #
8
- # This gem only provides the WASM binary. You choose how to run it
9
- # with your preferred WebAssembly runtime (wasmer, wasmtime, etc.)
10
8
  module CoreutilsWasm
11
9
  class Error < StandardError; end
10
+ class BinaryNotFound < Error; end
11
+ class ExecutionError < Error; end
12
+
13
+ DEFAULT_BINARY_PATH = File.join(File.dirname(__FILE__), 'coreutils_wasm', 'coreutils.wasm').freeze
12
14
 
13
15
  class << self
14
- # Get the absolute path to the coreutils WASM binary
15
- # @return [String] Absolute path to coreutils.wasm
16
- def wasm_path
17
- @wasm_path ||= File.expand_path("../wasm/coreutils.wasm", __dir__)
16
+ attr_writer :binary_path, :runtime
17
+
18
+ def binary_path
19
+ @binary_path || DEFAULT_BINARY_PATH
20
+ end
21
+
22
+ def runtime
23
+ @runtime || 'wasmtime'
18
24
  end
19
25
 
20
- # Get the raw WASM binary as a string of bytes
21
- # @return [String] Binary string containing the WASM binary
22
- def wasm_bytes
23
- File.binread(wasm_path)
26
+ def download_to_binary_path!
27
+ Downloader.download(to: binary_path)
24
28
  end
25
29
 
26
- # Get the WASM file size in bytes
27
- # @return [Integer] Size in bytes
28
- def wasm_size
29
- File.size(wasm_path)
30
+ def run(*args, wasm_dir: '.')
31
+ Runner.run(*args, wasm_dir: wasm_dir)
30
32
  end
31
33
 
32
- # Check if the WASM binary exists
33
- # @return [Boolean]
34
- def wasm_exists?
35
- File.exist?(wasm_path)
34
+ def available?
35
+ File.exist?(binary_path)
36
36
  end
37
37
 
38
- # List of all available commands in this binary
38
+ # List of all available commands in this binary (coreutils-specific convenience)
39
39
  # @return [Array<String>]
40
40
  def commands
41
41
  Commands::ALL
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coreutils-wasm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Himpens
@@ -24,59 +24,50 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: rake
27
+ name: minitest
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '13.0'
32
+ version: '5.0'
33
33
  type: :development
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '13.0'
39
+ version: '5.0'
40
40
  - !ruby/object:Gem::Dependency
41
- name: rspec
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '3.0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '3.0'
54
- - !ruby/object:Gem::Dependency
55
- name: rubocop
41
+ name: rake
56
42
  requirement: !ruby/object:Gem::Requirement
57
43
  requirements:
58
44
  - - "~>"
59
45
  - !ruby/object:Gem::Version
60
- version: '1.0'
46
+ version: '13.0'
61
47
  type: :development
62
48
  prerelease: false
63
49
  version_requirements: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
- version: '1.0'
53
+ version: '13.0'
68
54
  description: Provides GNU coreutils commands (ls, cat, head, tail, wc, etc.) running
69
- in a WebAssembly sandbox via Wasmer CLI.
55
+ in a WebAssembly sandbox via a WASI runtime (wasmtime, wasmer, etc.).
70
56
  email:
71
57
  - nathan@example.com
72
58
  executables: []
73
59
  extensions: []
74
60
  extra_rdoc_files: []
75
61
  files:
62
+ - Gemfile
76
63
  - LICENSE
77
64
  - README.md
65
+ - Rakefile
66
+ - coreutils-wasm.gemspec
78
67
  - lib/coreutils_wasm.rb
79
68
  - lib/coreutils_wasm/commands.rb
69
+ - lib/coreutils_wasm/downloader.rb
70
+ - lib/coreutils_wasm/runner.rb
80
71
  - lib/coreutils_wasm/version.rb
81
72
  - wasm/coreutils.wasm
82
73
  homepage: https://github.com/NathanHimpens/coreutils-wasm
@@ -86,6 +77,19 @@ metadata:
86
77
  homepage_uri: https://github.com/NathanHimpens/coreutils-wasm
87
78
  source_code_uri: https://github.com/NathanHimpens/coreutils-wasm
88
79
  changelog_uri: https://github.com/NathanHimpens/coreutils-wasm/blob/main/CHANGELOG.md
80
+ post_install_message: |
81
+ ============================================================
82
+ coreutils-wasm installed!
83
+
84
+ The .wasm binary is NOT bundled with this gem.
85
+ Download it on first use:
86
+
87
+ require 'coreutils_wasm'
88
+ CoreutilsWasm.download_to_binary_path!
89
+
90
+ Or it will be downloaded automatically when needed.
91
+ A WASI runtime (wasmtime, wasmer, etc.) must be installed.
92
+ ============================================================
89
93
  rdoc_options: []
90
94
  require_paths:
91
95
  - lib