spinel_kit 0.1.1 → 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 +51 -0
- data/README.md +83 -53
- data/docs/adoption.md +8 -0
- data/docs/spinel-discipline.md +17 -20
- data/sig/spinel_kit/hex.rbs +8 -0
- data/sig/spinel_kit/url.rbs +9 -0
- data/spin.toml +6 -0
- data/spinel_kit/hex.rb +59 -0
- data/spinel_kit/url.rb +164 -0
- data/spinel_kit/version.rb +6 -0
- data/spinel_kit.rb +39 -0
- metadata +20 -22
- data/lib/spinel_kit/json.rb +0 -149
- data/lib/spinel_kit/json_builder.rb +0 -142
- data/lib/spinel_kit/json_decoder.rb +0 -394
- data/lib/spinel_kit/version.rb +0 -6
- data/lib/spinel_kit.rb +0 -39
- data/sig/spinel_kit/json.rbs +0 -15
- data/sig/spinel_kit/json_builder.rbs +0 -19
- data/sig/spinel_kit/json_decoder.rbs +0 -21
- /data/{lib/spinel_kit → spinel_kit}/git.rb +0 -0
- /data/{lib/spinel_kit → spinel_kit}/log.rb +0 -0
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spinel_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ori Pekelman
|
|
@@ -10,14 +10,14 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: |-
|
|
13
|
-
SpinelKit consolidates the pure-Ruby
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
SpinelKit consolidates the pure-Ruby shims that every Spinel-compiled
|
|
14
|
+
project would otherwise hand-roll and that Spinel (the Ruby->native AOT
|
|
15
|
+
compiler) does not provide: .git/HEAD provenance, URL percent-codec +
|
|
16
|
+
query parsing, a minimal levelled logger, and a hex digit/byte codec.
|
|
17
|
+
(JSON left in 0.3.0: Spinel now bundles `json` as a require-gated stdlib
|
|
18
|
+
package, so consumers use JSON.parse/JSON.generate directly.) Pure Ruby,
|
|
19
|
+
no native extension, no runtime dependencies -- it vendors cleanly via
|
|
20
|
+
bundler-spinel. Pre-alpha.
|
|
21
21
|
email:
|
|
22
22
|
- ori@pekelman.com
|
|
23
23
|
executables: []
|
|
@@ -31,20 +31,19 @@ files:
|
|
|
31
31
|
- docs/gem-audit-first.md
|
|
32
32
|
- docs/spinel-discipline.md
|
|
33
33
|
- docs/spinelgems-issues.md
|
|
34
|
-
- lib/spinel_kit.rb
|
|
35
|
-
- lib/spinel_kit/git.rb
|
|
36
|
-
- lib/spinel_kit/json.rb
|
|
37
|
-
- lib/spinel_kit/json_builder.rb
|
|
38
|
-
- lib/spinel_kit/json_decoder.rb
|
|
39
|
-
- lib/spinel_kit/log.rb
|
|
40
|
-
- lib/spinel_kit/version.rb
|
|
41
34
|
- sig/spinel_kit/git.rbs
|
|
42
|
-
- sig/spinel_kit/
|
|
43
|
-
- sig/spinel_kit/json_builder.rbs
|
|
44
|
-
- sig/spinel_kit/json_decoder.rbs
|
|
35
|
+
- sig/spinel_kit/hex.rbs
|
|
45
36
|
- sig/spinel_kit/log.rbs
|
|
37
|
+
- sig/spinel_kit/url.rbs
|
|
46
38
|
- sig/spinel_kit/version.rbs
|
|
39
|
+
- spin.toml
|
|
47
40
|
- spinel-ext.json
|
|
41
|
+
- spinel_kit.rb
|
|
42
|
+
- spinel_kit/git.rb
|
|
43
|
+
- spinel_kit/hex.rb
|
|
44
|
+
- spinel_kit/log.rb
|
|
45
|
+
- spinel_kit/url.rb
|
|
46
|
+
- spinel_kit/version.rb
|
|
48
47
|
homepage: https://github.com/OriPekelman/spinelkit
|
|
49
48
|
licenses:
|
|
50
49
|
- MIT
|
|
@@ -56,7 +55,7 @@ metadata:
|
|
|
56
55
|
rubygems_mfa_required: 'true'
|
|
57
56
|
rdoc_options: []
|
|
58
57
|
require_paths:
|
|
59
|
-
-
|
|
58
|
+
- "."
|
|
60
59
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
60
|
requirements:
|
|
62
61
|
- - ">="
|
|
@@ -70,6 +69,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
69
|
requirements: []
|
|
71
70
|
rubygems_version: 3.6.9
|
|
72
71
|
specification_version: 4
|
|
73
|
-
summary:
|
|
74
|
-
apps'
|
|
72
|
+
summary: Pure-Ruby Git/Url/Log/Hex shims for Spinel AOT-compiled apps
|
|
75
73
|
test_files: []
|
data/lib/spinel_kit/json.rb
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# SpinelKit::Json -- Spinel-safe JSON ENCODERS (stateless).
|
|
2
|
-
#
|
|
3
|
-
# This file holds the encode half of the codec; the decode half lives in
|
|
4
|
-
# spinel_kit/json_decoder.rb (also `SpinelKit::Json`), and the incremental
|
|
5
|
-
# object builder in spinel_kit/json_builder.rb (`SpinelKit::Json::Builder`).
|
|
6
|
-
# The three are split because Spinel has no tree-shaking: every loaded method
|
|
7
|
-
# is compiled, and a set of uncalled methods can degrade each other's params
|
|
8
|
-
# (e.g. the dead decoder walkers collectively widening `escape`'s string arg
|
|
9
|
-
# to int, which silently miscompiled string keys to ""). Keeping
|
|
10
|
-
# encode/decode/build in separate files means a consumer compiles only the
|
|
11
|
-
# surface it calls, and each surface is independently warning-clean. Require
|
|
12
|
-
# only what you use:
|
|
13
|
-
#
|
|
14
|
-
# require "spinel_kit/json" # encoders (this file)
|
|
15
|
-
# require "spinel_kit/json_decoder" # decoders
|
|
16
|
-
# require "spinel_kit/json_builder" # builder
|
|
17
|
-
#
|
|
18
|
-
# WHY HAND-ROLLED. Spinel cannot lower the stdlib `json` gem (C-ext fast path
|
|
19
|
-
# + metaprogrammed pure fallback); `oj`/`yajl`/`multi_json` are C extensions.
|
|
20
|
-
# The spinelgems catalog confirms no verified pure-Ruby JSON gem exists. This
|
|
21
|
-
# is tep's encoder, standardized (the `j_`/`tj_` prefixes that worked around a
|
|
22
|
-
# now-fixed Spinel inference bug are gone -- see docs/spinel-discipline.md).
|
|
23
|
-
#
|
|
24
|
-
# Compose objects in user code by concatenation:
|
|
25
|
-
#
|
|
26
|
-
# "{" + SpinelKit::Json.encode_pair_str("name", name) + "," +
|
|
27
|
-
# SpinelKit::Json.encode_pair_int("age", age) + "}"
|
|
28
|
-
module SpinelKit
|
|
29
|
-
class Json
|
|
30
|
-
# Escape a string for inclusion inside a JSON string literal (does NOT
|
|
31
|
-
# add the surrounding quotes -- use `quote(s)` for that). Handles ", \,
|
|
32
|
-
# and the JSON-required control-char escapes (\b, \f, \n, \r, \t);
|
|
33
|
-
# other control bytes go through \u00XX. Forward slash is left
|
|
34
|
-
# unescaped (legal either way; unescaped is shorter/readable).
|
|
35
|
-
def self.escape(s)
|
|
36
|
-
out = ""
|
|
37
|
-
i = 0
|
|
38
|
-
n = s.length
|
|
39
|
-
while i < n
|
|
40
|
-
c = s[i]
|
|
41
|
-
if c == "\""
|
|
42
|
-
out = out + "\\\""
|
|
43
|
-
elsif c == "\\"
|
|
44
|
-
out = out + "\\\\"
|
|
45
|
-
elsif c == "\n"
|
|
46
|
-
out = out + "\\n"
|
|
47
|
-
elsif c == "\r"
|
|
48
|
-
out = out + "\\r"
|
|
49
|
-
elsif c == "\t"
|
|
50
|
-
out = out + "\\t"
|
|
51
|
-
elsif c == "\b"
|
|
52
|
-
out = out + "\\b"
|
|
53
|
-
elsif c == "\f"
|
|
54
|
-
out = out + "\\f"
|
|
55
|
-
elsif c < " "
|
|
56
|
-
# Other control byte -- emit \u00XX. c.getbyte(0) is the raw
|
|
57
|
-
# byte value, mapped to two hex digits.
|
|
58
|
-
b = c.getbyte(0)
|
|
59
|
-
out = out + "\\u00" + Json.hex2(b)
|
|
60
|
-
else
|
|
61
|
-
out = out + c
|
|
62
|
-
end
|
|
63
|
-
i += 1
|
|
64
|
-
end
|
|
65
|
-
out
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Two-digit lowercase hex of a byte (0..255).
|
|
69
|
-
def self.hex2(n)
|
|
70
|
-
hex = "0123456789abcdef"
|
|
71
|
-
out = ""
|
|
72
|
-
out = out + hex[(n / 16) % 16, 1]
|
|
73
|
-
out = out + hex[n % 16, 1]
|
|
74
|
-
out
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Wrap a string in JSON quotes, escaping its body.
|
|
78
|
-
def self.quote(s)
|
|
79
|
-
"\"" + Json.escape(s) + "\""
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Encode a single key/value pair as `"k":"v"` (escaped both sides).
|
|
83
|
-
def self.encode_pair_str(k, v)
|
|
84
|
-
Json.quote(k) + ":" + Json.quote(v)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Same shape, integer value side. `v` is rendered via `.to_s` so
|
|
88
|
-
# JSON-numeric output without quoting.
|
|
89
|
-
def self.encode_pair_int(k, v)
|
|
90
|
-
Json.quote(k) + ":" + v.to_s
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Encode a Hash<String,String> as a JSON object.
|
|
94
|
-
def self.from_str_hash(h)
|
|
95
|
-
out = "{"
|
|
96
|
-
first = true
|
|
97
|
-
h.each do |k, v|
|
|
98
|
-
if !first
|
|
99
|
-
out = out + ","
|
|
100
|
-
end
|
|
101
|
-
first = false
|
|
102
|
-
out = out + Json.quote(k) + ":" + Json.quote(v)
|
|
103
|
-
end
|
|
104
|
-
out + "}"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Same shape with integer values. JSON-numeric, no quoting.
|
|
108
|
-
def self.from_int_hash(h)
|
|
109
|
-
out = "{"
|
|
110
|
-
first = true
|
|
111
|
-
h.each do |k, v|
|
|
112
|
-
if !first
|
|
113
|
-
out = out + ","
|
|
114
|
-
end
|
|
115
|
-
first = false
|
|
116
|
-
out = out + Json.quote(k) + ":" + v.to_s
|
|
117
|
-
end
|
|
118
|
-
out + "}"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Encode a string array as a JSON array of quoted strings.
|
|
122
|
-
def self.from_str_array(a)
|
|
123
|
-
out = "["
|
|
124
|
-
i = 0
|
|
125
|
-
while i < a.length
|
|
126
|
-
if i > 0
|
|
127
|
-
out = out + ","
|
|
128
|
-
end
|
|
129
|
-
out = out + Json.quote(a[i])
|
|
130
|
-
i += 1
|
|
131
|
-
end
|
|
132
|
-
out + "]"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Encode an int array as a JSON array of numbers.
|
|
136
|
-
def self.from_int_array(a)
|
|
137
|
-
out = "["
|
|
138
|
-
i = 0
|
|
139
|
-
while i < a.length
|
|
140
|
-
if i > 0
|
|
141
|
-
out = out + ","
|
|
142
|
-
end
|
|
143
|
-
out = out + a[i].to_s
|
|
144
|
-
i += 1
|
|
145
|
-
end
|
|
146
|
-
out + "]"
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# SpinelKit::Json::Builder -- an incremental, ordered JSON-object builder.
|
|
2
|
-
#
|
|
3
|
-
# WHY A SEPARATE FILE/CLASS. This is toy's hand-rolled builder; the codec
|
|
4
|
-
# (SpinelKit::Json, encode/decode) is tep's. They are split so a consumer
|
|
5
|
-
# compiles only the surface it uses: Spinel has no tree-shaking, so an
|
|
6
|
-
# uncalled method is still compiled and degrades its params, emitting benign
|
|
7
|
-
# but gate-tripping `emitting 0` warnings. A builder-only consumer requires
|
|
8
|
-
# THIS file and never pays for the decoder walkers; a codec-only consumer
|
|
9
|
-
# requires spinel_kit/json and never pays for the builder.
|
|
10
|
-
#
|
|
11
|
-
# To stay self-contained, Builder carries its OWN escape/quote/hex2 rather
|
|
12
|
-
# than calling SpinelKit::Json.* (which would drag the whole codec, decoders
|
|
13
|
-
# included, into a builder-only compile). These are byte-identical to the
|
|
14
|
-
# codec's; the small duplication buys surface isolation. The same-named
|
|
15
|
-
# methods across the two classes are safe -- the Spinel name-collision bug
|
|
16
|
-
# that once made that dangerous is fixed (see docs/spinel-discipline.md).
|
|
17
|
-
#
|
|
18
|
-
# USAGE (mutating appenders, NOT method-chaining -- chaining is a Spinel
|
|
19
|
-
# poly-degradation risk):
|
|
20
|
-
#
|
|
21
|
-
# j = SpinelKit::Json::Builder.new
|
|
22
|
-
# j.add_str("kind", "run_start")
|
|
23
|
-
# j.add_num("t", now) # int OR float via .to_s
|
|
24
|
-
# host = SpinelKit::Json::Builder.new
|
|
25
|
-
# host.add_str("name", host_name)
|
|
26
|
-
# j.add_obj("host", host) # nest a sub-builder
|
|
27
|
-
# j.add_raw("lr", "0.001") # already-encoded JSON literal
|
|
28
|
-
# ev = j.dump # "{...}"
|
|
29
|
-
#
|
|
30
|
-
# NUMERIC NOTE: if a single compiled program passes BOTH Integer and Float to
|
|
31
|
-
# `add_num`'s `value`, Spinel unifies the param to Float and an Integer
|
|
32
|
-
# renders as "N.0". For hardcoded numeric literals where byte-exact output
|
|
33
|
-
# matters, use `add_raw` with an already-encoded string instead of relying on
|
|
34
|
-
# `value.to_s`.
|
|
35
|
-
module SpinelKit
|
|
36
|
-
class Json
|
|
37
|
-
class Builder
|
|
38
|
-
def initialize
|
|
39
|
-
@buf = "{"
|
|
40
|
-
@first = true
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Append `"key":"escaped-value"`.
|
|
44
|
-
def add_str(key, value)
|
|
45
|
-
comma
|
|
46
|
-
@buf = @buf + Builder.quote(key) + ":" + Builder.quote(value)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Append `"key":<number>` -- `value.to_s` covers Integer ("5") and
|
|
50
|
-
# Float ("1.5"). For hardcoded literals prefer add_raw.
|
|
51
|
-
def add_num(key, value)
|
|
52
|
-
comma
|
|
53
|
-
@buf = @buf + Builder.quote(key) + ":" + value.to_s
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Append `"key":true|false`.
|
|
57
|
-
def add_bool(key, value)
|
|
58
|
-
comma
|
|
59
|
-
@buf = @buf + Builder.quote(key) + ":" + (value ? "true" : "false")
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Append `"key":<already-encoded JSON>` -- for arrays / numeric literals.
|
|
63
|
-
def add_raw(key, raw)
|
|
64
|
-
comma
|
|
65
|
-
@buf = @buf + Builder.quote(key) + ":" + raw
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Append `"key":<nested object>` from another Builder.
|
|
69
|
-
def add_obj(key, child)
|
|
70
|
-
comma
|
|
71
|
-
@buf = @buf + Builder.quote(key) + ":" + child.dump
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Close the object and return the JSON string.
|
|
75
|
-
def dump
|
|
76
|
-
@buf + "}"
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Emit a separator before every entry except the first.
|
|
80
|
-
def comma
|
|
81
|
-
if @first
|
|
82
|
-
@first = false
|
|
83
|
-
else
|
|
84
|
-
@buf = @buf + ","
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# ---- self-contained string escaping (byte-identical to
|
|
89
|
-
# SpinelKit::Json.escape/quote/hex2; kept local so a builder-only
|
|
90
|
-
# compile never pulls in the codec) ----
|
|
91
|
-
|
|
92
|
-
# Wrap a string in JSON quotes, escaping its body.
|
|
93
|
-
def self.quote(s)
|
|
94
|
-
"\"" + Builder.escape(s) + "\""
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Escape a string for inclusion inside a JSON string literal (no
|
|
98
|
-
# surrounding quotes). Handles ", \, and the JSON control-char escapes
|
|
99
|
-
# (\b \f \n \r \t); other control bytes go through \u00XX. ASCII-clean
|
|
100
|
-
# input passes through unchanged.
|
|
101
|
-
def self.escape(s)
|
|
102
|
-
out = ""
|
|
103
|
-
i = 0
|
|
104
|
-
n = s.length
|
|
105
|
-
while i < n
|
|
106
|
-
c = s[i]
|
|
107
|
-
if c == "\""
|
|
108
|
-
out = out + "\\\""
|
|
109
|
-
elsif c == "\\"
|
|
110
|
-
out = out + "\\\\"
|
|
111
|
-
elsif c == "\n"
|
|
112
|
-
out = out + "\\n"
|
|
113
|
-
elsif c == "\r"
|
|
114
|
-
out = out + "\\r"
|
|
115
|
-
elsif c == "\t"
|
|
116
|
-
out = out + "\\t"
|
|
117
|
-
elsif c == "\b"
|
|
118
|
-
out = out + "\\b"
|
|
119
|
-
elsif c == "\f"
|
|
120
|
-
out = out + "\\f"
|
|
121
|
-
elsif c < " "
|
|
122
|
-
b = c.getbyte(0)
|
|
123
|
-
out = out + "\\u00" + Builder.hex2(b)
|
|
124
|
-
else
|
|
125
|
-
out = out + c
|
|
126
|
-
end
|
|
127
|
-
i += 1
|
|
128
|
-
end
|
|
129
|
-
out
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Two-digit lowercase hex of a byte (0..255).
|
|
133
|
-
def self.hex2(n)
|
|
134
|
-
hex = "0123456789abcdef"
|
|
135
|
-
out = ""
|
|
136
|
-
out = out + hex[(n / 16) % 16, 1]
|
|
137
|
-
out = out + hex[n % 16, 1]
|
|
138
|
-
out
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|