borsh 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f889d5e7f96680cfdb852425c23b8053c0c2b9b6c783e8cc2ba5929821091712
4
+ data.tar.gz: 9fb32d11e1f054a12aa752bf33ee94ff109ca5dae8c0555e28c41bd7c6217201
5
+ SHA512:
6
+ metadata.gz: e5321fc35e88b65794c7c22670667713f036b0b5a5a6217d72af8142179bc91fa15ef1ba079b22bc07098f81cb9f2c3eea040239723b2a63e10a2ba5dd7eb180
7
+ data.tar.gz: bb3dcf4db0c101e30450eaee3333745e5ba35829c35f17ee1c778fbde9a79b70673a977a61b7abf630195a1c893c90ed064dd239891d361b958e1ade0cf5557c
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ * Arto Bendiken <arto@bendiken.net>
data/CHANGES.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## 0.1.0 - 2025-01-11
9
+
10
+ ## 0.0.0 - 2025-01-09
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Borsh for Ruby
2
+
3
+ [![License](https://img.shields.io/badge/license-Public%20Domain-blue.svg)](https://unlicense.org)
4
+ [![Compatibility](https://img.shields.io/badge/ruby-2.6%2B-blue)](https://rubygems.org/gems/borsh)
5
+ [![Package](https://img.shields.io/gem/v/borsh)](https://rubygems.org/gems/borsh)
6
+
7
+ A Ruby library for the [Borsh] binary serialization format.
8
+
9
+ [Borsh]: https://borsh.io
10
+
11
+ ## 🛠️ Prerequisites
12
+
13
+ - [Ruby](https://ruby-lang.org) 2.6+
14
+
15
+ ## ⬇️ Installation
16
+
17
+ ### Installation via RubyGems
18
+
19
+ ```bash
20
+ gem install borsh
21
+ ```
22
+
23
+ ## 👉 Examples
24
+
25
+ ### Importing the library
26
+
27
+ ```ruby
28
+ require 'borsh'
29
+ ```
30
+
31
+ ### Writing to an output stream
32
+
33
+ ```ruby
34
+ $stdout.extend(Borsh::Writable)
35
+ $stdout.write_string("Hello, world!")
36
+ ```
37
+
38
+ ### Reading from an input stream
39
+
40
+ ```ruby
41
+ $stdin.extend(Borsh::Readable)
42
+ p $stdin.read_string
43
+ ```
44
+
45
+ ### Writing to an in-memory buffer
46
+
47
+ ```ruby
48
+ serialized_data = Borsh::Buffer.open do |buf|
49
+ # Primitive types:
50
+ buf.write_bool(true)
51
+ buf.write_u8(255)
52
+ buf.write_i32(-12345)
53
+ buf.write_f64(3.14159)
54
+ buf.write_string("Hello, Borsh!")
55
+
56
+ # Fixed-size array:
57
+ buf.write_array([1, 2, 3])
58
+
59
+ # Dynamic-sized array (array with a length prefix):
60
+ buf.write_vector(['a', 'b', 'c'])
61
+ end
62
+ ```
63
+
64
+ ### Reading from an in-memory buffer
65
+
66
+ ```ruby
67
+ Borsh::Buffer.new(serialized_data) do |buf|
68
+ # Primitive types:
69
+ bool_val = buf.read_bool # => true
70
+ u8_val = buf.read_u8 # => 255
71
+ i32_val = buf.read_i32 # => -12345
72
+ f64_val = buf.read_f64 # => 3.14159
73
+ string_val = buf.read_string # => "Hello, Borsh!"
74
+
75
+ # Fixed-size array:
76
+ array = buf.read_array(:i32, 3) # => [1, 2, 3]
77
+
78
+ # Dynamic-sized array (array with a length prefix):
79
+ vector = buf.read_vector(:string) # => ['a', 'b', 'c']
80
+ end
81
+ ```
82
+
83
+ ## 📚 Reference
84
+
85
+ | Informal Type | `Borsh::Writable` | `Borsh::Readable` |
86
+ | :------------ | :---------------- | :---------------- |
87
+ | nil/unit | `write_unit()` | `read_unit()` |
88
+ | boolean | `write_bool(x)` | `read_bool()` |
89
+ | u8 integer | `write_u8(n)` | `read_u8()` |
90
+ | u16 integer | `write_u16(n)` | `read_u16()` |
91
+ | u32 integer | `write_u32(n)` | `read_u32()` |
92
+ | u64 integer | `write_u64(n)` | `read_u64()` |
93
+ | u128 integer | `write_u128(n)` | `read_u128()` |
94
+ | i8 integer | `write_i8(n)` | `read_i8()` |
95
+ | i16 integer | `write_i16(n)` | `read_i16()` |
96
+ | i32 integer | `write_i32(n)` | `read_i32()` |
97
+ | i64 integer | `write_i64(n)` | `read_i64()` |
98
+ | i128 integer | `write_i128(n)` | `read_i128()` |
99
+ | f32 float | `write_f32(f)` | `read_f32()` |
100
+ | f64 float | `write_f64(f)` | `read_f64()` |
101
+ | string | `write_string(x)` | `read_string()` |
102
+ | array | `write_array(x)` | `read_array(element_type, count)` |
103
+ | vector | `write_vector(x)` | `read_vector(element_type)` |
104
+ | struct | `write_struct(x)` | `read_struct(struct_class)` |
105
+ | enum | `write_enum(x)` | `read_enum(variants)` |
106
+ | map/hash | `write_map(x)` | `read_map(key_type, value_type)` |
107
+ | set | `write_set(x)` | `read_set(element_type)` |
108
+ | option | `write_option(x)` | `read_option(element_type)` |
109
+ | result | `write_result(x)` | `read_result(ok_type, err_type)` |
110
+
111
+ ## 👨‍💻 Development
112
+
113
+ ```bash
114
+ git clone https://github.com/dryruby/borsh.rb.git
115
+ ```
116
+
117
+ - - -
118
+
119
+ [![Share on Twitter](https://img.shields.io/badge/share%20on-twitter-03A9F4?logo=twitter)](https://twitter.com/share?url=https://github.com/dryruby/borsh.rb&text=Borsh+for+Ruby)
120
+ [![Share on Reddit](https://img.shields.io/badge/share%20on-reddit-red?logo=reddit)](https://reddit.com/submit?url=https://github.com/dryruby/borsh.rb&title=Borsh+for+Ruby)
121
+ [![Share on Hacker News](https://img.shields.io/badge/share%20on-hacker%20news-orange?logo=ycombinator)](https://news.ycombinator.com/submitlink?u=https://github.com/dryruby/borsh.rb&t=Borsh+for+Ruby)
122
+ [![Share on Facebook](https://img.shields.io/badge/share%20on-facebook-1976D2?logo=facebook)](https://www.facebook.com/sharer/sharer.php?u=https://github.com/dryruby/borsh.rb)
data/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,77 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require 'stringio'
4
+ require_relative 'readable'
5
+ require_relative 'writable'
6
+
7
+ ##
8
+ # A buffer for reading and writing Borsh data.
9
+ class Borsh::Buffer
10
+ include Borsh::Readable
11
+ include Borsh::Writable
12
+
13
+ ##
14
+ # @param [String, nil] data
15
+ # @yield [buffer]
16
+ # @yieldreturn [Object]
17
+ # @return [String]
18
+ def self.open(data = nil, &block)
19
+ buffer = self.new(data || '', &block)
20
+ buffer.close
21
+ buffer.data
22
+ end
23
+
24
+ ##
25
+ # @param [String] data
26
+ # @yield [buffer]
27
+ # @yieldreturn [void]
28
+ # @return [void]
29
+ def initialize(data = '', &block)
30
+ @buffer = StringIO.new(data)
31
+ @buffer.binmode
32
+ block.call(self) if block_given?
33
+ end
34
+
35
+ ##
36
+ # Returns the buffer data.
37
+ #
38
+ # @return [String]
39
+ def data
40
+ @buffer.string
41
+ end
42
+ alias_method :string, :data
43
+
44
+ ##
45
+ # Returns `true` if the buffer is closed.
46
+ #
47
+ # @return [Boolean]
48
+ def closed?
49
+ @buffer.closed?
50
+ end
51
+
52
+ ##
53
+ # Closes the buffer.
54
+ #
55
+ # @return [void]
56
+ def close;
57
+ @buffer.close
58
+ end
59
+
60
+ ##
61
+ # Reads the specified number of bytes from the buffer.
62
+ #
63
+ # @param [Integer, #to_i] length
64
+ # @return [String]
65
+ def read(length)
66
+ @buffer.read(length.to_i)
67
+ end
68
+
69
+ ##
70
+ # Writes data to the buffer.
71
+ #
72
+ # @param [String, #to_s] data
73
+ # @return [Integer]
74
+ def write(data)
75
+ @buffer.write(data.to_s)
76
+ end
77
+ end # Borsh::Buffer
@@ -0,0 +1,171 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require 'set'
4
+
5
+ module Borsh::Readable
6
+ def read_object(type)
7
+ case type
8
+ when :nil then self.read_unit
9
+ when :bool then self.read_bool
10
+ when :u8 then self.read_u8
11
+ when :u16 then self.read_u16
12
+ when :u32 then self.read_u32
13
+ when :u64 then self.read_u64
14
+ when :u128 then self.read_u128
15
+ when :i8 then self.read_i8
16
+ when :i16 then self.read_i16
17
+ when :i32 then self.read_i32
18
+ when :i64 then self.read_i64
19
+ when :i128 then self.read_i128
20
+ when :f32 then self.read_f32
21
+ when :f64 then self.read_f64
22
+ when :string then self.read_string
23
+ when Array
24
+ case type.first
25
+ when :array then self.read_array(type[1], type[2])
26
+ when :vector then self.read_vector(type[1])
27
+ when :map then self.read_map(type[1], type[2])
28
+ when :set then self.read_set(type[1])
29
+ when :option then self.read_option(type[1])
30
+ when :result then self.read_result(type[1], type[2])
31
+ else raise "unsupported array type specifier: #{type.inspect}"
32
+ end
33
+ when Class
34
+ raise "unsupported class type: #{type}" unless type < Struct
35
+ self.read_struct(type)
36
+ else raise "unsupported type specifier: #{type.inspect}"
37
+ end
38
+ end
39
+
40
+ def read_unit
41
+ nil
42
+ end
43
+ alias_method :read_nil, :read_unit
44
+
45
+ def read_bool
46
+ self.read_u8 == 1
47
+ end
48
+
49
+ def read_u8
50
+ self.read(1).unpack('C').first
51
+ end
52
+
53
+ def read_u16
54
+ self.read(2).unpack('v').first
55
+ end
56
+
57
+ def read_u32
58
+ self.read(4).unpack('V').first
59
+ end
60
+
61
+ def read_u64
62
+ self.read(8).unpack('Q<').first
63
+ end
64
+
65
+ def read_u128
66
+ # Read two 64-bit integers (little-endian):
67
+ lower = self.read_u64
68
+ upper = self.read_u64
69
+ # Combine into a 128-bit number:
70
+ (upper << 64) | lower
71
+ end
72
+
73
+ def read_i8
74
+ self.read(1).unpack('c').first
75
+ end
76
+
77
+ def read_i16
78
+ self.read(2).unpack('s').first
79
+ end
80
+
81
+ def read_i32
82
+ self.read(4).unpack('l<').first
83
+ end
84
+
85
+ def read_i64
86
+ self.read(8).unpack('q<').first
87
+ end
88
+
89
+ def read_i128
90
+ n = self.read_u128
91
+ # Handle two's complement for negative numbers:
92
+ if n >= (1 << 127)
93
+ n - (1 << 128)
94
+ else
95
+ n
96
+ end
97
+ end
98
+
99
+ def read_f32
100
+ self.read(4).unpack('e').first
101
+ end
102
+
103
+ def read_f64
104
+ self.read(8).unpack('E').first
105
+ end
106
+
107
+ def read_string
108
+ self.read(self.read_u32)
109
+ end
110
+
111
+ def read_array(element_type, count)
112
+ Array.new(count) { self.read_object(element_type) }
113
+ end
114
+
115
+ def read_vector(element_type)
116
+ count = self.read_u32
117
+ self.read_array(element_type, count)
118
+ end
119
+ alias_method :read_vec, :read_vector
120
+
121
+ def read_struct(struct_class)
122
+ values = struct_class.members.map { |member|
123
+ type = struct_class::MEMBER_TYPES.fetch(member)
124
+ self.read_object(type)
125
+ }
126
+ struct_class.new(*values)
127
+ end
128
+
129
+ def read_enum(variants)
130
+ ordinal = self.read_u8
131
+ type = variants.fetch(ordinal)
132
+ value = self.read_object(type)
133
+ [ordinal, value]
134
+ end
135
+
136
+ def read_map(key_type, value_type)
137
+ count = self.read_u32
138
+ result = {}
139
+ count.times do
140
+ key = self.read_object(key_type)
141
+ value = self.read_object(value_type)
142
+ result[key] = value
143
+ end
144
+ result
145
+ end
146
+ alias_method :read_hash, :read_map
147
+
148
+ def read_set(element_type)
149
+ count = self.read_u32
150
+ result = Set.new
151
+ count.times do
152
+ result << self.read_object(element_type)
153
+ end
154
+ result
155
+ end
156
+
157
+ def read_option(element_type)
158
+ if self.read_bool
159
+ self.read_object(element_type)
160
+ else
161
+ nil
162
+ end
163
+ end
164
+
165
+ def read_result(ok_type, err_type)
166
+ ok = self.read_bool
167
+ type = ok ? ok_type : err_type
168
+ value = self.read_object(type)
169
+ [ok, value]
170
+ end
171
+ end # Borsh::Readable
@@ -0,0 +1,172 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require 'set'
4
+
5
+ module Borsh::Writable
6
+ def write_object(x)
7
+ x = x.to_borsh if x.respond_to?(:to_borsh)
8
+ case x
9
+ when NilClass then self.write_unit()
10
+ when FalseClass then self.write_bool(x)
11
+ when TrueClass then self.write_bool(x)
12
+ when Integer then self.write_i64(x)
13
+ when Float then self.write_f64(x)
14
+ when String then self.write_string(x)
15
+ when Array then self.write_vector(x)
16
+ when Hash then self.write_map(x)
17
+ when Set then self.write_set(x)
18
+ else raise "unsupported type: #{x.class}"
19
+ end
20
+ end
21
+
22
+ def write_unit()
23
+ # nothing to write
24
+ end
25
+ alias_method :write_nil, :write_unit
26
+
27
+ def write_bool(x)
28
+ self.write_u8(x ? 1 : 0)
29
+ end
30
+
31
+ def write_u8(n)
32
+ self.write([n].pack('C'))
33
+ end
34
+
35
+ def write_u16(n)
36
+ self.write([n].pack('v'))
37
+ end
38
+
39
+ def write_u32(n)
40
+ self.write([n].pack('V'))
41
+ end
42
+
43
+ def write_u64(n)
44
+ self.write([n].pack('Q<'))
45
+ end
46
+
47
+ def write_u128(n)
48
+ # Split 128-bit number into two 64-bit parts:
49
+ lower = n & ((1 << 64) - 1)
50
+ upper = (n >> 64) & ((1 << 64) - 1)
51
+ # Write the lower 64 bits first (little-endian):
52
+ self.write_u64(lower)
53
+ # Then write the upper 64 bits:
54
+ self.write_u64(upper)
55
+ end
56
+
57
+ def write_i8(n)
58
+ self.write([n].pack('c'))
59
+ end
60
+
61
+ def write_i16(n)
62
+ self.write([n].pack('s'))
63
+ end
64
+
65
+ def write_i32(n)
66
+ self.write([n].pack('l<'))
67
+ end
68
+
69
+ def write_i64(n)
70
+ self.write([n].pack('q<'))
71
+ end
72
+
73
+ def write_i128(n)
74
+ # Convert negative numbers to two's complement:
75
+ n = (1 << 128) + n if n < 0
76
+ # Use `#write_u128` to write the bits:
77
+ self.write_u128(n)
78
+ end
79
+
80
+ def write_f32(f)
81
+ self.write([f].pack('e'))
82
+ end
83
+
84
+ def write_f64(f)
85
+ self.write([f].pack('E'))
86
+ end
87
+
88
+ def write_string(x)
89
+ self.write_u32(x.bytesize)
90
+ self.write(x)
91
+ end
92
+
93
+ def write_array(x)
94
+ x.each { |e| self.write_object(e) }
95
+ end
96
+
97
+ def write_vector(x)
98
+ self.write_u32(x.size)
99
+ x.each { |e| self.write_object(e) }
100
+ end
101
+ alias_method :write_vec, :write_vector
102
+
103
+ def write_struct(x)
104
+ raise "value must be a Struct" unless x.is_a?(Struct)
105
+
106
+ x.members.each do |k|
107
+ self.write_object(x[k])
108
+ end
109
+ end
110
+
111
+ def write_enum(x)
112
+ # An enum should be represented as `[ordinal, value]`:
113
+ unless x.is_a?(Array) && x.size == 2 && x[0].is_a?(Integer)
114
+ raise "enum must be [ordinal, value]"
115
+ end
116
+
117
+ ordinal, value = x
118
+ self.write_u8(ordinal)
119
+ self.write_object(value)
120
+ end
121
+
122
+ def write_map(x)
123
+ self.write_u32(x.size)
124
+ case x
125
+ when Array
126
+ x.sort.each do |(k, v)|
127
+ self.write_object(k)
128
+ self.write_object(v)
129
+ end
130
+ when Hash
131
+ x.keys.sort.each do |k|
132
+ self.write_object(k)
133
+ self.write_object(x[k])
134
+ end
135
+ else raise "unsupported type: #{x.class}"
136
+ end
137
+ end
138
+ alias_method :write_hash, :write_map
139
+
140
+ def write_set(x)
141
+ self.write_u32(x.size)
142
+ keys = case x
143
+ when Array then x
144
+ when Hash then x.keys
145
+ when Set then x.to_a
146
+ else raise "unsupported type: #{x.class}"
147
+ end
148
+ keys.sort.each do |k|
149
+ self.write_object(k)
150
+ end
151
+ end
152
+
153
+ def write_option(x)
154
+ if !x.nil?
155
+ self.write_u8(1)
156
+ self.write_object(x)
157
+ else
158
+ self.write_u8(0)
159
+ end
160
+ end
161
+
162
+ def write_result(x)
163
+ # A result should be represented as `[ok, value]`:
164
+ unless x.is_a?(Array) && x.size == 2
165
+ raise "result must be [ok, value]"
166
+ end
167
+
168
+ ok, value = x
169
+ self.write_u8(ok ? 1 : 0)
170
+ self.write_object(value)
171
+ end
172
+ end # Borsh::Writable
data/lib/borsh.rb ADDED
@@ -0,0 +1,7 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ module Borsh; end
4
+
5
+ require_relative 'borsh/buffer'
6
+ require_relative 'borsh/readable'
7
+ require_relative 'borsh/writable'
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: borsh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Arto Bendiken
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-01-11 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.12'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.12'
26
+ - !ruby/object:Gem::Dependency
27
+ name: yard
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.9'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.9'
40
+ description: A Ruby library for the Borsh binary serialization format.
41
+ email: arto@bendiken.net
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - AUTHORS
47
+ - CHANGES.md
48
+ - README.md
49
+ - UNLICENSE
50
+ - VERSION
51
+ - lib/borsh.rb
52
+ - lib/borsh/buffer.rb
53
+ - lib/borsh/readable.rb
54
+ - lib/borsh/writable.rb
55
+ homepage: https://github.com/dryruby/borsh.rb
56
+ licenses:
57
+ - Unlicense
58
+ metadata:
59
+ bug_tracker_uri: https://github.com/dryruby/borsh.rb/issues
60
+ changelog_uri: https://github.com/dryruby/borsh.rb/blob/master/CHANGES.md
61
+ documentation_uri: https://github.com/dryruby/borsh.rb/blob/master/README.md
62
+ homepage_uri: https://github.com/dryruby/borsh.rb
63
+ source_code_uri: https://github.com/dryruby/borsh.rb
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '2.6'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.2
79
+ specification_version: 4
80
+ summary: Borsh for Ruby
81
+ test_files: []