borsh 0.0.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '026901148b33570032ecf66ae88dd74df8b510d88846916d84145886ba481547'
4
- data.tar.gz: f52074d33c2f1d1c36cb59c1df329cc904b8269a483eefd51a74689d0cac7ce9
3
+ metadata.gz: f889d5e7f96680cfdb852425c23b8053c0c2b9b6c783e8cc2ba5929821091712
4
+ data.tar.gz: 9fb32d11e1f054a12aa752bf33ee94ff109ca5dae8c0555e28c41bd7c6217201
5
5
  SHA512:
6
- metadata.gz: b65f1ce556d96681c15fcb83101b5dcccdd262702c9b44d24c306f1ee60be7188d164e4a497acd78ea69ca6991e18fff936a311c82a61789ba2aa88dfddbee71
7
- data.tar.gz: 5818449160d9c19ac06231f738f6fe641001be1c7995c29a97294d8dc2c753a9094272063265006354770db845e2c80f5e6bd93795104890e1e5e2570c5d4bfa
6
+ metadata.gz: e5321fc35e88b65794c7c22670667713f036b0b5a5a6217d72af8142179bc91fa15ef1ba079b22bc07098f81cb9f2c3eea040239723b2a63e10a2ba5dd7eb180
7
+ data.tar.gz: bb3dcf4db0c101e30450eaee3333745e5ba35829c35f17ee1c778fbde9a79b70673a977a61b7abf630195a1c893c90ed064dd239891d361b958e1ade0cf5557c
data/CHANGES.md CHANGED
@@ -5,4 +5,6 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.1.0 - 2025-01-11
9
+
8
10
  ## 0.0.0 - 2025-01-09
data/README.md CHANGED
@@ -28,15 +28,95 @@ gem install borsh
28
28
  require 'borsh'
29
29
  ```
30
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
+
31
111
  ## 👨‍💻 Development
32
112
 
33
113
  ```bash
34
- git clone https://github.com/artob/borsh.rb.git
114
+ git clone https://github.com/dryruby/borsh.rb.git
35
115
  ```
36
116
 
37
117
  - - -
38
118
 
39
- [![Share on Twitter](https://img.shields.io/badge/share%20on-twitter-03A9F4?logo=twitter)](https://twitter.com/share?url=https://github.com/artob/borsh.rb&text=Borsh+for+Ruby)
40
- [![Share on Reddit](https://img.shields.io/badge/share%20on-reddit-red?logo=reddit)](https://reddit.com/submit?url=https://github.com/artob/borsh.rb&title=Borsh+for+Ruby)
41
- [![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/artob/borsh.rb&t=Borsh+for+Ruby)
42
- [![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/artob/borsh.rb)
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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
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 CHANGED
@@ -1,3 +1,7 @@
1
1
  # This is free and unencumbered software released into the public domain.
2
2
 
3
3
  module Borsh; end
4
+
5
+ require_relative 'borsh/buffer'
6
+ require_relative 'borsh/readable'
7
+ require_relative 'borsh/writable'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: borsh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arto Bendiken
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-09 00:00:00.000000000 Z
10
+ date: 2025-01-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rspec
@@ -49,15 +49,18 @@ files:
49
49
  - UNLICENSE
50
50
  - VERSION
51
51
  - lib/borsh.rb
52
- homepage: https://github.com/artob/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
53
56
  licenses:
54
57
  - Unlicense
55
58
  metadata:
56
- bug_tracker_uri: https://github.com/artob/borsh.rb/issues
57
- changelog_uri: https://github.com/artob/borsh.rb/blob/master/CHANGES.md
58
- documentation_uri: https://github.com/artob/borsh.rb/blob/master/README.md
59
- homepage_uri: https://github.com/artob/borsh.rb
60
- source_code_uri: https://github.com/artob/borsh.rb
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
61
64
  rdoc_options: []
62
65
  require_paths:
63
66
  - lib