zon-rb 0.1.0 → 0.3.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/CHANGELOG.md +3 -1
- data/README.md +36 -4
- data/lib/zon/hasher.rb +176 -0
- data/lib/zon/version.rb +1 -1
- data/lib/zon/zig.rb +80 -0
- data/lib/zon.rb +2 -0
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 818004dd00ec5169c64a80953c55e2b49f9b92a7691aef46251463e671c8a033
|
|
4
|
+
data.tar.gz: 2519a754dd2e2cc19ee40c8dbfa7c2ae0370e5bd33fc6e69f7dcc0bebd56eb00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e03c8137c996cb03adc77ddebc0afbb95a6cb078f635052eb88dfb2b782365e7ca335aa5436560619b20e246d7693ba96860625943b780deb15c1a2386ef9472
|
|
7
|
+
data.tar.gz: 497d4a8b6b830abe6b533fdbb22407524616f5b44727d7c73d8a0f9e49105a7db6d2941396c3a904e4dcfc029c0f5157eb4dfe1db63b22248032d56052403344
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
# Zig Object Notation (ZON) for Ruby
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/zon-rb)
|
|
4
|
+
|
|
3
5
|
The [Zig](https://ziglang.org/) Object Notation (ZON) is a file format primarily used within the Zig ecosystem. For example, ZON is used for the Zig package [manifest](https://github.com/ziglang/zig/blob/b7ab62540963d80f68d0e9ee7ce18520fb173487/doc/build.zig.zon.md).
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
|
-
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
8
|
-
|
|
9
9
|
Install the gem and add to the application's Gemfile by executing:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
bundle add
|
|
12
|
+
bundle add zon-rb
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
gem install
|
|
18
|
+
gem install zon-rb
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
|
+
To use this Gem you have to require it:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require "zon"
|
|
27
|
+
```
|
|
28
|
+
|
|
23
29
|
To parse ZON data into a Ruby object, one can use the `Zon::parse` method. The method expects either a `String` or `IO` (`File`) object as its argument.
|
|
24
30
|
|
|
25
31
|
```irb
|
|
@@ -94,6 +100,32 @@ irb(main):010> Zon::serialize(255, {:ibase => 2})
|
|
|
94
100
|
=> "0b11111111"
|
|
95
101
|
```
|
|
96
102
|
|
|
103
|
+
### Zig Manifest
|
|
104
|
+
|
|
105
|
+
`Zon::Zig::Manifest` provides you with an abstraction for your `build.zig.zon`. It will also validate your Zig package [manifest](https://github.com/ziglang/zig/blob/b7ab62540963d80f68d0e9ee7ce18520fb173487/doc/build.zig.zon.md) and raise an exception if one of the required fields is missing.
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
o = Zon::parse(File.open("../PassKeeZ/build.zig.zon"))
|
|
109
|
+
manifest = Zon::Zig::Manifest.new o
|
|
110
|
+
|
|
111
|
+
# Use:
|
|
112
|
+
# manifest.name
|
|
113
|
+
# manifest.version
|
|
114
|
+
# ...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Zig Package Hash
|
|
118
|
+
|
|
119
|
+
Given a path `pdir` that points to the root of a Zig package, one can calculate the packages hash using the `Zon::Hasher`:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
hasher = Zon::Zig::Hasher.new pdir
|
|
123
|
+
hasher.hash
|
|
124
|
+
# Hash with the format nnnn-vvvv-XXXX..XXXX
|
|
125
|
+
# e.g.: uuid-0.4.0-oOieIR2AAAChAUVBY4ABjYI1XN0EbVALmiN0JIlggC3i
|
|
126
|
+
result = hasher.result
|
|
127
|
+
```
|
|
128
|
+
|
|
97
129
|
## Development
|
|
98
130
|
|
|
99
131
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/zon/hasher.rb
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "find"
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Zon
|
|
6
|
+
module Zig
|
|
7
|
+
##
|
|
8
|
+
# Produces a package hash of the format: $name-$semver-$hashplus
|
|
9
|
+
#
|
|
10
|
+
# Along with 200 bits of a SHA-256, a package hash includes:
|
|
11
|
+
# - package name
|
|
12
|
+
# - package version
|
|
13
|
+
# - id component of the package fingerprint
|
|
14
|
+
# - total unpacked size on disk
|
|
15
|
+
#
|
|
16
|
+
# The following steps are taken to calculate the hash:
|
|
17
|
+
# 1. Read the list of included paths from the manifest, i.e. files that are part of the package.
|
|
18
|
+
# 2. Resolve the paths:
|
|
19
|
+
# - for directories: include all files that are within the directory or a child dir.
|
|
20
|
+
# - for files: include the file
|
|
21
|
+
# - for symlinks: TODO
|
|
22
|
+
# 3. Sort the paths by:
|
|
23
|
+
# 1. Lexographical order
|
|
24
|
+
# 2. by length
|
|
25
|
+
# 4. For every included file:
|
|
26
|
+
# - Calculate a SHA-256 over: RELATIVE_PATH_FROM_PKG_ROOT || 0x0000 || DATA
|
|
27
|
+
# - Record the file size in bytes
|
|
28
|
+
# - TODO: the 0x0000 will probably change in the future
|
|
29
|
+
# 5. Calculate a SHA-256 sum over all calculated file hashes
|
|
30
|
+
# - respecting the previously mentioned sorting rules.
|
|
31
|
+
# 6. Sum up the sizes of all files into a u32 (sizes that don't fit into a 32-bit unsingned integer will be saturated with the value 2**32 - 1)
|
|
32
|
+
# 7. Produce the package hash
|
|
33
|
+
class Hasher
|
|
34
|
+
attr_reader :paths, :total_size, :digest
|
|
35
|
+
|
|
36
|
+
SUPPORTED_ZIG_VERSIONS = ["0.14.0", "0.14.1", "0.15.1", "0.15.2"]
|
|
37
|
+
|
|
38
|
+
def initialize(package_path, manifest_name: "build.zig.zon")
|
|
39
|
+
# Make sure we have a valid path to work with
|
|
40
|
+
@package_path = package_path
|
|
41
|
+
raise ArgumentError, "not a directory '#{@package_path}'" if not Dir.exist? @package_path
|
|
42
|
+
|
|
43
|
+
manifest_path = File.join(@package_path, manifest_name)
|
|
44
|
+
raise ArgumentError, "no manifest '#{manifest_name}' in '#{@package_path}'" if not File.exist? manifest_path
|
|
45
|
+
|
|
46
|
+
@manifest = Zon.parse(File.open(manifest_path))
|
|
47
|
+
raise ArgumentError, "no 'paths' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :paths
|
|
48
|
+
raise ArgumentError, "no 'name' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :name
|
|
49
|
+
raise ArgumentError, "no 'version' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :version
|
|
50
|
+
raise ArgumentError, "no 'fingerprint' field in ZON manifest '#{manifest_path}'" if not @manifest.key? :fingerprint
|
|
51
|
+
|
|
52
|
+
@paths = []
|
|
53
|
+
@results = {}
|
|
54
|
+
@total_size = 0
|
|
55
|
+
@digest = nil
|
|
56
|
+
|
|
57
|
+
@manifest[:paths].each do |path|
|
|
58
|
+
full_path = File.join(@package_path, path)
|
|
59
|
+
|
|
60
|
+
if File.directory? full_path
|
|
61
|
+
f = Find.find(full_path)
|
|
62
|
+
f.each do |p|
|
|
63
|
+
next if File.directory? p
|
|
64
|
+
|
|
65
|
+
pstr = p.delete_prefix(@package_path)[1..]
|
|
66
|
+
@paths.append(pstr) if not @paths.include? pstr
|
|
67
|
+
end
|
|
68
|
+
elsif File.file? full_path or File.symlink? full_path
|
|
69
|
+
pstr = full_path.delete_prefix(@package_path)[1..]
|
|
70
|
+
@paths.append(pstr) if not @paths.include? pstr
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@paths = @paths.sort_by { |str| [str, -str.length] }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def name
|
|
78
|
+
String(@manifest[:name])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def version
|
|
82
|
+
@manifest[:version]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def hash
|
|
86
|
+
@total_size = 0
|
|
87
|
+
@digest = nil
|
|
88
|
+
|
|
89
|
+
# Hash all files individually
|
|
90
|
+
@paths.each do |str|
|
|
91
|
+
@results[str] = Zon::Zig::Hasher::hash_file(@package_path, str)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sha2 = Digest::SHA2.new
|
|
95
|
+
|
|
96
|
+
@paths.each do |str|
|
|
97
|
+
res = @results[str]
|
|
98
|
+
|
|
99
|
+
@total_size += res[:size]
|
|
100
|
+
sha2.update [res[:digest]].pack('H*')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
@digest = sha2.hexdigest
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Get the ID part of the fingerprint
|
|
108
|
+
def get_id
|
|
109
|
+
@manifest[:fingerprint] & 0xffffffff
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Get the calculated, total size of the unpacked package.
|
|
114
|
+
def get_saturated_size
|
|
115
|
+
max_uint32 = 2**32 - 1
|
|
116
|
+
|
|
117
|
+
if @total_size > max_uint32
|
|
118
|
+
max_uint32
|
|
119
|
+
else
|
|
120
|
+
@total_size
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def result
|
|
125
|
+
Zon::Zig::Hasher::make_hash(@digest, name, version, get_id, get_saturated_size)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Produces $name-$semver-$hashplus
|
|
130
|
+
#
|
|
131
|
+
# - name is the name field from a build.zig.zon manifest. It is expected
|
|
132
|
+
# to be at most 32 bytes long and a valid Zig identifier.
|
|
133
|
+
# - semver is the version field from a build.zig.zon manifest. It is expected
|
|
134
|
+
# to be at most 32 bytes long.
|
|
135
|
+
# - hashplus is a base64 (urlsafe and nopad) encoded, 33-byte string:
|
|
136
|
+
# - Pacakge ID (4) || Decompressed Size (4) || Digest0, Digest1, ..., Digest24
|
|
137
|
+
def self.make_hash(digest, name, semver, id, size)
|
|
138
|
+
raise ArgumentError, "name '#{name}' is expected to be at most 32 bytes long but it is #{name.bytesize} bytes" if name.bytesize > 32
|
|
139
|
+
raise ArgumentError, "semver '#{semver}' is expected to be at most 32 bytes long but it is #{semver.bytesize} bytes" if semver.bytesize > 32
|
|
140
|
+
|
|
141
|
+
hash_plus = [id].pack("V")
|
|
142
|
+
hash_plus += [size].pack("V")
|
|
143
|
+
hash_plus += [digest].pack('H*')[0..24]
|
|
144
|
+
|
|
145
|
+
"#{name}-#{semver}-#{Base64.urlsafe_encode64(hash_plus, padding: false)}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def self.hash_file(package_path, path)
|
|
149
|
+
sha2 = Digest::SHA2.new
|
|
150
|
+
sha2.update path
|
|
151
|
+
|
|
152
|
+
full_path = File.join(package_path, path)
|
|
153
|
+
|
|
154
|
+
if File.file? full_path
|
|
155
|
+
f = File.open full_path
|
|
156
|
+
data = f.read
|
|
157
|
+
file_size = data.bytesize
|
|
158
|
+
|
|
159
|
+
# Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463
|
|
160
|
+
sha2.update "\x00\x00"
|
|
161
|
+
sha2.update data
|
|
162
|
+
elsif File.symlink? full_path
|
|
163
|
+
link_name = File.readlink full_path
|
|
164
|
+
sha2.update link_name
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# TODO: symlink
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
digest: sha2.hexdigest,
|
|
171
|
+
size: file_size,
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
data/lib/zon/version.rb
CHANGED
data/lib/zon/zig.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Zon
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Everything related specifically to the Zig programming language.
|
|
5
|
+
module Zig
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# An abstraction of the Zig package manifest.
|
|
9
|
+
#
|
|
10
|
+
# Every Zig package should have a +build.zig.zon+ in
|
|
11
|
+
# its package root, the manifest. Like a manifest in
|
|
12
|
+
# Ruby, it is used to describe the package and specify
|
|
13
|
+
# required dependencies.
|
|
14
|
+
class Manifest
|
|
15
|
+
def initialize(zon)
|
|
16
|
+
raise "Missing '.name'" if not zon[:name]
|
|
17
|
+
raise "Missing '.version'" if not zon[:version]
|
|
18
|
+
raise "Missing '.fingerprint'" if not zon[:fingerprint]
|
|
19
|
+
raise "Missing '.paths'" if not zon[:paths]
|
|
20
|
+
|
|
21
|
+
if zon[:dependencies]
|
|
22
|
+
zon[:dependencies].each do |key, value|
|
|
23
|
+
if value[:url]
|
|
24
|
+
raise "Missing '.hash' for dependency '#{key}'" if not value[:hash]
|
|
25
|
+
elsif not value[:path]
|
|
26
|
+
raise "Expected either '.url' or '.path' for dependency '#{key}'" if not value[:hash]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@zon = zon
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Get the '.name' field of the manifest.
|
|
36
|
+
def name
|
|
37
|
+
@zon[:name]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# Get the '.version' field of the manifest.
|
|
42
|
+
def version
|
|
43
|
+
@zon[:version]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Get the '.fingerprint' field of the manifest.
|
|
48
|
+
def fingerprint
|
|
49
|
+
@zon[:fingerprint]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Get the '.dependencies' field of the manifest.
|
|
54
|
+
#
|
|
55
|
+
# This will return nil if the '.dependencies' field does not exist.
|
|
56
|
+
def dependencies
|
|
57
|
+
@zon[:dependencies]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Get the '.paths' field of the manifest.
|
|
62
|
+
#
|
|
63
|
+
# This will return nil if the '.paths' field does not exist.
|
|
64
|
+
def paths
|
|
65
|
+
@zon[:paths]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# Get the '.minimum_zig_version' field of the manifest.
|
|
70
|
+
#
|
|
71
|
+
# This will return nil if the '.minimum_zig_version' field does not exist.
|
|
72
|
+
def minimum_zig_version
|
|
73
|
+
@zon[:minimum_zig_version]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
data/lib/zon.rb
CHANGED
metadata
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zon-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David P. Sugar
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-10-19 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description: This gem allows you to translate ZON data into
|
|
13
|
-
versa.
|
|
12
|
+
description: This gem allows you to translate Zig Object Notation (ZON) data into
|
|
13
|
+
Ruby objects and vice versa.
|
|
14
14
|
email:
|
|
15
15
|
- david@thesugar.de
|
|
16
16
|
executables: []
|
|
@@ -24,10 +24,12 @@ files:
|
|
|
24
24
|
- README.md
|
|
25
25
|
- Rakefile
|
|
26
26
|
- lib/zon.rb
|
|
27
|
+
- lib/zon/hasher.rb
|
|
27
28
|
- lib/zon/lexer.rb
|
|
28
29
|
- lib/zon/parser.rb
|
|
29
30
|
- lib/zon/serializer.rb
|
|
30
31
|
- lib/zon/version.rb
|
|
32
|
+
- lib/zon/zig.rb
|
|
31
33
|
- lib/zon/zon_grammar.treetop
|
|
32
34
|
- sig/zon.rbs
|
|
33
35
|
homepage: https://github.com/r4gus/zon-rb
|