wtf8 0.0.1 → 0.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 +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +128 -19
- data/lib/wtf8/cesu8.rb +50 -0
- data/lib/wtf8/codec.rb +170 -0
- data/lib/wtf8/errors.rb +7 -0
- data/lib/wtf8/modified_utf8.rb +18 -0
- data/lib/wtf8/string.rb +148 -0
- data/lib/wtf8/surrogates.rb +40 -0
- data/lib/wtf8/version.rb +1 -1
- data/lib/wtf8.rb +32 -0
- data/sig/wtf8/cesu8.rbs +11 -0
- data/sig/wtf8/codec.rbs +57 -0
- data/sig/wtf8/errors.rbs +12 -0
- data/sig/wtf8/modified_utf8.rbs +15 -0
- data/sig/wtf8/string.rbs +107 -0
- data/sig/wtf8/surrogates.rbs +32 -0
- data/sig/wtf8/version.rbs +5 -0
- data/sig/wtf8.rbs +29 -3
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7ce5d331e0d651f4c9459fed8f598edeb395f7a1edcecba524db6c08bfebfb1
|
|
4
|
+
data.tar.gz: b859dc0c6c5642d8c802486318e6b71c8e660cf716a4a3b29936c7ccfe5dc410
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 956122b0ec988b861939aaf643ed7b5b62bbdf9fb7802216784414316d89ca70ef0811b4ed6225216f137f84ea2c2483816933ab77112ec2375f5ffbcfb5da68
|
|
7
|
+
data.tar.gz: 5bce8715f5fb0c813876a26f67e6f03b3062030daebd59cdc906ac76e2d37906a50b99fa4a10a8cf3adbfb6ac32f50fd779b13f52fa5963dba7c053172ece099
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marco Roth
|
|
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
CHANGED
|
@@ -1,39 +1,148 @@
|
|
|
1
|
-
|
|
1
|
+
<h2 align="center">🤨 WTF-8 for Ruby</h2>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<h4 align="center">A superset of UTF-8 that encodes surrogate code points if they are not in a pair.</h4>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<div align="center">Ruby implementation of <a href="https://wtf-8.codeberg.page">WTF-8</a>, the encoding for potentially ill-formed UTF-16.</div><br/>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://rubygems.org/gems/wtf8"><img alt="Gem Version" src="https://img.shields.io/gem/v/wtf8"></a>
|
|
9
|
+
<a href="https://wtf-8.codeberg.page"><img alt="Specification" src="https://img.shields.io/badge/wtf--8.codeberg.page-specification-green"></a>
|
|
10
|
+
<a href="https://github.com/marcoroth/wtf8-ruby/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/github/license/marcoroth/wtf8-ruby"></a>
|
|
11
|
+
<a href="https://github.com/marcoroth/wtf8-ruby/issues"><img alt="Issues" src="https://img.shields.io/github/issues/marcoroth/wtf8-ruby"></a>
|
|
12
|
+
</p>
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
<br/>
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
### What is WTF-8 for Ruby?
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
Windows filenames, JavaScript strings, Java strings, and JSON `\uD800` escapes are all sequences of 16-bit code units that are only *potentially* UTF-16. An unpaired surrogate is a legal value in all of them, but it isn't a Unicode scalar value, so UTF-8 can't encode it. Ruby refuses to convert one:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
[0xD800].pack("v").force_encoding("UTF-16LE").encode("UTF-8")
|
|
22
|
+
# Encoding::InvalidByteSequenceError: incomplete "\x00\xD8" on UTF-16LE
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
WTF-8 fills that gap. It encodes a surrogate the same way UTF-8 encodes any other code point, and encodes a surrogate *pair* as the single four-byte sequence for the code point it represents. That second rule is what makes conversion to and from potentially ill-formed UTF-16 lossless in both directions.
|
|
26
|
+
|
|
27
|
+
This gem implements [the WTF-8 specification](https://wtf-8.codeberg.page) by Simon Sapin. Rust uses WTF-8 internally to represent Windows paths, and there is a [Rust implementation](https://crates.io/crates/wtf8) by the same author.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
WTF8.from_utf16le([0xD800].pack("v")).code_points
|
|
31
|
+
#=> [55296]
|
|
15
32
|
```
|
|
16
33
|
|
|
17
|
-
|
|
34
|
+
### Installation
|
|
18
35
|
|
|
19
36
|
```bash
|
|
20
|
-
|
|
37
|
+
bundle add wtf8
|
|
21
38
|
```
|
|
22
39
|
|
|
23
|
-
|
|
40
|
+
Pure Ruby, no dependencies and no native extension. Requires Ruby 3.2 or later.
|
|
41
|
+
|
|
42
|
+
### Usage
|
|
43
|
+
|
|
44
|
+
#### Reading UTF-16
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
WTF8.from_utf16le(bytes) # also from_utf16be and from_utf16
|
|
48
|
+
WTF8.from_utf16([0xD83D, 0xDE00]) #=> #<WTF8::String "😀">
|
|
49
|
+
WTF8.from_utf16([0xD83D]) #=> #<WTF8::String "\u{D83D}">
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
UTF-8 is a subset of WTF-8, so reading a UTF-8 string costs nothing:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
WTF8.from_utf8("héllo 😀")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Converting back
|
|
59
|
+
|
|
60
|
+
`to_utf16` is exact. `to_utf8` isn't, since UTF-8 has no encoding for a surrogate, so each one is replaced with U+FFFD.
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
string = WTF8.from_code_points([0x61, 0xD800, 0x62])
|
|
64
|
+
|
|
65
|
+
string.to_utf16 #=> [97, 55296, 98]
|
|
66
|
+
string.to_utf8 #=> "a�b"
|
|
67
|
+
string.to_utf8(replacement: "?") #=> "a?b"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
One surrogate costs one replacement character. Ruby's `String#scrub` sees three invalid bytes rather than one invalid code point, and spends three:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
"\xED\xA0\x80".dup.force_encoding("UTF-8").scrub("?") #=> "???"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### WTF8::String
|
|
77
|
+
|
|
78
|
+
A Ruby String can store these bytes, since it's bytes plus an encoding tag, but it can't interpret them. Tagged as UTF-8, three bytes of lone surrogate have a `length` of 3 and raise from `codepoints`.
|
|
24
79
|
|
|
25
|
-
|
|
80
|
+
```ruby
|
|
81
|
+
string = WTF8.from_code_points([0x61, 0xD83D])
|
|
26
82
|
|
|
27
|
-
|
|
83
|
+
string.length #=> 2
|
|
84
|
+
string.code_points #=> [97, 55357]
|
|
85
|
+
string.well_formed? #=> false
|
|
28
86
|
|
|
29
|
-
|
|
87
|
+
string.raw.dup.force_encoding("UTF-8").length #=> 4
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Instances are frozen, validated on construction, and compare and hash by their bytes.
|
|
91
|
+
|
|
92
|
+
#### Concatenation
|
|
93
|
+
|
|
94
|
+
Concatenating WTF-8 isn't the same as concatenating bytes. If the left side ends with a lead surrogate and the right side starts with a trail surrogate, the two form a pair, and a pair has to be encoded as one four-byte sequence. Six bytes become four, and the result is one code point shorter than its halves were:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
left = WTF8.from_code_points([0x61, 0xD83D])
|
|
98
|
+
right = WTF8.from_code_points([0xDE00, 0x62])
|
|
99
|
+
|
|
100
|
+
left.length + right.length #=> 4
|
|
101
|
+
(left + right).length #=> 3
|
|
102
|
+
(left + right).to_s #=> "a😀b"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Slicing is the same thing in reverse. It's defined in UTF-16 code units, because that's the only index space where the halves of a supplementary code point can be addressed:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
emoji = WTF8.from_utf8("a😀b")
|
|
109
|
+
|
|
110
|
+
emoji.slice_utf16(0..1) #=> #<WTF8::String "a\u{D83D}">
|
|
111
|
+
emoji.slice_utf16(2..3) #=> #<WTF8::String "\u{DE00}b">
|
|
112
|
+
|
|
113
|
+
emoji.slice_utf16(0..1) + emoji.slice_utf16(2..3) == emoji #=> true
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### CESU-8 and Modified UTF-8
|
|
117
|
+
|
|
118
|
+
CESU-8 inverts the rule WTF-8 uses for supplementary code points. It always encodes them as two surrogates of three bytes each, where WTF-8 always encodes them as one four-byte sequence. Java's Modified UTF-8 is CESU-8 with U+0000 encoded as `C0 80`, so that an encoded string never contains a NUL byte. `DataOutputStream.writeUTF`, `.class` constant pools, dex files and JNI all use it.
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
WTF8.from_cesu8(bytes)
|
|
122
|
+
WTF8.from_modified_utf8(bytes)
|
|
123
|
+
|
|
124
|
+
WTF8.from_utf8("😀").to_cesu8.bytes #=> [237, 160, 189, 237, 184, 128]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Ruby has a built-in CESU-8 transcoder, and for well-formed input this produces identical bytes. Ruby's rejects lone surrogates in both directions; this one doesn't.
|
|
128
|
+
|
|
129
|
+
#### Working with bytes
|
|
130
|
+
|
|
131
|
+
`WTF8::Codec` offers the same operations on plain binary strings, without the wrapper object:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
WTF8::Codec.decode("\xED\xA0\x80") #=> [55296]
|
|
135
|
+
WTF8::Codec.encode([0xD800]).bytes #=> [237, 160, 128]
|
|
136
|
+
WTF8::Codec.concat(left, right)
|
|
137
|
+
WTF8::Codec.valid?("\xED\xA0\xBD\xED\xB8\x80") #=> false
|
|
138
|
+
```
|
|
30
139
|
|
|
31
|
-
|
|
140
|
+
### Don't use it for interchange
|
|
32
141
|
|
|
33
|
-
|
|
142
|
+
WTF-8 is an internal representation. Reading UTF-8 as WTF-8 is safe, but WTF-8 is not UTF-8 and must not be sent as though it were. The byte sequences it adds are exactly the ones a UTF-8 decoder is required to reject, and inconsistent handling of them across a pipeline is a known validation-bypass vector.
|
|
34
143
|
|
|
35
|
-
|
|
144
|
+
Convert with `to_utf8` at the boundary. Don't serve it as `charset=utf-8`, don't write it to a file another program will read as UTF-8, and don't store it in a UTF-8 column.
|
|
36
145
|
|
|
37
|
-
|
|
146
|
+
### License
|
|
38
147
|
|
|
39
|
-
|
|
148
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/wtf8/cesu8.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module CESU8
|
|
5
|
+
#: (::String) -> ::String
|
|
6
|
+
def self.encode(bytes)
|
|
7
|
+
cesu8 = +"".b
|
|
8
|
+
|
|
9
|
+
Codec.decode(bytes).each do |code_point|
|
|
10
|
+
if code_point > 0xFFFF
|
|
11
|
+
Surrogates.split(code_point).each { |unit| cesu8 << [unit].pack("U").b }
|
|
12
|
+
else
|
|
13
|
+
cesu8 << [code_point].pack("U").b
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
cesu8
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#: (::String, ?strict: bool) -> ::String
|
|
21
|
+
def self.decode(bytes, strict: true)
|
|
22
|
+
code_points = Codec.unpack(bytes)
|
|
23
|
+
|
|
24
|
+
wtf8 = +"".b
|
|
25
|
+
index = 0
|
|
26
|
+
|
|
27
|
+
while index < code_points.length
|
|
28
|
+
code_point = code_points[index]
|
|
29
|
+
following = code_points[index + 1]
|
|
30
|
+
|
|
31
|
+
if code_point > Codec::MAX_CODE_POINT
|
|
32
|
+
raise InvalidCodePointError, format("U+%04X is above U+10FFFF", code_point)
|
|
33
|
+
elsif code_point > 0xFFFF
|
|
34
|
+
raise InvalidByteSequenceError, "a four-byte sequence is not CESU-8" if strict
|
|
35
|
+
|
|
36
|
+
wtf8 << [code_point].pack("U").b
|
|
37
|
+
index += 1
|
|
38
|
+
elsif Surrogates.lead?(code_point) && following && Surrogates.trail?(following)
|
|
39
|
+
wtf8 << [Surrogates.combine(code_point, following)].pack("U").b
|
|
40
|
+
index += 2
|
|
41
|
+
else
|
|
42
|
+
wtf8 << [code_point].pack("U").b
|
|
43
|
+
index += 1
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
wtf8
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/wtf8/codec.rb
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module Codec
|
|
5
|
+
MAX_CODE_POINT = 0x10FFFF
|
|
6
|
+
REPLACEMENT_CHARACTER = "\u{FFFD}"
|
|
7
|
+
|
|
8
|
+
#: (::String) -> Array[Integer]
|
|
9
|
+
def self.decode(bytes)
|
|
10
|
+
code_points = unpack(bytes)
|
|
11
|
+
|
|
12
|
+
code_points.each do |code_point|
|
|
13
|
+
next unless code_point > MAX_CODE_POINT
|
|
14
|
+
|
|
15
|
+
raise InvalidCodePointError, format("U+%04X is above U+10FFFF", code_point)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
code_points.each_cons(2) do |pair|
|
|
19
|
+
lead = pair[0]
|
|
20
|
+
trail = pair[1]
|
|
21
|
+
|
|
22
|
+
next unless Surrogates.lead?(lead) && Surrogates.trail?(trail)
|
|
23
|
+
|
|
24
|
+
raise InvalidByteSequenceError, format("U+%<lead>04X U+%<trail>04X is an encoded surrogate pair, which WTF-8 writes as one four-byte sequence", lead: lead, trail: trail)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
code_points
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: (::String) -> Array[Integer]
|
|
31
|
+
def self.unpack(bytes)
|
|
32
|
+
bytes.b.unpack("U*") #: Array[Integer]
|
|
33
|
+
rescue ArgumentError => e
|
|
34
|
+
raise InvalidByteSequenceError, e.message
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: (Array[Integer]) -> ::String
|
|
38
|
+
def self.encode(code_points)
|
|
39
|
+
code_points.each do |code_point|
|
|
40
|
+
next if code_point.is_a?(Integer) && code_point >= 0 && code_point <= MAX_CODE_POINT
|
|
41
|
+
|
|
42
|
+
raise InvalidCodePointError, "#{code_point.inspect} is not a code point in U+0000..U+10FFFF"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
code_points.each_cons(2) do |pair|
|
|
46
|
+
next unless Surrogates.lead?(pair[0]) && Surrogates.trail?(pair[1])
|
|
47
|
+
|
|
48
|
+
raise InvalidCodePointError, "a surrogate pair has to be combined before it is encoded, which from_utf16 does"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
code_points.each_with_object(+"".b) { |code_point, bytes| bytes << [code_point].pack("U").b }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#: (::String) -> bool
|
|
55
|
+
def self.valid?(bytes)
|
|
56
|
+
decode(bytes)
|
|
57
|
+
true
|
|
58
|
+
rescue Error
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#: (::String) -> bool
|
|
63
|
+
def self.well_formed?(bytes)
|
|
64
|
+
bytes.b.dup.force_encoding(Encoding::UTF_8).valid_encoding?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#: (::String) -> ::String
|
|
68
|
+
def self.from_utf8(string)
|
|
69
|
+
string = string.encode(Encoding::UTF_8) unless string.encoding == Encoding::UTF_8 || string.ascii_only?
|
|
70
|
+
|
|
71
|
+
raise InvalidByteSequenceError, "not valid UTF-8" unless string.valid_encoding?
|
|
72
|
+
|
|
73
|
+
string.b
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#: (::String, ?replacement: ::String) -> ::String
|
|
77
|
+
def self.to_utf8(bytes, replacement: REPLACEMENT_CHARACTER)
|
|
78
|
+
utf8 = +""
|
|
79
|
+
|
|
80
|
+
decode(bytes).each do |code_point|
|
|
81
|
+
utf8 << (Surrogates.surrogate?(code_point) ? replacement : [code_point].pack("U"))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
utf8.force_encoding(Encoding::UTF_8)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#: (Array[Integer]) -> ::String
|
|
88
|
+
def self.from_utf16(units)
|
|
89
|
+
bytes = +"".b
|
|
90
|
+
index = 0
|
|
91
|
+
|
|
92
|
+
while index < units.length
|
|
93
|
+
unit = units[index]
|
|
94
|
+
|
|
95
|
+
unless unit.is_a?(Integer) && unit >= 0 && unit <= 0xFFFF
|
|
96
|
+
raise InvalidCodePointError, "#{unit.inspect} is not a UTF-16 code unit"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
following = units[index + 1]
|
|
100
|
+
|
|
101
|
+
if Surrogates.lead?(unit) && following && Surrogates.trail?(following)
|
|
102
|
+
bytes << [Surrogates.combine(unit, following)].pack("U").b
|
|
103
|
+
index += 2
|
|
104
|
+
else
|
|
105
|
+
bytes << [unit].pack("U").b
|
|
106
|
+
index += 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
bytes
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#: (::String) -> Array[Integer]
|
|
114
|
+
def self.to_utf16(bytes)
|
|
115
|
+
decode(bytes).flat_map { |code_point| code_point > 0xFFFF ? Surrogates.split(code_point) : code_point }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#: (::String) -> ::String
|
|
119
|
+
def self.from_utf16le(bytes)
|
|
120
|
+
units = bytes.b.unpack("v*") #: Array[Integer]
|
|
121
|
+
|
|
122
|
+
from_utf16(units)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
#: (::String) -> ::String
|
|
126
|
+
def self.from_utf16be(bytes)
|
|
127
|
+
units = bytes.b.unpack("n*") #: Array[Integer]
|
|
128
|
+
|
|
129
|
+
from_utf16(units)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#: (::String) -> ::String
|
|
133
|
+
def self.to_utf16le(bytes)
|
|
134
|
+
to_utf16(bytes).pack("v*")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
#: (::String) -> ::String
|
|
138
|
+
def self.to_utf16be(bytes)
|
|
139
|
+
to_utf16(bytes).pack("n*")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
#: (::String, ::String) -> ::String
|
|
143
|
+
def self.concat(left, right)
|
|
144
|
+
left = left.b
|
|
145
|
+
right = right.b
|
|
146
|
+
|
|
147
|
+
return left + right unless ends_with_lead_surrogate?(left) && starts_with_trail_surrogate?(right)
|
|
148
|
+
|
|
149
|
+
lead_bytes = left.byteslice(-3, 3) #: ::String
|
|
150
|
+
trail_bytes = right.byteslice(0, 3) #: ::String
|
|
151
|
+
head = left.byteslice(0, left.bytesize - 3) #: ::String
|
|
152
|
+
tail = right.byteslice(3, right.bytesize - 3) #: ::String
|
|
153
|
+
|
|
154
|
+
lead = lead_bytes.unpack1("U") #: Integer
|
|
155
|
+
trail = trail_bytes.unpack1("U") #: Integer
|
|
156
|
+
|
|
157
|
+
head + [Surrogates.combine(lead, trail)].pack("U").b + tail
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
#: (::String) -> bool
|
|
161
|
+
def self.ends_with_lead_surrogate?(bytes)
|
|
162
|
+
bytes.bytesize >= 3 && bytes.getbyte(-3) == Surrogates::PREFIX_BYTE && Surrogates::LEAD_SECOND_BYTE.cover?(bytes.getbyte(-2))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
#: (::String) -> bool
|
|
166
|
+
def self.starts_with_trail_surrogate?(bytes)
|
|
167
|
+
bytes.bytesize >= 3 && bytes.getbyte(0) == Surrogates::PREFIX_BYTE && Surrogates::TRAIL_SECOND_BYTE.cover?(bytes.getbyte(1))
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
data/lib/wtf8/errors.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module ModifiedUTF8
|
|
5
|
+
NUL = "\x00"
|
|
6
|
+
ENCODED_NUL = "\xC0\x80"
|
|
7
|
+
|
|
8
|
+
#: (::String) -> ::String
|
|
9
|
+
def self.encode(bytes)
|
|
10
|
+
CESU8.encode(bytes).gsub(NUL.b, ENCODED_NUL.b)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#: (::String, ?strict: bool) -> ::String
|
|
14
|
+
def self.decode(bytes, strict: true)
|
|
15
|
+
CESU8.decode(bytes.b.gsub(ENCODED_NUL.b, NUL.b), strict: strict)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/wtf8/string.rb
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
class String
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
#: (::String) -> WTF8::String
|
|
8
|
+
def self.from_utf8(string) = new(Codec.from_utf8(string))
|
|
9
|
+
|
|
10
|
+
#: (Array[Integer]) -> WTF8::String
|
|
11
|
+
def self.from_utf16(units) = new(Codec.from_utf16(units))
|
|
12
|
+
|
|
13
|
+
#: (::String) -> WTF8::String
|
|
14
|
+
def self.from_utf16le(bytes) = new(Codec.from_utf16le(bytes))
|
|
15
|
+
|
|
16
|
+
#: (::String) -> WTF8::String
|
|
17
|
+
def self.from_utf16be(bytes) = new(Codec.from_utf16be(bytes))
|
|
18
|
+
|
|
19
|
+
#: (Array[Integer]) -> WTF8::String
|
|
20
|
+
def self.from_code_points(code_points) = new(Codec.encode(code_points))
|
|
21
|
+
|
|
22
|
+
#: (::String, ?strict: bool) -> WTF8::String
|
|
23
|
+
def self.from_cesu8(bytes, strict: true) = new(CESU8.decode(bytes, strict: strict))
|
|
24
|
+
|
|
25
|
+
#: (::String, ?strict: bool) -> WTF8::String
|
|
26
|
+
def self.from_modified_utf8(bytes, strict: true) = new(ModifiedUTF8.decode(bytes, strict: strict))
|
|
27
|
+
|
|
28
|
+
attr_reader :raw #: ::String
|
|
29
|
+
attr_reader :code_points #: Array[Integer]
|
|
30
|
+
|
|
31
|
+
#: (::String) -> void
|
|
32
|
+
def initialize(bytes)
|
|
33
|
+
@raw = bytes.b.freeze
|
|
34
|
+
@code_points = Codec.decode(@raw).freeze
|
|
35
|
+
|
|
36
|
+
freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#: () -> Integer
|
|
40
|
+
def length = code_points.length
|
|
41
|
+
|
|
42
|
+
alias size length
|
|
43
|
+
|
|
44
|
+
#: () -> Integer
|
|
45
|
+
def bytesize = @raw.bytesize
|
|
46
|
+
|
|
47
|
+
#: () -> bool
|
|
48
|
+
def empty? = @raw.empty?
|
|
49
|
+
|
|
50
|
+
#: () -> bool
|
|
51
|
+
def well_formed? = Codec.well_formed?(@raw)
|
|
52
|
+
|
|
53
|
+
#: () { (Integer) -> void } -> WTF8::String
|
|
54
|
+
#: () -> Enumerator[Integer, WTF8::String]
|
|
55
|
+
def each_code_point(&block)
|
|
56
|
+
return enum_for(:each_code_point) unless block
|
|
57
|
+
|
|
58
|
+
code_points.each(&block)
|
|
59
|
+
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#: (WTF8::String | ::String) -> WTF8::String
|
|
64
|
+
def +(other)
|
|
65
|
+
self.class.new(Codec.concat(@raw, other.is_a?(WTF8::String) ? other.raw : Codec.from_utf8(other)))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#: (Integer) -> WTF8::String?
|
|
69
|
+
#: (Range[Integer]) -> WTF8::String?
|
|
70
|
+
def [](index)
|
|
71
|
+
case index
|
|
72
|
+
when Range
|
|
73
|
+
sliced = code_points[index]
|
|
74
|
+
|
|
75
|
+
sliced && self.class.from_code_points(sliced)
|
|
76
|
+
else
|
|
77
|
+
code_point = code_points[index]
|
|
78
|
+
|
|
79
|
+
code_point && self.class.from_code_points([code_point])
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
alias slice []
|
|
84
|
+
|
|
85
|
+
#: (Range[Integer]) -> WTF8::String
|
|
86
|
+
def slice_utf16(range)
|
|
87
|
+
self.class.from_utf16(to_utf16[range] || [])
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#: (WTF8::String | ::String) -> bool
|
|
91
|
+
def start_with?(prefix)
|
|
92
|
+
@raw.start_with?(prefix.is_a?(WTF8::String) ? prefix.raw : Codec.from_utf8(prefix))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#: (WTF8::String | ::String) -> bool
|
|
96
|
+
def end_with?(suffix)
|
|
97
|
+
@raw.end_with?(suffix.is_a?(WTF8::String) ? suffix.raw : Codec.from_utf8(suffix))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: (?replacement: ::String) -> ::String
|
|
101
|
+
def to_utf8(replacement: Codec::REPLACEMENT_CHARACTER) = Codec.to_utf8(@raw, replacement: replacement)
|
|
102
|
+
|
|
103
|
+
alias to_s to_utf8
|
|
104
|
+
|
|
105
|
+
#: () -> Array[Integer]
|
|
106
|
+
def to_utf16 = Codec.to_utf16(@raw)
|
|
107
|
+
|
|
108
|
+
#: () -> ::String
|
|
109
|
+
def to_utf16le = Codec.to_utf16le(@raw)
|
|
110
|
+
|
|
111
|
+
#: () -> ::String
|
|
112
|
+
def to_utf16be = Codec.to_utf16be(@raw)
|
|
113
|
+
|
|
114
|
+
#: () -> ::String
|
|
115
|
+
def to_cesu8 = CESU8.encode(@raw)
|
|
116
|
+
|
|
117
|
+
#: () -> ::String
|
|
118
|
+
def to_modified_utf8 = ModifiedUTF8.encode(@raw)
|
|
119
|
+
|
|
120
|
+
#: (untyped) -> bool
|
|
121
|
+
def ==(other)
|
|
122
|
+
other.is_a?(WTF8::String) && other.raw == @raw
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
alias eql? ==
|
|
126
|
+
|
|
127
|
+
#: () -> Integer
|
|
128
|
+
def hash = [self.class, @raw].hash
|
|
129
|
+
|
|
130
|
+
#: (untyped) -> Integer?
|
|
131
|
+
def <=>(other)
|
|
132
|
+
other.is_a?(WTF8::String) ? (@raw <=> other.raw) : nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#: () -> ::String
|
|
136
|
+
def inspect
|
|
137
|
+
shown = code_points.map { |code_point|
|
|
138
|
+
if Surrogates.surrogate?(code_point)
|
|
139
|
+
format("\\u{%04X}", code_point)
|
|
140
|
+
else
|
|
141
|
+
[code_point].pack("U").inspect[1..-2]
|
|
142
|
+
end
|
|
143
|
+
}.join
|
|
144
|
+
|
|
145
|
+
%(#<#{self.class.name} "#{shown}">)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module Surrogates
|
|
5
|
+
RANGE = 0xD800..0xDFFF
|
|
6
|
+
LEAD_RANGE = 0xD800..0xDBFF
|
|
7
|
+
TRAIL_RANGE = 0xDC00..0xDFFF
|
|
8
|
+
|
|
9
|
+
PREFIX_BYTE = 0xED
|
|
10
|
+
LEAD_SECOND_BYTE = 0xA0..0xAF
|
|
11
|
+
TRAIL_SECOND_BYTE = 0xB0..0xBF
|
|
12
|
+
|
|
13
|
+
#: (Integer) -> bool
|
|
14
|
+
def self.surrogate?(code_point)
|
|
15
|
+
RANGE.cover?(code_point)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: (Integer) -> bool
|
|
19
|
+
def self.lead?(code_point)
|
|
20
|
+
LEAD_RANGE.cover?(code_point)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: (Integer) -> bool
|
|
24
|
+
def self.trail?(code_point)
|
|
25
|
+
TRAIL_RANGE.cover?(code_point)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#: (Integer, Integer) -> Integer
|
|
29
|
+
def self.combine(lead, trail)
|
|
30
|
+
0x10000 + ((lead - 0xD800) << 10) + (trail - 0xDC00)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#: (Integer) -> Array[Integer]
|
|
34
|
+
def self.split(code_point)
|
|
35
|
+
remainder = code_point - 0x10000
|
|
36
|
+
|
|
37
|
+
[0xD800 + (remainder >> 10), 0xDC00 + (remainder & 0x3FF)]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/wtf8/version.rb
CHANGED
data/lib/wtf8.rb
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "wtf8/version"
|
|
4
|
+
require_relative "wtf8/errors"
|
|
5
|
+
require_relative "wtf8/surrogates"
|
|
6
|
+
require_relative "wtf8/codec"
|
|
7
|
+
require_relative "wtf8/cesu8"
|
|
8
|
+
require_relative "wtf8/modified_utf8"
|
|
9
|
+
require_relative "wtf8/string"
|
|
4
10
|
|
|
5
11
|
module WTF8
|
|
12
|
+
#: (::String) -> WTF8::String
|
|
13
|
+
def self.from_utf8(string) = String.from_utf8(string)
|
|
14
|
+
|
|
15
|
+
#: (Array[Integer]) -> WTF8::String
|
|
16
|
+
def self.from_utf16(units) = String.from_utf16(units)
|
|
17
|
+
|
|
18
|
+
#: (::String) -> WTF8::String
|
|
19
|
+
def self.from_utf16le(bytes) = String.from_utf16le(bytes)
|
|
20
|
+
|
|
21
|
+
#: (::String) -> WTF8::String
|
|
22
|
+
def self.from_utf16be(bytes) = String.from_utf16be(bytes)
|
|
23
|
+
|
|
24
|
+
#: (Array[Integer]) -> WTF8::String
|
|
25
|
+
def self.from_code_points(code_points) = String.from_code_points(code_points)
|
|
26
|
+
|
|
27
|
+
#: (::String, ?strict: bool) -> WTF8::String
|
|
28
|
+
def self.from_cesu8(bytes, strict: true) = String.from_cesu8(bytes, strict: strict)
|
|
29
|
+
|
|
30
|
+
#: (::String, ?strict: bool) -> WTF8::String
|
|
31
|
+
def self.from_modified_utf8(bytes, strict: true) = String.from_modified_utf8(bytes, strict: strict)
|
|
32
|
+
|
|
33
|
+
#: (::String) -> WTF8::String
|
|
34
|
+
def self.from_wtf8(bytes) = String.new(bytes)
|
|
35
|
+
|
|
36
|
+
#: (::String) -> bool
|
|
37
|
+
def self.valid?(bytes) = Codec.valid?(bytes)
|
|
6
38
|
end
|
data/sig/wtf8/cesu8.rbs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Generated from lib/wtf8/cesu8.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module CESU8
|
|
5
|
+
# : (::String) -> ::String
|
|
6
|
+
def self.encode: (::String) -> ::String
|
|
7
|
+
|
|
8
|
+
# : (::String, ?strict: bool) -> ::String
|
|
9
|
+
def self.decode: (::String, ?strict: bool) -> ::String
|
|
10
|
+
end
|
|
11
|
+
end
|
data/sig/wtf8/codec.rbs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Generated from lib/wtf8/codec.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module Codec
|
|
5
|
+
MAX_CODE_POINT: ::Integer
|
|
6
|
+
|
|
7
|
+
REPLACEMENT_CHARACTER: ::String
|
|
8
|
+
|
|
9
|
+
# : (::String) -> Array[Integer]
|
|
10
|
+
def self.decode: (::String) -> Array[Integer]
|
|
11
|
+
|
|
12
|
+
# : (::String) -> Array[Integer]
|
|
13
|
+
def self.unpack: (::String) -> Array[Integer]
|
|
14
|
+
|
|
15
|
+
# : (Array[Integer]) -> ::String
|
|
16
|
+
def self.encode: (Array[Integer]) -> ::String
|
|
17
|
+
|
|
18
|
+
# : (::String) -> bool
|
|
19
|
+
def self.valid?: (::String) -> bool
|
|
20
|
+
|
|
21
|
+
# : (::String) -> bool
|
|
22
|
+
def self.well_formed?: (::String) -> bool
|
|
23
|
+
|
|
24
|
+
# : (::String) -> ::String
|
|
25
|
+
def self.from_utf8: (::String) -> ::String
|
|
26
|
+
|
|
27
|
+
# : (::String, ?replacement: ::String) -> ::String
|
|
28
|
+
def self.to_utf8: (::String, ?replacement: ::String) -> ::String
|
|
29
|
+
|
|
30
|
+
# : (Array[Integer]) -> ::String
|
|
31
|
+
def self.from_utf16: (Array[Integer]) -> ::String
|
|
32
|
+
|
|
33
|
+
# : (::String) -> Array[Integer]
|
|
34
|
+
def self.to_utf16: (::String) -> Array[Integer]
|
|
35
|
+
|
|
36
|
+
# : (::String) -> ::String
|
|
37
|
+
def self.from_utf16le: (::String) -> ::String
|
|
38
|
+
|
|
39
|
+
# : (::String) -> ::String
|
|
40
|
+
def self.from_utf16be: (::String) -> ::String
|
|
41
|
+
|
|
42
|
+
# : (::String) -> ::String
|
|
43
|
+
def self.to_utf16le: (::String) -> ::String
|
|
44
|
+
|
|
45
|
+
# : (::String) -> ::String
|
|
46
|
+
def self.to_utf16be: (::String) -> ::String
|
|
47
|
+
|
|
48
|
+
# : (::String, ::String) -> ::String
|
|
49
|
+
def self.concat: (::String, ::String) -> ::String
|
|
50
|
+
|
|
51
|
+
# : (::String) -> bool
|
|
52
|
+
def self.ends_with_lead_surrogate?: (::String) -> bool
|
|
53
|
+
|
|
54
|
+
# : (::String) -> bool
|
|
55
|
+
def self.starts_with_trail_surrogate?: (::String) -> bool
|
|
56
|
+
end
|
|
57
|
+
end
|
data/sig/wtf8/errors.rbs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Generated from lib/wtf8/modified_utf8.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module ModifiedUTF8
|
|
5
|
+
NUL: ::String
|
|
6
|
+
|
|
7
|
+
ENCODED_NUL: ::String
|
|
8
|
+
|
|
9
|
+
# : (::String) -> ::String
|
|
10
|
+
def self.encode: (::String) -> ::String
|
|
11
|
+
|
|
12
|
+
# : (::String, ?strict: bool) -> ::String
|
|
13
|
+
def self.decode: (::String, ?strict: bool) -> ::String
|
|
14
|
+
end
|
|
15
|
+
end
|
data/sig/wtf8/string.rbs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Generated from lib/wtf8/string.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
class String
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
# : (::String) -> WTF8::String
|
|
8
|
+
def self.from_utf8: (::String) -> WTF8::String
|
|
9
|
+
|
|
10
|
+
# : (Array[Integer]) -> WTF8::String
|
|
11
|
+
def self.from_utf16: (Array[Integer]) -> WTF8::String
|
|
12
|
+
|
|
13
|
+
# : (::String) -> WTF8::String
|
|
14
|
+
def self.from_utf16le: (::String) -> WTF8::String
|
|
15
|
+
|
|
16
|
+
# : (::String) -> WTF8::String
|
|
17
|
+
def self.from_utf16be: (::String) -> WTF8::String
|
|
18
|
+
|
|
19
|
+
# : (Array[Integer]) -> WTF8::String
|
|
20
|
+
def self.from_code_points: (Array[Integer]) -> WTF8::String
|
|
21
|
+
|
|
22
|
+
# : (::String, ?strict: bool) -> WTF8::String
|
|
23
|
+
def self.from_cesu8: (::String, ?strict: bool) -> WTF8::String
|
|
24
|
+
|
|
25
|
+
# : (::String, ?strict: bool) -> WTF8::String
|
|
26
|
+
def self.from_modified_utf8: (::String, ?strict: bool) -> WTF8::String
|
|
27
|
+
|
|
28
|
+
attr_reader raw: ::String
|
|
29
|
+
|
|
30
|
+
attr_reader code_points: Array[Integer]
|
|
31
|
+
|
|
32
|
+
# : (::String) -> void
|
|
33
|
+
def initialize: (::String) -> void
|
|
34
|
+
|
|
35
|
+
# : () -> Integer
|
|
36
|
+
def length: () -> Integer
|
|
37
|
+
|
|
38
|
+
alias size length
|
|
39
|
+
|
|
40
|
+
# : () -> Integer
|
|
41
|
+
def bytesize: () -> Integer
|
|
42
|
+
|
|
43
|
+
# : () -> bool
|
|
44
|
+
def empty?: () -> bool
|
|
45
|
+
|
|
46
|
+
# : () -> bool
|
|
47
|
+
def well_formed?: () -> bool
|
|
48
|
+
|
|
49
|
+
# : () { (Integer) -> void } -> WTF8::String
|
|
50
|
+
# : () -> Enumerator[Integer, WTF8::String]
|
|
51
|
+
def each_code_point: () { (Integer) -> void } -> WTF8::String
|
|
52
|
+
| () -> Enumerator[Integer, WTF8::String]
|
|
53
|
+
|
|
54
|
+
# : (WTF8::String | ::String) -> WTF8::String
|
|
55
|
+
def +: (WTF8::String | ::String) -> WTF8::String
|
|
56
|
+
|
|
57
|
+
# : (Integer) -> WTF8::String?
|
|
58
|
+
# : (Range[Integer]) -> WTF8::String?
|
|
59
|
+
def []: (Integer) -> WTF8::String?
|
|
60
|
+
| (Range[Integer]) -> WTF8::String?
|
|
61
|
+
|
|
62
|
+
alias slice []
|
|
63
|
+
|
|
64
|
+
# : (Range[Integer]) -> WTF8::String
|
|
65
|
+
def slice_utf16: (Range[Integer]) -> WTF8::String
|
|
66
|
+
|
|
67
|
+
# : (WTF8::String | ::String) -> bool
|
|
68
|
+
def start_with?: (WTF8::String | ::String) -> bool
|
|
69
|
+
|
|
70
|
+
# : (WTF8::String | ::String) -> bool
|
|
71
|
+
def end_with?: (WTF8::String | ::String) -> bool
|
|
72
|
+
|
|
73
|
+
# : (?replacement: ::String) -> ::String
|
|
74
|
+
def to_utf8: (?replacement: ::String) -> ::String
|
|
75
|
+
|
|
76
|
+
alias to_s to_utf8
|
|
77
|
+
|
|
78
|
+
# : () -> Array[Integer]
|
|
79
|
+
def to_utf16: () -> Array[Integer]
|
|
80
|
+
|
|
81
|
+
# : () -> ::String
|
|
82
|
+
def to_utf16le: () -> ::String
|
|
83
|
+
|
|
84
|
+
# : () -> ::String
|
|
85
|
+
def to_utf16be: () -> ::String
|
|
86
|
+
|
|
87
|
+
# : () -> ::String
|
|
88
|
+
def to_cesu8: () -> ::String
|
|
89
|
+
|
|
90
|
+
# : () -> ::String
|
|
91
|
+
def to_modified_utf8: () -> ::String
|
|
92
|
+
|
|
93
|
+
# : (untyped) -> bool
|
|
94
|
+
def ==: (untyped) -> bool
|
|
95
|
+
|
|
96
|
+
alias eql? ==
|
|
97
|
+
|
|
98
|
+
# : () -> Integer
|
|
99
|
+
def hash: () -> Integer
|
|
100
|
+
|
|
101
|
+
# : (untyped) -> Integer?
|
|
102
|
+
def <=>: (untyped) -> Integer?
|
|
103
|
+
|
|
104
|
+
# : () -> ::String
|
|
105
|
+
def inspect: () -> ::String
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Generated from lib/wtf8/surrogates.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
module Surrogates
|
|
5
|
+
RANGE: untyped
|
|
6
|
+
|
|
7
|
+
LEAD_RANGE: untyped
|
|
8
|
+
|
|
9
|
+
TRAIL_RANGE: untyped
|
|
10
|
+
|
|
11
|
+
PREFIX_BYTE: ::Integer
|
|
12
|
+
|
|
13
|
+
LEAD_SECOND_BYTE: untyped
|
|
14
|
+
|
|
15
|
+
TRAIL_SECOND_BYTE: untyped
|
|
16
|
+
|
|
17
|
+
# : (Integer) -> bool
|
|
18
|
+
def self.surrogate?: (Integer) -> bool
|
|
19
|
+
|
|
20
|
+
# : (Integer) -> bool
|
|
21
|
+
def self.lead?: (Integer) -> bool
|
|
22
|
+
|
|
23
|
+
# : (Integer) -> bool
|
|
24
|
+
def self.trail?: (Integer) -> bool
|
|
25
|
+
|
|
26
|
+
# : (Integer, Integer) -> Integer
|
|
27
|
+
def self.combine: (Integer, Integer) -> Integer
|
|
28
|
+
|
|
29
|
+
# : (Integer) -> Array[Integer]
|
|
30
|
+
def self.split: (Integer) -> Array[Integer]
|
|
31
|
+
end
|
|
32
|
+
end
|
data/sig/wtf8.rbs
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Generated from lib/wtf8.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module WTF8
|
|
4
|
+
# : (::String) -> WTF8::String
|
|
5
|
+
def self.from_utf8: (::String) -> WTF8::String
|
|
6
|
+
|
|
7
|
+
# : (Array[Integer]) -> WTF8::String
|
|
8
|
+
def self.from_utf16: (Array[Integer]) -> WTF8::String
|
|
9
|
+
|
|
10
|
+
# : (::String) -> WTF8::String
|
|
11
|
+
def self.from_utf16le: (::String) -> WTF8::String
|
|
12
|
+
|
|
13
|
+
# : (::String) -> WTF8::String
|
|
14
|
+
def self.from_utf16be: (::String) -> WTF8::String
|
|
15
|
+
|
|
16
|
+
# : (Array[Integer]) -> WTF8::String
|
|
17
|
+
def self.from_code_points: (Array[Integer]) -> WTF8::String
|
|
18
|
+
|
|
19
|
+
# : (::String, ?strict: bool) -> WTF8::String
|
|
20
|
+
def self.from_cesu8: (::String, ?strict: bool) -> WTF8::String
|
|
21
|
+
|
|
22
|
+
# : (::String, ?strict: bool) -> WTF8::String
|
|
23
|
+
def self.from_modified_utf8: (::String, ?strict: bool) -> WTF8::String
|
|
24
|
+
|
|
25
|
+
# : (::String) -> WTF8::String
|
|
26
|
+
def self.from_wtf8: (::String) -> WTF8::String
|
|
27
|
+
|
|
28
|
+
# : (::String) -> bool
|
|
29
|
+
def self.valid?: (::String) -> bool
|
|
4
30
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wtf8
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marco Roth
|
|
@@ -18,10 +18,24 @@ executables: []
|
|
|
18
18
|
extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
|
20
20
|
files:
|
|
21
|
+
- LICENSE.txt
|
|
21
22
|
- README.md
|
|
22
23
|
- lib/wtf8.rb
|
|
24
|
+
- lib/wtf8/cesu8.rb
|
|
25
|
+
- lib/wtf8/codec.rb
|
|
26
|
+
- lib/wtf8/errors.rb
|
|
27
|
+
- lib/wtf8/modified_utf8.rb
|
|
28
|
+
- lib/wtf8/string.rb
|
|
29
|
+
- lib/wtf8/surrogates.rb
|
|
23
30
|
- lib/wtf8/version.rb
|
|
24
31
|
- sig/wtf8.rbs
|
|
32
|
+
- sig/wtf8/cesu8.rbs
|
|
33
|
+
- sig/wtf8/codec.rbs
|
|
34
|
+
- sig/wtf8/errors.rbs
|
|
35
|
+
- sig/wtf8/modified_utf8.rbs
|
|
36
|
+
- sig/wtf8/string.rbs
|
|
37
|
+
- sig/wtf8/surrogates.rbs
|
|
38
|
+
- sig/wtf8/version.rbs
|
|
25
39
|
- wtf8.gemspec
|
|
26
40
|
homepage: https://github.com/marcoroth/wtf8-ruby
|
|
27
41
|
licenses:
|
|
@@ -45,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
45
59
|
- !ruby/object:Gem::Version
|
|
46
60
|
version: '0'
|
|
47
61
|
requirements: []
|
|
48
|
-
rubygems_version: 4.0.
|
|
62
|
+
rubygems_version: 4.0.6
|
|
49
63
|
specification_version: 4
|
|
50
64
|
summary: Ruby Implementation of the WTF-8 encoding.
|
|
51
65
|
test_files: []
|