borsh 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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