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.
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.1.1
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 "stdlib substitute" shims that every
14
- Spinel-compiled project would otherwise hand-roll. Spinel (the Ruby->native
15
- AOT compiler) cannot lower the json gem's C-ext fast path / metaprogrammed
16
- fallback, stdlib Logger, or C-ext git bindings -- and the spinelgems
17
- compatibility catalog confirms no verified gem exists to reuse. So this gem
18
- ships a JSON encoder+decoder+builder, .git/HEAD provenance, and a minimal
19
- levelled logger. Pure Ruby, no native extension, no runtime dependencies --
20
- it vendors cleanly via bundler-spinel. Pre-alpha.
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/json.rbs
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
- - lib
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: 'The Spinel stdlib-surface gem: pure-Ruby JSON/Git/Log shims for AOT-compiled
74
- apps'
72
+ summary: Pure-Ruby Git/Url/Log/Hex shims for Spinel AOT-compiled apps
75
73
  test_files: []
@@ -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