ruby_wasm_ic_minimal 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/lib/date.rb +22 -0
- data/lib/encoding_constants.rb +16 -0
- data/lib/ruby_wasm_ic_minimal/guard.rb +24 -0
- data/lib/ruby_wasm_ic_minimal/version.rb +5 -0
- data/lib/ruby_wasm_ic_minimal.rb +10 -0
- data/lib/stringio.rb +270 -0
- data/lib/strscan.rb +197 -0
- metadata +51 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cc18f5b18fe43d51c78dccd18e8065cb1dc22d3ed9dbfcec7e03e34c84cbeb99
|
|
4
|
+
data.tar.gz: 310d256e55b950d1091d0093f90707a189bccde91063171dda295294e4a4a985
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8d1d198fd172fc2fda92b76d7da1e1ed8c3de301cc90b5ccd25f3e661bbf448c3cdb546388b58d8d2d2da040d2501fa5b041d92f8d81896abdac74c5fa5b3ff2
|
|
7
|
+
data.tar.gz: 4173adeef1c2ee9435dcc27bbb1c110f07aaad4a9f4d744737f8f4ce898a7bb4220612c791f935ad809561115c69685679b0ad5b3070f1e2c2b21187e25b738f
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 the ruby_wasm_builder authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ruby_wasm_ic_minimal
|
|
2
|
+
|
|
3
|
+
Pure-Ruby stand-ins for four things a **`minimal`** Ruby canister lacks, for
|
|
4
|
+
canisters packed by [ruby_wasm_builder](https://gitlab.com/phrug/ruby_wasm_builder)
|
|
5
|
+
on its `minimal` Build. That Build links no C extension and defines no
|
|
6
|
+
`Encoding` constant, and Rack and Roda need all four:
|
|
7
|
+
|
|
8
|
+
| file | stands in for | why Rack needs it |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `encoding_constants.rb` | `Encoding::UTF_8`, `US_ASCII`, `ASCII_8BIT` (and `BINARY`, `ASCII`) | it decodes every query parameter as UTF-8 |
|
|
11
|
+
| `stringio.rb` | `StringIO` (a C extension in CRuby) | `rack.input`, `Rack::MockRequest` |
|
|
12
|
+
| `strscan.rb` | `StringScanner` (a C extension) | the multipart parser, loaded by `Rack::Request` |
|
|
13
|
+
| `date.rb` | `date` (its work is the C extension `date_core`) | only because `time` requires it; **a stub** |
|
|
14
|
+
|
|
15
|
+
`stringio.rb` and `strscan.rb` are checked against CRuby's C extensions on
|
|
16
|
+
MRI, operation by operation, and `StringScanner` keeps Ruby 3.3's meaning (a
|
|
17
|
+
`String` in `scan_until` is a `TypeError`). `date.rb` is a stub: Rack only
|
|
18
|
+
formats and reads HTTP dates, which need nothing from `Date`; `Time.parse`,
|
|
19
|
+
`Time.strptime` and `Date` itself raise `NotImplementedError` when called.
|
|
20
|
+
|
|
21
|
+
## Only in a `minimal` canister
|
|
22
|
+
|
|
23
|
+
**Do not put this gem in a Gemfile.** Each file shadows a real library by its
|
|
24
|
+
name, and `date.rb` is a stub. So every file refuses to load anywhere but a
|
|
25
|
+
`minimal` canister, raising `LoadError`. A `minimal` canister is one where the
|
|
26
|
+
builder's wrapper gem has defined `IC::DATA_DIR` and the core knows no more
|
|
27
|
+
than its three built-in encodings (`Encoding.list.size <= 3`). On the `full`
|
|
28
|
+
Build, which links the real libraries and every encoding, it refuses too:
|
|
29
|
+
carry none of it there.
|
|
30
|
+
|
|
31
|
+
## Carrying it
|
|
32
|
+
|
|
33
|
+
Copy its `lib/` into your Application and put it first on the load path,
|
|
34
|
+
before Rack is required:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
gem fetch ruby_wasm_ic_minimal && gem unpack ruby_wasm_ic_minimal-*.gem
|
|
38
|
+
cp -r ruby_wasm_ic_minimal-*/lib my_app/vendor/ruby_wasm_ic_minimal
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# app.rb
|
|
43
|
+
here = __dir__
|
|
44
|
+
$LOAD_PATH.unshift(File.join(here, "vendor/ruby_wasm_ic_minimal"), File.join(here, "vendor/rack"))
|
|
45
|
+
require "encoding_constants"
|
|
46
|
+
require "stringio"
|
|
47
|
+
require "rack"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`require "ruby_wasm_ic_minimal"` loads all four at once. Otherwise Rack's own
|
|
51
|
+
`require "strscan"` and `time`'s `require "date"` find them by name.
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
The gem lives in the ruby_wasm_builder repository, `gems/ruby_wasm_ic_minimal/`. Its
|
|
56
|
+
specs are in that repository's `spec/gems/`, and run with the builder's suite,
|
|
57
|
+
including the 0.7.0 `roda_on_minimal` Script played as `minimal` with this
|
|
58
|
+
gem in place of the Challenge's own `carried/` files.
|
|
59
|
+
|
|
60
|
+
**Not yet observed on a canister:** that `Encoding.list.size` is 3 or less on
|
|
61
|
+
`minimal`. The first deploy that carries this gem checks it; if the guard
|
|
62
|
+
refuses there, the rule is what needs changing.
|
|
63
|
+
|
|
64
|
+
MIT licensed.
|
data/lib/date.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# date.rb (ruby_wasm_ic_minimal): a stand-in for CRuby's `date`, whose work is done by the
|
|
2
|
+
# C extension date_core, which the `minimal` Build does not link. `time`
|
|
3
|
+
# requires `date` at load, and Rack requires `time`, so without this file
|
|
4
|
+
# `require "rack"` fails. Rack only formats and reads HTTP dates
|
|
5
|
+
# (Time#httpdate, Time.httpdate), and those need nothing from Date.
|
|
6
|
+
#
|
|
7
|
+
# What needs a real Date fails here, loudly and only when called:
|
|
8
|
+
# Time.parse and Time.strptime (through Date._parse and Date._strptime), and
|
|
9
|
+
# Date itself. Under `full` this file is not carried.
|
|
10
|
+
require_relative "ruby_wasm_ic_minimal/guard"
|
|
11
|
+
RubyWasmIcMinimal.guard!("date")
|
|
12
|
+
class Date
|
|
13
|
+
def self.unavailable(what)
|
|
14
|
+
raise NotImplementedError, "#{what} needs the date C extension, which the minimal Build lacks"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self._parse(*) = unavailable("Date._parse (Time.parse)")
|
|
18
|
+
def self._strptime(*) = unavailable("Date._strptime (Time.strptime)")
|
|
19
|
+
def self.new(*) = unavailable("Date.new")
|
|
20
|
+
def self.today(*) = unavailable("Date.today")
|
|
21
|
+
def self.parse(*) = unavailable("Date.parse")
|
|
22
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# encoding_constants.rb (ruby_wasm_ic_minimal): the Encoding constants a canister lacks.
|
|
2
|
+
# The `minimal` Build has three encodings, UTF-8, US-ASCII and ASCII-8BIT,
|
|
3
|
+
# and no constant naming any of them (ADR-0060). Rack names
|
|
4
|
+
# Encoding::UTF_8 to decode every query parameter, and `cgi/util` names it as
|
|
5
|
+
# it loads. Each is found here from a literal, without naming a constant:
|
|
6
|
+
# a "\u" escape is always UTF-8, a Symbol's text US-ASCII, String#b binary.
|
|
7
|
+
# Only these three: there is no transcoding to any other (ADR-0053).
|
|
8
|
+
require_relative "ruby_wasm_ic_minimal/guard"
|
|
9
|
+
RubyWasmIcMinimal.guard!("encoding_constants")
|
|
10
|
+
class Encoding
|
|
11
|
+
UTF_8 = "\u0000".encoding unless const_defined?(:UTF_8, false)
|
|
12
|
+
US_ASCII = :a.to_s.encoding unless const_defined?(:US_ASCII, false)
|
|
13
|
+
ASCII_8BIT = "".b.encoding unless const_defined?(:ASCII_8BIT, false)
|
|
14
|
+
BINARY = ASCII_8BIT unless const_defined?(:BINARY, false)
|
|
15
|
+
ASCII = US_ASCII unless const_defined?(:ASCII, false)
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# The guard every stand-in in ruby_wasm_ic_minimal requires first. A stand-in shadows a
|
|
2
|
+
# real library by its name (`date`, `stringio`, `strscan`), and `date.rb` here
|
|
3
|
+
# is a stub, so loading one anywhere but a `minimal` canister would quietly
|
|
4
|
+
# take the real library away. The guard refuses that instead, with LoadError.
|
|
5
|
+
#
|
|
6
|
+
# A `minimal` canister is one where the wrapper gem has defined IC::DATA_DIR
|
|
7
|
+
# (every canister packed by ruby_wasm_builder) and the core knows no more than
|
|
8
|
+
# its three built-in encodings (UTF-8, US-ASCII, ASCII-8BIT). It counts the
|
|
9
|
+
# encodings rather than looking for Encoding::UTF_8, which this gem's own
|
|
10
|
+
# encoding_constants.rb defines. Decided once, at the first stand-in loaded,
|
|
11
|
+
# and kept in RubyWasmIcMinimal::ON_MINIMAL; a test harness may set that constant
|
|
12
|
+
# first (the builder's MRI player does, for `minimal: true`).
|
|
13
|
+
module RubyWasmIcMinimal
|
|
14
|
+
unless const_defined?(:ON_MINIMAL, false)
|
|
15
|
+
ON_MINIMAL = !defined?(IC::DATA_DIR).nil? && Encoding.list.size <= 3
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.guard!(feature)
|
|
19
|
+
return if ON_MINIMAL
|
|
20
|
+
|
|
21
|
+
raise LoadError, "ruby_wasm_ic_minimal's #{feature} is a stand-in only for a minimal canister, and this is not " \
|
|
22
|
+
"one; remove ruby_wasm_ic_minimal from $LOAD_PATH so the real #{feature} loads"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# ruby_wasm_ic_minimal: pure-Ruby stand-ins for what Rack and Roda need and a `minimal`
|
|
2
|
+
# canister lacks, for canisters packed by ruby_wasm_builder on its `minimal`
|
|
3
|
+
# Build only. Requiring this file loads all four; each can be required alone
|
|
4
|
+
# by its library's own name. Each refuses to load anywhere else (see
|
|
5
|
+
# ruby_wasm_ic_minimal/guard.rb).
|
|
6
|
+
require_relative "ruby_wasm_ic_minimal/version"
|
|
7
|
+
require_relative "encoding_constants"
|
|
8
|
+
require_relative "stringio"
|
|
9
|
+
require_relative "strscan"
|
|
10
|
+
require_relative "date"
|
data/lib/stringio.rb
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# stringio.rb (ruby_wasm_ic_minimal): a pure-Ruby StringIO for the `minimal` Build, where
|
|
2
|
+
# CRuby's is a C extension it does not link. Rack names it for request bodies
|
|
3
|
+
# (rack.input) and in Rack::MockRequest; an Application may use it as any
|
|
4
|
+
# in-memory IO.
|
|
5
|
+
#
|
|
6
|
+
# The common reading and writing API, by bytes, with CRuby's meaning. Not
|
|
7
|
+
# here: the encoding conversions (set_encoding only tags), fcntl, and the
|
|
8
|
+
# *_nonblock methods.
|
|
9
|
+
require_relative "ruby_wasm_ic_minimal/guard"
|
|
10
|
+
RubyWasmIcMinimal.guard!("stringio")
|
|
11
|
+
class StringIO
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
14
|
+
attr_reader :string, :lineno
|
|
15
|
+
attr_writer :lineno
|
|
16
|
+
|
|
17
|
+
def self.open(*args)
|
|
18
|
+
io = new(*args)
|
|
19
|
+
return io unless block_given?
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
yield io
|
|
23
|
+
ensure
|
|
24
|
+
io.close
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(string = +"", mode = nil)
|
|
29
|
+
@string = string
|
|
30
|
+
@pos = 0
|
|
31
|
+
@lineno = 0
|
|
32
|
+
@closed_read = @closed_write = false
|
|
33
|
+
@readonly = string.frozen? || mode.to_s.start_with?("r") && !mode.to_s.include?("+")
|
|
34
|
+
@string.replace("") if mode.to_s.start_with?("w") && !@string.frozen?
|
|
35
|
+
@pos = @string.bytesize if mode.to_s.start_with?("a")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def string=(s)
|
|
39
|
+
@string = s
|
|
40
|
+
@pos = 0
|
|
41
|
+
@lineno = 0
|
|
42
|
+
s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def pos = @pos
|
|
46
|
+
alias tell pos
|
|
47
|
+
|
|
48
|
+
def pos=(n)
|
|
49
|
+
raise Errno::EINVAL, "negative position" if n.negative?
|
|
50
|
+
|
|
51
|
+
@pos = n
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def seek(offset, whence = IO::SEEK_SET)
|
|
55
|
+
base = case whence
|
|
56
|
+
when IO::SEEK_SET, :SET then 0
|
|
57
|
+
when IO::SEEK_CUR, :CUR then @pos
|
|
58
|
+
when IO::SEEK_END, :END then @string.bytesize
|
|
59
|
+
else raise Errno::EINVAL, "invalid whence"
|
|
60
|
+
end
|
|
61
|
+
self.pos = base + offset
|
|
62
|
+
0
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rewind
|
|
66
|
+
@pos = 0
|
|
67
|
+
@lineno = 0
|
|
68
|
+
0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def size = @string.bytesize
|
|
72
|
+
alias length size
|
|
73
|
+
def eof? = @pos >= @string.bytesize
|
|
74
|
+
alias eof eof?
|
|
75
|
+
|
|
76
|
+
def read(length = nil, buffer = nil)
|
|
77
|
+
readable!
|
|
78
|
+
if length.nil?
|
|
79
|
+
out = (@string.byteslice(@pos, @string.bytesize - @pos) || "").dup
|
|
80
|
+
@pos = [@pos, @string.bytesize].max
|
|
81
|
+
out.force_encoding(@string.encoding)
|
|
82
|
+
else
|
|
83
|
+
raise ArgumentError, "negative length #{length} given" if length.negative?
|
|
84
|
+
|
|
85
|
+
out = (@string.byteslice(@pos, length) || "").b
|
|
86
|
+
@pos += out.bytesize
|
|
87
|
+
return (buffer&.replace("") && nil) if length.positive? && out.empty?
|
|
88
|
+
end
|
|
89
|
+
buffer ? buffer.replace(out) : out
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def read_nonblock(length, buffer = nil, exception: true)
|
|
93
|
+
out = read(length, buffer)
|
|
94
|
+
return out unless out.nil?
|
|
95
|
+
raise EOFError, "end of file reached" if exception
|
|
96
|
+
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def readpartial(length, buffer = nil)
|
|
101
|
+
out = read(length, buffer)
|
|
102
|
+
raise EOFError, "end of file reached" if out.nil?
|
|
103
|
+
|
|
104
|
+
out
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def getc
|
|
108
|
+
readable!
|
|
109
|
+
return nil if eof?
|
|
110
|
+
|
|
111
|
+
ch = (@string.byteslice(@pos, @string.bytesize - @pos) || "").force_encoding(@string.encoding)[0]
|
|
112
|
+
@pos += ch.bytesize
|
|
113
|
+
ch
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def getbyte
|
|
117
|
+
readable!
|
|
118
|
+
return nil if eof?
|
|
119
|
+
|
|
120
|
+
b = @string.getbyte(@pos)
|
|
121
|
+
@pos += 1
|
|
122
|
+
b
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def each_byte
|
|
126
|
+
return enum_for(:each_byte) unless block_given?
|
|
127
|
+
|
|
128
|
+
while (b = getbyte)
|
|
129
|
+
yield b
|
|
130
|
+
end
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def ungetc(s)
|
|
135
|
+
readable!
|
|
136
|
+
return nil if s.nil?
|
|
137
|
+
|
|
138
|
+
s = s.chr if s.is_a?(Integer)
|
|
139
|
+
if @pos.zero?
|
|
140
|
+
@string.prepend(s)
|
|
141
|
+
else
|
|
142
|
+
@pos -= s.bytesize
|
|
143
|
+
@string.bytesplice(@pos, s.bytesize, s)
|
|
144
|
+
end
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
147
|
+
alias ungetbyte ungetc
|
|
148
|
+
|
|
149
|
+
def gets(sep = $/, limit = nil, chomp: false)
|
|
150
|
+
readable!
|
|
151
|
+
sep, limit = nil, sep if sep.is_a?(Integer)
|
|
152
|
+
return nil if eof?
|
|
153
|
+
|
|
154
|
+
rest = (@string.byteslice(@pos, @string.bytesize - @pos) || "").force_encoding(@string.encoding)
|
|
155
|
+
line =
|
|
156
|
+
if sep.nil?
|
|
157
|
+
rest
|
|
158
|
+
elsif sep.empty?
|
|
159
|
+
rest.sub!(/\A\n+/) { |nl| @pos += nl.bytesize; "" }
|
|
160
|
+
(i = rest.index(/\n\n+/)) ? rest[0, i] + rest[i..][/\A\n+/] : rest
|
|
161
|
+
else
|
|
162
|
+
(i = rest.index(sep)) ? rest[0, i + sep.length] : rest
|
|
163
|
+
end
|
|
164
|
+
line = line.byteslice(0, limit) if limit && limit >= 0 && line.bytesize > limit
|
|
165
|
+
@pos += line.bytesize
|
|
166
|
+
@lineno += 1
|
|
167
|
+
$_ = chomp && sep ? line.chomp(sep.empty? ? "\n" : sep) : line
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def readline(*args, **kw) = gets(*args, **kw) || raise(EOFError, "end of file reached")
|
|
171
|
+
|
|
172
|
+
def each_line(sep = $/, limit = nil, chomp: false)
|
|
173
|
+
return enum_for(:each_line, sep, limit, chomp: chomp) unless block_given?
|
|
174
|
+
|
|
175
|
+
while (line = gets(sep, limit, chomp: chomp))
|
|
176
|
+
yield line
|
|
177
|
+
end
|
|
178
|
+
self
|
|
179
|
+
end
|
|
180
|
+
alias each each_line
|
|
181
|
+
|
|
182
|
+
def readlines(*args, **kw) = each_line(*args, **kw).to_a
|
|
183
|
+
|
|
184
|
+
def write(*objs)
|
|
185
|
+
writable!
|
|
186
|
+
objs.sum do |obj|
|
|
187
|
+
s = obj.to_s
|
|
188
|
+
if @pos > @string.bytesize
|
|
189
|
+
@string << ("\0" * (@pos - @string.bytesize))
|
|
190
|
+
end
|
|
191
|
+
@string.bytesplice(@pos, [s.bytesize, @string.bytesize - @pos].min, s.b.force_encoding(@string.encoding))
|
|
192
|
+
@pos += s.bytesize
|
|
193
|
+
s.bytesize
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
alias syswrite write
|
|
197
|
+
alias write_nonblock write
|
|
198
|
+
|
|
199
|
+
def <<(obj)
|
|
200
|
+
write(obj)
|
|
201
|
+
self
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def print(*objs)
|
|
205
|
+
objs = [$_] if objs.empty?
|
|
206
|
+
objs.each { write(_1) }
|
|
207
|
+
write($\) if $\
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def puts(*objs)
|
|
212
|
+
return write("\n") && nil if objs.empty?
|
|
213
|
+
|
|
214
|
+
objs.flatten.each do |o|
|
|
215
|
+
s = o.nil? ? "" : o.to_s
|
|
216
|
+
write(s.end_with?("\n") ? s : "#{s}\n")
|
|
217
|
+
end
|
|
218
|
+
nil
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def printf(fmt, *args) = write(format(fmt, *args)) && nil
|
|
222
|
+
def putc(c) = (write(c.is_a?(Integer) ? (c & 0xff).chr : c.to_s[0]) && c)
|
|
223
|
+
|
|
224
|
+
def truncate(n)
|
|
225
|
+
writable!
|
|
226
|
+
@string.replace(n < @string.bytesize ? @string.byteslice(0, n) : @string + ("\0" * (n - @string.bytesize)))
|
|
227
|
+
0
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def close
|
|
231
|
+
@closed_read = @closed_write = true
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def close_read = (@closed_read = true) && nil
|
|
236
|
+
def close_write = (@closed_write = true) && nil
|
|
237
|
+
def closed? = @closed_read && @closed_write
|
|
238
|
+
def closed_read? = @closed_read
|
|
239
|
+
def closed_write? = @closed_write
|
|
240
|
+
|
|
241
|
+
def binmode
|
|
242
|
+
@string.force_encoding("".b.encoding) unless @string.frozen?
|
|
243
|
+
self
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def set_encoding(*) = self
|
|
247
|
+
def external_encoding = @string.encoding
|
|
248
|
+
def internal_encoding = nil
|
|
249
|
+
def sync = true
|
|
250
|
+
def sync=(value)
|
|
251
|
+
value
|
|
252
|
+
end
|
|
253
|
+
def flush = self
|
|
254
|
+
def fsync = 0
|
|
255
|
+
def fileno = nil
|
|
256
|
+
def isatty = false
|
|
257
|
+
alias tty? isatty
|
|
258
|
+
def pid = nil
|
|
259
|
+
def fcntl(*) = raise(NotImplementedError, "fcntl")
|
|
260
|
+
|
|
261
|
+
private
|
|
262
|
+
|
|
263
|
+
def readable!
|
|
264
|
+
raise IOError, "not opened for reading" if @closed_read
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def writable!
|
|
268
|
+
raise IOError, "not opened for writing" if @closed_write || @readonly
|
|
269
|
+
end
|
|
270
|
+
end
|
data/lib/strscan.rb
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# strscan.rb (ruby_wasm_ic_minimal): a pure-Ruby StringScanner for the `minimal` Build,
|
|
2
|
+
# where CRuby's is a C extension it does not link. Rack's multipart parser
|
|
3
|
+
# requires it at load, so Rack::Request cannot load without it, and it is what
|
|
4
|
+
# reads a multipart body.
|
|
5
|
+
#
|
|
6
|
+
# The common API, with CRuby's meaning: positions are byte offsets, a pattern
|
|
7
|
+
# is matched only at the scan pointer (scan, skip, match?, check; a Regexp
|
|
8
|
+
# or a String) or anywhere after it (*_until, exist?; a Regexp only, as in
|
|
9
|
+
# Ruby 3.3). As in CRuby's default mode, the text begins at the pointer: \A
|
|
10
|
+
# and ^ match there, which Rack's boundary pattern depends on. Not here: the
|
|
11
|
+
# fixed_anchor: mode, named_captures, scan_byte and peek_byte. Checked
|
|
12
|
+
# against CRuby's on MRI, operation by operation.
|
|
13
|
+
require_relative "ruby_wasm_ic_minimal/guard"
|
|
14
|
+
RubyWasmIcMinimal.guard!("strscan")
|
|
15
|
+
class StringScanner
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
attr_reader :string
|
|
19
|
+
|
|
20
|
+
def initialize(string, _dup = false, fixed_anchor: false)
|
|
21
|
+
raise ArgumentError, "fixed_anchor: true is not supported here" if fixed_anchor
|
|
22
|
+
|
|
23
|
+
@string = string
|
|
24
|
+
@pos = 0
|
|
25
|
+
clear
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def string=(s)
|
|
29
|
+
@string = s
|
|
30
|
+
@pos = 0
|
|
31
|
+
clear
|
|
32
|
+
s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def concat(s)
|
|
36
|
+
@string << s
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
alias << concat
|
|
40
|
+
|
|
41
|
+
def pos = @pos
|
|
42
|
+
|
|
43
|
+
def pos=(n)
|
|
44
|
+
n += @string.bytesize if n.negative?
|
|
45
|
+
raise RangeError, "index out of range" unless n.between?(0, @string.bytesize)
|
|
46
|
+
|
|
47
|
+
@pos = n
|
|
48
|
+
end
|
|
49
|
+
alias pointer pos
|
|
50
|
+
alias pointer= pos=
|
|
51
|
+
|
|
52
|
+
def charpos = @string.byteslice(0, @pos).length
|
|
53
|
+
|
|
54
|
+
def reset
|
|
55
|
+
@pos = 0
|
|
56
|
+
clear
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def terminate
|
|
61
|
+
@pos = @string.bytesize
|
|
62
|
+
clear
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
alias clear_terminate terminate
|
|
66
|
+
|
|
67
|
+
def eos? = @pos >= @string.bytesize
|
|
68
|
+
def rest = @string.byteslice(@pos, @string.bytesize - @pos) || ""
|
|
69
|
+
def rest? = !eos?
|
|
70
|
+
def rest_size = @string.bytesize - @pos
|
|
71
|
+
def beginning_of_line? = @pos.zero? || @string.getbyte(@pos - 1) == 10
|
|
72
|
+
alias bol? beginning_of_line?
|
|
73
|
+
|
|
74
|
+
# At the pointer: advance, return the match.
|
|
75
|
+
def scan(pattern) = go(pattern, anchored: true, advance: true, string: true)
|
|
76
|
+
# At the pointer: advance, return its length.
|
|
77
|
+
def skip(pattern) = go(pattern, anchored: true, advance: true, string: false)
|
|
78
|
+
# At the pointer: do not advance, return its length.
|
|
79
|
+
def match?(pattern) = go(pattern, anchored: true, advance: false, string: false)
|
|
80
|
+
# At the pointer: do not advance, return the match.
|
|
81
|
+
def check(pattern) = go(pattern, anchored: true, advance: false, string: true)
|
|
82
|
+
# Anywhere after it: advance past the match, return what was passed over.
|
|
83
|
+
def scan_until(pattern) = go(pattern, anchored: false, advance: true, string: true)
|
|
84
|
+
def skip_until(pattern) = go(pattern, anchored: false, advance: true, string: false)
|
|
85
|
+
def check_until(pattern) = go(pattern, anchored: false, advance: false, string: true)
|
|
86
|
+
def exist?(pattern) = go(pattern, anchored: false, advance: false, string: false)
|
|
87
|
+
|
|
88
|
+
def search_full(pattern, advance, return_string)
|
|
89
|
+
go(pattern, anchored: false, advance: advance, string: return_string)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def scan_full(pattern, advance, return_string)
|
|
93
|
+
go(pattern, anchored: true, advance: advance, string: return_string)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def getch
|
|
97
|
+
return nil if eos?
|
|
98
|
+
|
|
99
|
+
ch = rest[0]
|
|
100
|
+
set_match(@pos, @pos + ch.bytesize, nil)
|
|
101
|
+
@pos += ch.bytesize
|
|
102
|
+
ch
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def get_byte
|
|
106
|
+
return nil if eos?
|
|
107
|
+
|
|
108
|
+
b = @string.byteslice(@pos, 1)
|
|
109
|
+
set_match(@pos, @pos + 1, nil)
|
|
110
|
+
@pos += 1
|
|
111
|
+
b
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def peek(n) = @string.byteslice(@pos, n) || ""
|
|
115
|
+
|
|
116
|
+
def unscan
|
|
117
|
+
raise Error, "unscan error: nothing to unscan" unless @last
|
|
118
|
+
|
|
119
|
+
@pos = @last
|
|
120
|
+
clear
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def matched? = !@m_end.nil?
|
|
125
|
+
def matched = matched? ? @string.byteslice(@m_start, @m_end - @m_start) : nil
|
|
126
|
+
def matched_size = matched? ? @m_end - @m_start : nil
|
|
127
|
+
def pre_match = matched? ? @string.byteslice(0, @m_start) : nil
|
|
128
|
+
def post_match = matched? ? @string.byteslice(@m_end, @string.bytesize - @m_end) : nil
|
|
129
|
+
|
|
130
|
+
def [](i)
|
|
131
|
+
return nil unless matched?
|
|
132
|
+
return (i.zero? ? matched : nil) unless @md
|
|
133
|
+
|
|
134
|
+
@md[i]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def size = matched? ? (@md ? @md.size : 1) : nil
|
|
138
|
+
def captures = @md ? @md.captures : (matched? ? [] : nil)
|
|
139
|
+
def values_at(*is) = matched? ? is.map { self[_1] } : nil
|
|
140
|
+
|
|
141
|
+
def inspect
|
|
142
|
+
return "#<StringScanner fin>" if eos?
|
|
143
|
+
|
|
144
|
+
"#<StringScanner #{@pos}/#{@string.bytesize} @ #{peek(5).inspect}>"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def clear
|
|
150
|
+
@m_start = @m_end = @md = @last = nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def set_match(from, to, md)
|
|
154
|
+
@last = @pos
|
|
155
|
+
@m_start = from
|
|
156
|
+
@m_end = to
|
|
157
|
+
@md = md
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def go(pattern, anchored:, advance:, string:)
|
|
161
|
+
start = @pos
|
|
162
|
+
if pattern.is_a?(String)
|
|
163
|
+
# As Ruby 3.3's strscan: a String only where the match is at the pointer.
|
|
164
|
+
raise TypeError, "wrong argument type String (expected Regexp)" unless anchored
|
|
165
|
+
return fail_match unless @string.byteslice(start, pattern.bytesize) == pattern
|
|
166
|
+
|
|
167
|
+
from = start
|
|
168
|
+
to = from + pattern.bytesize
|
|
169
|
+
md = nil
|
|
170
|
+
else
|
|
171
|
+
# As CRuby's (fixed_anchor: false): the text begins at the pointer, so
|
|
172
|
+
# \A and ^ match there and nothing before it is seen.
|
|
173
|
+
md = regexp(pattern, anchored).match(rest)
|
|
174
|
+
return fail_match unless md
|
|
175
|
+
|
|
176
|
+
from = start + md.byteoffset(0)[0]
|
|
177
|
+
to = start + md.byteoffset(0)[1]
|
|
178
|
+
end
|
|
179
|
+
set_match(from, to, md)
|
|
180
|
+
@pos = to if advance
|
|
181
|
+
length = to - start
|
|
182
|
+
return length unless string
|
|
183
|
+
|
|
184
|
+
anchored ? @string.byteslice(from, to - from) : @string.byteslice(start, length)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def fail_match
|
|
188
|
+
clear
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def regexp(re, anchored)
|
|
193
|
+
return re unless anchored
|
|
194
|
+
|
|
195
|
+
(@anchored ||= {})[re] ||= Regexp.new("\\A(?:#{re.source})", re.options)
|
|
196
|
+
end
|
|
197
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby_wasm_ic_minimal
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- graial
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: This gem contains the four carried files required for running rack and
|
|
13
|
+
roda on the minimal ruby.wasm build.
|
|
14
|
+
email:
|
|
15
|
+
- graial@paglalayag.net
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/date.rb
|
|
23
|
+
- lib/encoding_constants.rb
|
|
24
|
+
- lib/ruby_wasm_ic_minimal.rb
|
|
25
|
+
- lib/ruby_wasm_ic_minimal/guard.rb
|
|
26
|
+
- lib/ruby_wasm_ic_minimal/version.rb
|
|
27
|
+
- lib/stringio.rb
|
|
28
|
+
- lib/strscan.rb
|
|
29
|
+
homepage: https://github.com/graial/ruby_wasm_ic_minimal
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata: {}
|
|
33
|
+
rdoc_options: []
|
|
34
|
+
require_paths:
|
|
35
|
+
- lib
|
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 3.3.3
|
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
requirements: []
|
|
47
|
+
rubygems_version: 3.6.7
|
|
48
|
+
specification_version: 4
|
|
49
|
+
summary: Four carried files required for running rack and roda on the minimal ruby.wasm
|
|
50
|
+
build
|
|
51
|
+
test_files: []
|