spinel_kit 0.2.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 +33 -0
- data/README.md +83 -54
- data/docs/adoption.md +8 -0
- data/docs/spinel-discipline.md +17 -20
- data/spin.toml +6 -0
- data/{lib/spinel_kit → spinel_kit}/hex.rb +3 -7
- data/spinel_kit/version.rb +6 -0
- data/spinel_kit.rb +39 -0
- metadata +18 -24
- 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 -45
- 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
- /data/{lib/spinel_kit → spinel_kit}/url.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fca4df06c4d57130a5053954a6429beac7d6ce0d6f6d1ee26194753aea11732c
|
|
4
|
+
data.tar.gz: 496c1116e14068768cb482723d1be6c6daebfc26f45d9972da3ab36d572eae69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d15a4186fd29c584767e64c7f015b6424ce72bae04d39b283529f48478923075662bc2ecdc5e0ac03a73c51bcc344a9140cc775123a33c02e2dc76f9860cf73
|
|
7
|
+
data.tar.gz: 4d0ee98f92b44fa1e1345efdac6378626c31ba11c42cf0a6be12ab065f9e9854799eec16edb363da670b01eacd8421623631d2259c296f66f8b9892cd6f2eaa5
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to SpinelKit are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-07-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **Converted to a spin package** (spin is Spinel's package system;
|
|
9
|
+
[#3](https://github.com/OriPekelman/spinelkit/issues/3)). The package root
|
|
10
|
+
is now the require root: `lib/spinel_kit{,.rb}` moved to the repo top level,
|
|
11
|
+
`spin.toml` carries the identity (`name = "spinel_kit"`), and the gem
|
|
12
|
+
mirrors the layout with `require_paths = ["."]` — one tree serves both
|
|
13
|
+
consumers. Tests split by oracle: `test/*.rb` are compiled `spin test`
|
|
14
|
+
snapshot programs with committed `.expected` files (verified byte-identical
|
|
15
|
+
under CRuby and under spinel master `f9a81b1d`, 4/4); the CRuby minitest
|
|
16
|
+
parity suite moved to `test/parity/`.
|
|
17
|
+
|
|
18
|
+
### Removed
|
|
19
|
+
- **`SpinelKit::Json` and `SpinelKit::Json::Builder`** (`lib/spinel_kit/json.rb`,
|
|
20
|
+
`json_decoder.rb`, `json_builder.rb`, their `sig/` RBS and the parity test).
|
|
21
|
+
Spinel now bundles `json` as a require-gated stdlib package — a typed native
|
|
22
|
+
binding (`native_func :parse/:generate/:dump`) into the runtime's
|
|
23
|
+
`sp_json.c` — so the compiler provides the stdlib surface SpinelKit was
|
|
24
|
+
shimming ([#3](https://github.com/OriPekelman/spinelkit/issues/3)).
|
|
25
|
+
Verified on spinel master `f9a81b1d` with a tep-shaped consumer under
|
|
26
|
+
`SPINEL_REQUIRE_GATE=1`: byte-identical CRuby parity and **no
|
|
27
|
+
cross-degrade**; values from `JSON.parse` must be coerced at the boundary
|
|
28
|
+
(`.to_s`/`.to_i`/`.to_f`/`.map { |x| x.to_i }`) to stay fully typed —
|
|
29
|
+
without coercion, direct sinks widen to `untyped` (slow path, still
|
|
30
|
+
correct output).
|
|
31
|
+
|
|
32
|
+
**Migration:** `require "json"`; `JSON.parse(s)["k"].to_i` replaces
|
|
33
|
+
`SpinelKit::Json.get_int(s, "k")` (same shape for `get_str`/`get_float`/
|
|
34
|
+
`get_int_array`); `h.key?("k")` replaces `has_key?(s, "k")`;
|
|
35
|
+
`JSON.generate(hash)` replaces `encode_pair_*`/`from_*` and the Builder
|
|
36
|
+
(Ruby hashes are insertion-ordered; `native_obj_reflect` covers Structs).
|
|
37
|
+
|
|
5
38
|
## [0.2.0] - 2026-06-08
|
|
6
39
|
|
|
7
40
|
### Added
|
data/README.md
CHANGED
|
@@ -6,18 +6,38 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
**The Spinel
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
**The non-stdlib toolkit for Spinel-compiled apps.** A pure-Ruby, Spinel-safe
|
|
10
|
+
library holding the shims [Spinel](https://github.com/matz/spinel) (the
|
|
11
|
+
Ruby→native AOT compiler) does not provide and that every Spinel project
|
|
12
|
+
would otherwise hand-roll: **git provenance, URL percent-codec + query
|
|
13
|
+
parsing, a minimal levelled logger, and a hex codec**.
|
|
14
|
+
[toy](https://github.com/OriPekelman/toy) and
|
|
15
|
+
[tep](https://github.com/OriPekelman/tep) grew these independently — parts
|
|
16
|
+
came out *byte-identical* — and SpinelKit is that code, consolidated once.
|
|
17
|
+
|
|
18
|
+
## Where JSON went
|
|
19
|
+
|
|
20
|
+
SpinelKit began as "the stdlib-surface gem," and its founding surface was a
|
|
21
|
+
JSON codec. Spinel now **bundles `json` as a require-gated stdlib package** —
|
|
22
|
+
a typed native binding straight into the runtime's `sp_json.c` — so the
|
|
23
|
+
compiler provides the stdlib surface itself and `SpinelKit::Json` was retired
|
|
24
|
+
in 0.3.0 ([#3](https://github.com/OriPekelman/spinelkit/issues/3)). Migration:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require "json" # bare stdlib require, no manifest entry
|
|
28
|
+
|
|
29
|
+
h = JSON.parse(body)
|
|
30
|
+
model = h["model"].to_s # coerce at the parse boundary and
|
|
31
|
+
max_tokens = h["max_tokens"].to_i # everything downstream stays fully
|
|
32
|
+
temp = h["temperature"].to_f # specialized under whole-program
|
|
33
|
+
ids = h["prompt"].map { |x| x.to_i } # inference
|
|
34
|
+
|
|
35
|
+
JSON.generate({ "ok" => true, "n" => max_tokens }) # replaces the Builder
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Coerce parsed values once at the boundary (`.to_s`/`.to_i`/`.to_f`/mapped
|
|
39
|
+
`.to_i`) — verified against spinel master: byte-identical CRuby parity, no
|
|
40
|
+
cross-degrade, and full re-typification with the coercions in place.
|
|
21
41
|
|
|
22
42
|
## Installation
|
|
23
43
|
|
|
@@ -38,20 +58,20 @@ cleanly into a Spinel build via [bundler-spinel](https://github.com/OriPekelman/
|
|
|
38
58
|
|
|
39
59
|
The first thing we did was audit the
|
|
40
60
|
[spinelgems](https://github.com/OriPekelman/spinelgems) compatibility catalog
|
|
41
|
-
(verdict ladder: `verified > loaded > clean > risky > rejected`) for
|
|
42
|
-
|
|
43
|
-
shims *are* the ecosystem's gaps:
|
|
61
|
+
(verdict ladder: `verified > loaded > clean > risky > rejected`) for existing
|
|
62
|
+
gems to reuse — that would have been the biggest win. **There weren't any.**
|
|
63
|
+
These shims *are* the ecosystem's gaps:
|
|
44
64
|
|
|
45
65
|
| Surface | Catalog finding | Decision |
|
|
46
66
|
|---------|-----------------|----------|
|
|
47
|
-
| JSON | `json` **rejected** (C-ext + metaprogrammed fallback); `oj` **risky** (C-ext) | implement |
|
|
48
67
|
| Log | `logger` **rejected** (unresolved calls) | implement |
|
|
49
68
|
| Git | `rugged` **rejected** (C); `gitkite`/`git_manager` only **clean**, unmet `needs:` | implement (read `.git/HEAD`) |
|
|
69
|
+
| Url/Hex | `CGI`/`URI` stdlib not lowered; no verified gem | implement |
|
|
50
70
|
| Path | `hike` **verified** at an older rev, only **loaded** now; overkill for basename/join | deferred |
|
|
51
71
|
| Bytes | `unicode_utils`/`utf8-cleaner` **clean** only; toy's need is tokenizer-specific | deferred |
|
|
52
72
|
|
|
53
|
-
See [`docs/gem-audit-first.md`](docs/gem-audit-first.md) for the full audit
|
|
54
|
-
|
|
73
|
+
See [`docs/gem-audit-first.md`](docs/gem-audit-first.md) for the full audit
|
|
74
|
+
(which also covers the since-retired JSON surface).
|
|
55
75
|
|
|
56
76
|
## What's in it
|
|
57
77
|
|
|
@@ -61,31 +81,8 @@ require "spinel_kit" # everything (CRuby / convenience)
|
|
|
61
81
|
|
|
62
82
|
For a **Spinel-compiled** consumer, require only the surface you use — Spinel
|
|
63
83
|
has no tree-shaking, so every loaded method is compiled (and an uncalled one
|
|
64
|
-
can degrade)
|
|
65
|
-
|
|
66
|
-
`spinel_kit/json_builder` (the builder). e.g. tep requires `json` + `json_decoder`;
|
|
67
|
-
toy requires `json_builder`.
|
|
68
|
-
|
|
69
|
-
- **`SpinelKit::Json`** — a JSON-over-HTTP codec: encoders
|
|
70
|
-
(`escape`/`quote`/`encode_pair_*`/`from_*`, in `spinel_kit/json`) and flat-key
|
|
71
|
-
decoders (`get_str`/`get_int`/`get_float`/`get_int_array`/`has_key?`, in
|
|
72
|
-
`spinel_kit/json_decoder`).
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
SpinelKit::Json.get_int('{"age":33}', "age") # => 33
|
|
76
|
-
SpinelKit::Json.from_int_hash({"a" => 1, "b" => 2}) # => {"a":1,"b":2}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
- **`SpinelKit::Json::Builder`** — an incremental ordered-object builder
|
|
80
|
-
(`add_str`/`add_num`/`add_bool`/`add_raw`/`add_obj`/`dump`), in its own file
|
|
81
|
-
so a builder-only consumer never compiles the codec, and vice versa.
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
j = SpinelKit::Json::Builder.new
|
|
85
|
-
j.add_str("kind", "run_start")
|
|
86
|
-
j.add_num("t", 1715000000)
|
|
87
|
-
j.dump # => {"kind":"run_start","t":1715000000}
|
|
88
|
-
```
|
|
84
|
+
can degrade): `spinel_kit/hex`, `spinel_kit/url` (pulls in hex),
|
|
85
|
+
`spinel_kit/git`, `spinel_kit/log`.
|
|
89
86
|
|
|
90
87
|
- **`SpinelKit::Git`** — git provenance from `.git/HEAD`.
|
|
91
88
|
|
|
@@ -95,6 +92,15 @@ toy requires `json_builder`.
|
|
|
95
92
|
g.branch # => "main"
|
|
96
93
|
```
|
|
97
94
|
|
|
95
|
+
- **`SpinelKit::Url`** — the `CGI`/`URI`-component surface:
|
|
96
|
+
`escape`/`unescape` (RFC 3986 percent-codec), `parse_query`
|
|
97
|
+
(form-urlencoded → Hash), `split_url` (scheme/host/port/path/query).
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
SpinelKit::Url.escape("a b&c") # => "a%20b%26c"
|
|
101
|
+
SpinelKit::Url.parse_query("x=1&y=z") # => {"x"=>"1", "y"=>"z"}
|
|
102
|
+
```
|
|
103
|
+
|
|
98
104
|
- **`SpinelKit::Log`** — a minimal levelled logger (CRuby `Logger` doesn't
|
|
99
105
|
compile under Spinel).
|
|
100
106
|
|
|
@@ -104,6 +110,9 @@ toy requires `json_builder`.
|
|
|
104
110
|
log.info("server up")
|
|
105
111
|
```
|
|
106
112
|
|
|
113
|
+
- **`SpinelKit::Hex`** — hex digit/byte encode + decode (`nibble`,
|
|
114
|
+
`nibble_char`, `byte2`, `to_int`); the shared surface `Url` builds on.
|
|
115
|
+
|
|
107
116
|
## Design constraints (read before editing)
|
|
108
117
|
|
|
109
118
|
SpinelKit is **pure Ruby, no native extension** (`spinel-ext.json` is `[]`) and
|
|
@@ -111,24 +120,44 @@ has **no runtime dependencies**, so it vendors cleanly via `bundler-spinel`. The
|
|
|
111
120
|
surface uses plain, standard names; the `j_`/`tj_`/`gi_` prefixes the donor
|
|
112
121
|
copies carried were a workaround for a Spinel whole-program-inference bug that
|
|
113
122
|
has since been fixed upstream (verified with toy's `gate-poly-degrade` on the
|
|
114
|
-
current compiler).
|
|
115
|
-
|
|
123
|
+
current compiler). See [`docs/spinel-discipline.md`](docs/spinel-discipline.md).
|
|
124
|
+
|
|
125
|
+
## Package formats: spin package + gem, one tree
|
|
126
|
+
|
|
127
|
+
This repo **is a [spin](https://github.com/matz/spin-index) package** (spin is
|
|
128
|
+
Spinel's package system): the package root is the require root
|
|
129
|
+
(`spinel_kit.rb` + `spinel_kit/` at top level), `spin.toml` carries the
|
|
130
|
+
identity, and the top-level `test/*.rb` programs are compiled snapshot tests
|
|
131
|
+
(`spin test` — each diffs against its committed `.expected`, byte-identical
|
|
132
|
+
under CRuby too). Spinel projects take it with:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
spin add spinel_kit # index form, once seeded — or:
|
|
136
|
+
spin add spinel_kit --git https://github.com/OriPekelman/spinelkit
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The same tree still ships as the `spinel_kit` **gem** for CRuby-side
|
|
140
|
+
consumers (`require_paths` is the package root), and vendors via
|
|
141
|
+
`bundler-spinel` as before.
|
|
116
142
|
|
|
117
143
|
## Status
|
|
118
144
|
|
|
119
|
-
Pre-alpha (`0.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
[
|
|
145
|
+
Pre-alpha (`0.3.0` — the JSON retirement + spin package conversion). tep and
|
|
146
|
+
toy migrated to `SpinelKit::*` directly and deleted their donor modules (see
|
|
147
|
+
[`docs/adoption.md`](docs/adoption.md)); their JSON call sites migrate to the
|
|
148
|
+
bundled stdlib `json` next
|
|
149
|
+
([tep#217](https://github.com/OriPekelman/tep/issues/217),
|
|
150
|
+
[tep#213](https://github.com/OriPekelman/tep/issues/213)), and their
|
|
151
|
+
dependency declarations move to `spin.toml`
|
|
152
|
+
([tep#234](https://github.com/OriPekelman/tep/issues/234),
|
|
153
|
+
[toy#107](https://github.com/OriPekelman/toy/issues/107)). Repositioning
|
|
154
|
+
tracked in [#3](https://github.com/OriPekelman/spinelkit/issues/3).
|
|
127
155
|
|
|
128
156
|
## Development
|
|
129
157
|
|
|
130
158
|
```sh
|
|
131
|
-
rake test # CRuby-side parity tests (never compiled)
|
|
159
|
+
rake test # CRuby-side parity tests, test/parity/ (never compiled)
|
|
160
|
+
spin test # compiled snapshot tests, test/*.rb vs .expected
|
|
132
161
|
rake rbs:validate # syntax-check the advisory RBS in sig/
|
|
133
162
|
gem build spinel_kit.gemspec
|
|
134
163
|
```
|
data/docs/adoption.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Adoption — standardize and clean (not shim-and-alias)
|
|
2
2
|
|
|
3
|
+
> **Historical note (2026-07-08).** This doc describes the 0.1–0.2 era
|
|
4
|
+
> migration of tep and toy onto `SpinelKit::*`, including the Json surface.
|
|
5
|
+
> `SpinelKit::Json`/`Builder` were **retired in 0.3.0** — Spinel now bundles
|
|
6
|
+
> `json` as a stdlib package, and the Json call sites below migrate again, to
|
|
7
|
+
> bare `require "json"` (see
|
|
8
|
+
> [#3](https://github.com/OriPekelman/spinelkit/issues/3) and the CHANGELOG
|
|
9
|
+
> migration note). The Git/Url/Log/Hex parts remain accurate.
|
|
10
|
+
|
|
3
11
|
Bootstrap landed the gem and its three shims. Adoption by tep and toy is the
|
|
4
12
|
next phase. **Because we author every repo in this set — toy, tep, spinelgems,
|
|
5
13
|
and Spinel itself — the goal is to standardize and clean, not to bolt
|
data/docs/spinel-discipline.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
SpinelKit ships code that gets compiled into *other* programs by Spinel's
|
|
4
4
|
whole-program AOT compiler. That changes the rules for how it must be written.
|
|
5
|
-
This doc is the contract for anyone editing `
|
|
5
|
+
This doc is the contract for anyone editing `spinel_kit/*.rb`.
|
|
6
6
|
|
|
7
7
|
## 1. The name-keyed inference bug — FIXED, prefixes dropped
|
|
8
8
|
|
|
@@ -35,28 +35,24 @@ names (`escape`/`quote`/`add_str`/`sha`/`branch`/plain `value`).
|
|
|
35
35
|
- **Split the surface so consumers don't compile dead code.** Spinel has no
|
|
36
36
|
tree-shaking: every loaded method is compiled, and a *set* of uncalled
|
|
37
37
|
methods can degrade each other's (and nearby live methods') param types. We
|
|
38
|
-
saw this concretely — with the encoders and
|
|
39
|
-
encode-only program left the 9 decoder walkers
|
|
40
|
-
collapsed to `int` and dragged `escape`'s `s` to
|
|
41
|
-
emitting `""` keys
|
|
42
|
-
(`
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`57af7f9`. A consumer that loads a surface but leaves part of it uncalled can
|
|
47
|
-
still trip the gate; that's expected, and the gate is what flags it.
|
|
48
|
-
- **`escape`/`quote`/`hex2` are single canonical methods** — the builder
|
|
49
|
-
carries its own byte-identical copies (so a builder-only compile pulls in no
|
|
50
|
-
codec); there is no `tj_*` prefix any more.
|
|
51
|
-
- **Keep `get_float` inlined** (it does NOT delegate to a `parse_float_value`
|
|
52
|
-
helper). This is unrelated to the name bug — it's a value-walk *indirection*
|
|
53
|
-
issue where Spinel mis-widened the string arg `s` to int through the helper
|
|
54
|
-
call. Until that's separately confirmed fixed, leave it inlined.
|
|
38
|
+
saw this concretely in the retired Json surface — with the encoders and
|
|
39
|
+
decoders in one class, an encode-only program left the 9 decoder walkers
|
|
40
|
+
dead; their `s` params collapsed to `int` and dragged `escape`'s `s` to
|
|
41
|
+
`int` too, silently emitting `""` keys. The fix was structural: one
|
|
42
|
+
coherent surface per file (today: `hex.rb`, `url.rb`, `git.rb`, `log.rb`),
|
|
43
|
+
so a consumer loads only what it actually exercises. A consumer that loads
|
|
44
|
+
a surface but leaves part of it uncalled can still trip the gate; that's
|
|
45
|
+
expected, and the gate is what flags it.
|
|
55
46
|
- **Never override `to_s`** — it merges across the whole program.
|
|
56
47
|
- The stale cautionary comments in toy's `toy_json.rb`/`toy_git.rb` and the
|
|
57
48
|
landmine memory note should be annotated "fixed upstream by `ac7720e`/
|
|
58
49
|
`23ba632`, verified on `57af7f9`" when those repos are next touched.
|
|
59
50
|
|
|
51
|
+
(Historical, retired with the Json surface in 0.3.0: the three-file Json
|
|
52
|
+
split, the builder's byte-identical private escapers, and the keep-`get_float`
|
|
53
|
+
-inlined rule. The last known instance of the value-walk indirection issue was
|
|
54
|
+
verified fixed on master `b60fbd7`.)
|
|
55
|
+
|
|
60
56
|
## 2. The poly-degrade gate (how consumers verify SpinelKit is safe)
|
|
61
57
|
|
|
62
58
|
Each consumer has a poly-degrade scan that compiles a canonical entrypoint and
|
|
@@ -101,6 +97,7 @@ destined for Spinel. SpinelKit's own decoders follow this rule.
|
|
|
101
97
|
runs, but file output is the portable path.
|
|
102
98
|
- `Time.now` exposes integer seconds only (no rich `strftime`) — `Log` formats
|
|
103
99
|
with `Time.now.to_i`.
|
|
104
|
-
- `Integer#chr` is not uniform for arbitrary bytes —
|
|
105
|
-
printable-ASCII table with a `"?"` fallback
|
|
100
|
+
- `Integer#chr` is not uniform for arbitrary bytes — the retired Json surface
|
|
101
|
+
used a printable-ASCII table with a `"?"` fallback; apply the same pattern
|
|
102
|
+
if a surface ever needs byte→char again.
|
|
106
103
|
- No C extensions — `spinel-ext.json` is `[]`.
|
data/spin.toml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# spin package manifest — SpinelKit as an ordinary spin package.
|
|
2
|
+
# The package NAME is the require string (`require "spinel_kit/git"` etc.);
|
|
3
|
+
# the repo keeps its historical name. See issue #3 for the repositioning.
|
|
4
|
+
[package]
|
|
5
|
+
name = "spinel_kit"
|
|
6
|
+
version = "0.3.0"
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# SpinelKit::Hex -- hex digit/byte encode + decode: the pieces every Spinel
|
|
2
2
|
# project re-rolls. The decode nibble appeared BYTE-IDENTICAL in Tep::Url,
|
|
3
|
-
#
|
|
4
|
-
# Tep::Llm's chunked-transfer size parser. Pure string/byte ops,
|
|
5
|
-
#
|
|
6
|
-
# NOTE on the Json overlap: SpinelKit::Json keeps its OWN private `hex2`/
|
|
7
|
-
# `hex_nibble` so a JSON-only consumer never compiles this file (Spinel has no
|
|
8
|
-
# tree-shaking — see json.rb). Hex is the shared surface for everyone else,
|
|
9
|
-
# e.g. SpinelKit::Url.
|
|
3
|
+
# in the retired SpinelKit::Json string decoder, and (as a multi-digit
|
|
4
|
+
# variant) in Tep::Llm's chunked-transfer size parser. Pure string/byte ops,
|
|
5
|
+
# Spinel-safe. Hex is the shared surface, e.g. for SpinelKit::Url.
|
|
10
6
|
module SpinelKit
|
|
11
7
|
class Hex
|
|
12
8
|
# Hex digit char -> int 0..15, or -1 if not a hex digit (upper or lower).
|
data/spinel_kit.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SpinelKit -- the non-stdlib toolkit for Spinel-compiled apps.
|
|
2
|
+
#
|
|
3
|
+
# A pure-Ruby, Spinel-safe toolkit holding the shims Spinel (the Ruby->native
|
|
4
|
+
# AOT compiler) does not provide and that every Spinel-compiled project would
|
|
5
|
+
# otherwise hand-roll:
|
|
6
|
+
#
|
|
7
|
+
# SpinelKit::Hex -- hex digit/byte encode + decode.
|
|
8
|
+
# SpinelKit::Url -- percent-encode/decode + form-query + URL split.
|
|
9
|
+
# SpinelKit::Git -- git provenance from .git/HEAD (was Toy::Git).
|
|
10
|
+
# SpinelKit::Log -- minimal levelled logger (was Tep::Logger).
|
|
11
|
+
#
|
|
12
|
+
# JSON is NOT here any more. Spinel bundles `json` as a require-gated stdlib
|
|
13
|
+
# package (a typed native binding into sp_json.c), so consumers write a bare
|
|
14
|
+
# `require "json"` and use JSON.parse / JSON.generate, coercing parsed values
|
|
15
|
+
# at the boundary (`.to_s` / `.to_i` / `.to_f` / `.map { |x| x.to_i }`) to
|
|
16
|
+
# keep inference fully typed. The old SpinelKit::Json codec + Builder were
|
|
17
|
+
# retired in 0.3.0 -- see CHANGELOG.md and issue #3.
|
|
18
|
+
#
|
|
19
|
+
# MINIMAL-SURFACE REQUIRING. This umbrella requires everything for
|
|
20
|
+
# convenience (and for CRuby use). But because Spinel compiles every loaded
|
|
21
|
+
# method with no tree-shaking, a Spinel-compiled consumer should require ONLY
|
|
22
|
+
# the file(s) it uses, to avoid compiling -- and degrading -- code it never
|
|
23
|
+
# calls:
|
|
24
|
+
#
|
|
25
|
+
# require "spinel_kit/hex" # hex digit/byte codec
|
|
26
|
+
# require "spinel_kit/url" # percent-codec + query (pulls in hex)
|
|
27
|
+
# require "spinel_kit/git"
|
|
28
|
+
# require "spinel_kit/log"
|
|
29
|
+
#
|
|
30
|
+
# No native extension (spinel-ext.json is []), no runtime dependencies. See
|
|
31
|
+
# docs/adoption.md and docs/spinel-discipline.md.
|
|
32
|
+
require_relative "spinel_kit/version"
|
|
33
|
+
require_relative "spinel_kit/hex"
|
|
34
|
+
require_relative "spinel_kit/url"
|
|
35
|
+
require_relative "spinel_kit/git"
|
|
36
|
+
require_relative "spinel_kit/log"
|
|
37
|
+
|
|
38
|
+
module SpinelKit
|
|
39
|
+
end
|
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,24 +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/hex.rb
|
|
37
|
-
- lib/spinel_kit/json.rb
|
|
38
|
-
- lib/spinel_kit/json_builder.rb
|
|
39
|
-
- lib/spinel_kit/json_decoder.rb
|
|
40
|
-
- lib/spinel_kit/log.rb
|
|
41
|
-
- lib/spinel_kit/url.rb
|
|
42
|
-
- lib/spinel_kit/version.rb
|
|
43
34
|
- sig/spinel_kit/git.rbs
|
|
44
35
|
- sig/spinel_kit/hex.rbs
|
|
45
|
-
- sig/spinel_kit/json.rbs
|
|
46
|
-
- sig/spinel_kit/json_builder.rbs
|
|
47
|
-
- sig/spinel_kit/json_decoder.rbs
|
|
48
36
|
- sig/spinel_kit/log.rbs
|
|
49
37
|
- sig/spinel_kit/url.rbs
|
|
50
38
|
- sig/spinel_kit/version.rbs
|
|
39
|
+
- spin.toml
|
|
51
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
|
|
52
47
|
homepage: https://github.com/OriPekelman/spinelkit
|
|
53
48
|
licenses:
|
|
54
49
|
- MIT
|
|
@@ -60,7 +55,7 @@ metadata:
|
|
|
60
55
|
rubygems_mfa_required: 'true'
|
|
61
56
|
rdoc_options: []
|
|
62
57
|
require_paths:
|
|
63
|
-
-
|
|
58
|
+
- "."
|
|
64
59
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
60
|
requirements:
|
|
66
61
|
- - ">="
|
|
@@ -74,6 +69,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
74
69
|
requirements: []
|
|
75
70
|
rubygems_version: 3.6.9
|
|
76
71
|
specification_version: 4
|
|
77
|
-
summary:
|
|
78
|
-
apps'
|
|
72
|
+
summary: Pure-Ruby Git/Url/Log/Hex shims for Spinel AOT-compiled apps
|
|
79
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
|
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
# SpinelKit::Json -- Spinel-safe JSON DECODERS (flat-key, top-level only).
|
|
2
|
-
#
|
|
3
|
-
# The decode half of the codec; encoders are in spinel_kit/json.rb. Split out
|
|
4
|
-
# so an encode-only consumer never compiles these walkers (their dead-code
|
|
5
|
-
# degradation otherwise widens the encoders' string args to int -- see the
|
|
6
|
-
# header of json.rb and docs/spinel-discipline.md).
|
|
7
|
-
#
|
|
8
|
-
# `get_str(s, key)` finds the entry for `key` in the top-level object literal
|
|
9
|
-
# `s` and returns its value as a string. Returns "" when `key` is absent or
|
|
10
|
-
# the value isn't a string. Same shape for `get_int`. `has_key?(s, key)`
|
|
11
|
-
# returns a boolean independent of value type. The parser is a hand-rolled
|
|
12
|
-
# state machine that walks one `{ "k": <value>, ... }` pair at a time,
|
|
13
|
-
# skipping over any value (including nested objects / arrays) it doesn't need.
|
|
14
|
-
# Strings inside values are honoured for escape sequences so that `\"` doesn't
|
|
15
|
-
# terminate the string and corrupt the walk. Decodes the escape sequences
|
|
16
|
-
# `SpinelKit::Json.escape` produces.
|
|
17
|
-
module SpinelKit
|
|
18
|
-
class Json
|
|
19
|
-
def self.get_str(s, key)
|
|
20
|
-
pos = Json.find_value_start(s, key)
|
|
21
|
-
if pos < 0
|
|
22
|
-
return ""
|
|
23
|
-
end
|
|
24
|
-
Json.parse_str_value(s, pos)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def self.get_int(s, key)
|
|
28
|
-
pos = Json.find_value_start(s, key)
|
|
29
|
-
if pos < 0
|
|
30
|
-
return 0
|
|
31
|
-
end
|
|
32
|
-
Json.parse_int_value(s, pos)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Decode a JSON number value at `key` -> Float. Accepts both
|
|
36
|
-
# integer-literal (`42`) and float-literal (`3.14`, `-0.5`, `1e2`)
|
|
37
|
-
# JSON-number syntax; the integer form returns N.0. Missing key or
|
|
38
|
-
# malformed value returns 0.0 (consistent with the other getters'
|
|
39
|
-
# missing-key defaults).
|
|
40
|
-
#
|
|
41
|
-
# Implementation: delegates the value-span walking to skip_value (already
|
|
42
|
-
# handles all JSON-number syntax + structural-char boundaries), then
|
|
43
|
-
# String#to_f on the substring. Inlined rather than factored into a
|
|
44
|
-
# parse_float_value helper because spinel's type inference mis-widens `s`
|
|
45
|
-
# to int through the indirection. NOTE: that is a value-walk indirection
|
|
46
|
-
# concern, NOT the name-collision bug (which was fixed) -- keep it inlined.
|
|
47
|
-
def self.get_float(s, key)
|
|
48
|
-
pos = Json.find_value_start(s, key)
|
|
49
|
-
if pos < 0
|
|
50
|
-
return 0.0
|
|
51
|
-
end
|
|
52
|
-
pos = Json.skip_ws(s, pos)
|
|
53
|
-
if pos >= s.length
|
|
54
|
-
return 0.0
|
|
55
|
-
end
|
|
56
|
-
end_pos = Json.skip_value(s, pos)
|
|
57
|
-
if end_pos <= pos
|
|
58
|
-
return 0.0
|
|
59
|
-
end
|
|
60
|
-
s[pos, end_pos - pos].to_f
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def self.has_key?(s, key)
|
|
64
|
-
Json.find_value_start(s, key) >= 0
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Decode a flat JSON array of integers at `key` -> Array[Integer].
|
|
68
|
-
# A missing or non-array value yields [] (the typed-empty-array idiom);
|
|
69
|
-
# non-int elements are skipped.
|
|
70
|
-
def self.get_int_array(s, key)
|
|
71
|
-
out = [0]
|
|
72
|
-
out.delete_at(0)
|
|
73
|
-
pos = Json.find_value_start(s, key)
|
|
74
|
-
if pos < 0
|
|
75
|
-
return out
|
|
76
|
-
end
|
|
77
|
-
pos = Json.skip_ws(s, pos)
|
|
78
|
-
if pos >= s.length || s[pos] != "["
|
|
79
|
-
return out
|
|
80
|
-
end
|
|
81
|
-
pos += 1
|
|
82
|
-
while pos < s.length
|
|
83
|
-
pos = Json.skip_ws(s, pos)
|
|
84
|
-
if pos >= s.length
|
|
85
|
-
return out
|
|
86
|
-
end
|
|
87
|
-
c = s[pos]
|
|
88
|
-
if c == "]"
|
|
89
|
-
return out
|
|
90
|
-
elsif c == ","
|
|
91
|
-
pos += 1
|
|
92
|
-
elsif (c >= "0" && c <= "9") || c == "-"
|
|
93
|
-
out.push(Json.parse_int_value(s, pos))
|
|
94
|
-
# Advance past the number parse_int_value just consumed
|
|
95
|
-
# (optional '-' then digits).
|
|
96
|
-
if s[pos] == "-"
|
|
97
|
-
pos += 1
|
|
98
|
-
end
|
|
99
|
-
while pos < s.length && s[pos] >= "0" && s[pos] <= "9"
|
|
100
|
-
pos += 1
|
|
101
|
-
end
|
|
102
|
-
else
|
|
103
|
-
# Non-int element (string / object / etc.): skip it.
|
|
104
|
-
pos = Json.skip_value(s, pos)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
out
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# ---- Internal helpers ----
|
|
111
|
-
|
|
112
|
-
# Skip whitespace starting at `pos`, return the new position.
|
|
113
|
-
def self.skip_ws(s, pos)
|
|
114
|
-
while pos < s.length
|
|
115
|
-
c = s[pos]
|
|
116
|
-
if c == " " || c == "\t" || c == "\n" || c == "\r"
|
|
117
|
-
pos += 1
|
|
118
|
-
else
|
|
119
|
-
return pos
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
pos
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Walk a JSON-quoted string starting at `pos` (which must point at the
|
|
126
|
-
# opening `"`). Returns the position one past the closing `"`. Returns
|
|
127
|
-
# -1 on malformed input.
|
|
128
|
-
def self.skip_str(s, pos)
|
|
129
|
-
if pos >= s.length || s[pos] != "\""
|
|
130
|
-
return -1
|
|
131
|
-
end
|
|
132
|
-
pos += 1
|
|
133
|
-
while pos < s.length
|
|
134
|
-
c = s[pos]
|
|
135
|
-
if c == "\\"
|
|
136
|
-
# Skip the escape and the escaped character. \uXXXX spans 6
|
|
137
|
-
# chars total but skipping 2 still keeps us inside the string
|
|
138
|
-
# for the rest of the walk -- the remaining 4 hex digits look
|
|
139
|
-
# like ordinary string bytes and won't terminate the literal.
|
|
140
|
-
pos += 2
|
|
141
|
-
elsif c == "\""
|
|
142
|
-
return pos + 1
|
|
143
|
-
else
|
|
144
|
-
pos += 1
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
-1
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Walk a JSON value starting at `pos` (which must point at the first
|
|
151
|
-
# non-ws char of the value). Returns the position one past the value
|
|
152
|
-
# (or the input length on truncation).
|
|
153
|
-
def self.skip_value(s, pos)
|
|
154
|
-
pos = Json.skip_ws(s, pos)
|
|
155
|
-
if pos >= s.length
|
|
156
|
-
return pos
|
|
157
|
-
end
|
|
158
|
-
c = s[pos]
|
|
159
|
-
if c == "\""
|
|
160
|
-
return Json.skip_str(s, pos)
|
|
161
|
-
end
|
|
162
|
-
if c == "{" || c == "["
|
|
163
|
-
return Json.skip_container(s, pos)
|
|
164
|
-
end
|
|
165
|
-
# number / true / false / null -- read until the next structural /
|
|
166
|
-
# whitespace char.
|
|
167
|
-
while pos < s.length
|
|
168
|
-
c = s[pos]
|
|
169
|
-
if c == "," || c == "}" || c == "]" ||
|
|
170
|
-
c == " " || c == "\t" || c == "\n" || c == "\r"
|
|
171
|
-
return pos
|
|
172
|
-
end
|
|
173
|
-
pos += 1
|
|
174
|
-
end
|
|
175
|
-
pos
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
# Walk a balanced { ... } or [ ... ] starting at `pos`. Honours string
|
|
179
|
-
# literals so that `{` / `}` inside a value-string don't confuse the
|
|
180
|
-
# brace counter. Returns position one past the matching closer.
|
|
181
|
-
def self.skip_container(s, pos)
|
|
182
|
-
open_c = s[pos]
|
|
183
|
-
close_c = open_c == "{" ? "}" : "]"
|
|
184
|
-
depth = 1
|
|
185
|
-
pos += 1
|
|
186
|
-
while pos < s.length && depth > 0
|
|
187
|
-
c = s[pos]
|
|
188
|
-
if c == "\""
|
|
189
|
-
# whole nested string -- skip past it
|
|
190
|
-
npos = Json.skip_str(s, pos)
|
|
191
|
-
if npos < 0
|
|
192
|
-
return s.length
|
|
193
|
-
end
|
|
194
|
-
pos = npos
|
|
195
|
-
elsif c == open_c
|
|
196
|
-
depth += 1
|
|
197
|
-
pos += 1
|
|
198
|
-
elsif c == close_c
|
|
199
|
-
depth -= 1
|
|
200
|
-
pos += 1
|
|
201
|
-
else
|
|
202
|
-
pos += 1
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
pos
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Read a JSON-quoted string at `pos` and return its decoded contents
|
|
209
|
-
# (no surrounding quotes). Decodes the same escape sequences that
|
|
210
|
-
# `escape` produces. Returns "" on malformed input.
|
|
211
|
-
def self.parse_str_value(s, pos)
|
|
212
|
-
pos = Json.skip_ws(s, pos)
|
|
213
|
-
if pos >= s.length || s[pos] != "\""
|
|
214
|
-
return ""
|
|
215
|
-
end
|
|
216
|
-
pos += 1
|
|
217
|
-
out = ""
|
|
218
|
-
while pos < s.length
|
|
219
|
-
c = s[pos]
|
|
220
|
-
if c == "\""
|
|
221
|
-
return out
|
|
222
|
-
end
|
|
223
|
-
if c == "\\"
|
|
224
|
-
if pos + 1 >= s.length
|
|
225
|
-
return out
|
|
226
|
-
end
|
|
227
|
-
esc = s[pos + 1]
|
|
228
|
-
if esc == "\""
|
|
229
|
-
out = out + "\""
|
|
230
|
-
elsif esc == "\\"
|
|
231
|
-
out = out + "\\"
|
|
232
|
-
elsif esc == "/"
|
|
233
|
-
out = out + "/"
|
|
234
|
-
elsif esc == "n"
|
|
235
|
-
out = out + "\n"
|
|
236
|
-
elsif esc == "r"
|
|
237
|
-
out = out + "\r"
|
|
238
|
-
elsif esc == "t"
|
|
239
|
-
out = out + "\t"
|
|
240
|
-
elsif esc == "b"
|
|
241
|
-
out = out + "\b"
|
|
242
|
-
elsif esc == "f"
|
|
243
|
-
out = out + "\f"
|
|
244
|
-
elsif esc == "u"
|
|
245
|
-
# \u00XX -> map the two-digit hex back to a byte. Wider
|
|
246
|
-
# codepoints (U+0100+ or surrogate pairs) aren't decoded; the
|
|
247
|
-
# byte we emit is the low byte of the codepoint, which
|
|
248
|
-
# round-trips ASCII at minimum.
|
|
249
|
-
if pos + 5 < s.length
|
|
250
|
-
h1 = Json.hex_nibble(s[pos + 4])
|
|
251
|
-
h2 = Json.hex_nibble(s[pos + 5])
|
|
252
|
-
if h1 >= 0 && h2 >= 0
|
|
253
|
-
# rebuild the byte and push it -- spinel strings are
|
|
254
|
-
# byte-blobs, so this works for ASCII; for non-ASCII the
|
|
255
|
-
# original encoder would have used a passthrough byte
|
|
256
|
-
# anyway.
|
|
257
|
-
b = h1 * 16 + h2
|
|
258
|
-
out = out + Json.byte_to_chr(b)
|
|
259
|
-
pos += 6
|
|
260
|
-
next
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
out = out + "?"
|
|
264
|
-
pos += 2
|
|
265
|
-
next
|
|
266
|
-
else
|
|
267
|
-
out = out + esc
|
|
268
|
-
end
|
|
269
|
-
pos += 2
|
|
270
|
-
else
|
|
271
|
-
out = out + c
|
|
272
|
-
pos += 1
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
out
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def self.hex_nibble(c)
|
|
279
|
-
if c >= "0" && c <= "9"
|
|
280
|
-
return c.getbyte(0) - "0".getbyte(0)
|
|
281
|
-
end
|
|
282
|
-
if c >= "a" && c <= "f"
|
|
283
|
-
return c.getbyte(0) - "a".getbyte(0) + 10
|
|
284
|
-
end
|
|
285
|
-
if c >= "A" && c <= "F"
|
|
286
|
-
return c.getbyte(0) - "A".getbyte(0) + 10
|
|
287
|
-
end
|
|
288
|
-
-1
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Build a single-byte string from an integer 0..255. Spinel doesn't
|
|
292
|
-
# expose `n.chr` for arbitrary bytes uniformly; the table covers the
|
|
293
|
-
# ASCII printable range and falls back to "?" for anything else (the
|
|
294
|
-
# JSON encoder side never produces non-ASCII via \u, so the fallback
|
|
295
|
-
# is reachable only for malformed input).
|
|
296
|
-
def self.byte_to_chr(n)
|
|
297
|
-
printable = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
|
298
|
-
if n >= 32 && n < 127
|
|
299
|
-
return printable[n - 32, 1]
|
|
300
|
-
end
|
|
301
|
-
if n == 9
|
|
302
|
-
return "\t"
|
|
303
|
-
end
|
|
304
|
-
if n == 10
|
|
305
|
-
return "\n"
|
|
306
|
-
end
|
|
307
|
-
if n == 13
|
|
308
|
-
return "\r"
|
|
309
|
-
end
|
|
310
|
-
"?"
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# Read an integer at `pos`. Accepts an optional leading `-`. Returns 0
|
|
314
|
-
# on no-digit / non-numeric input (caller can use `has_key?` first if
|
|
315
|
-
# 0-vs-absent matters).
|
|
316
|
-
def self.parse_int_value(s, pos)
|
|
317
|
-
pos = Json.skip_ws(s, pos)
|
|
318
|
-
if pos >= s.length
|
|
319
|
-
return 0
|
|
320
|
-
end
|
|
321
|
-
neg = false
|
|
322
|
-
if s[pos] == "-"
|
|
323
|
-
neg = true
|
|
324
|
-
pos += 1
|
|
325
|
-
end
|
|
326
|
-
n = 0
|
|
327
|
-
saw_digit = false
|
|
328
|
-
while pos < s.length
|
|
329
|
-
c = s[pos]
|
|
330
|
-
if c >= "0" && c <= "9"
|
|
331
|
-
n = n * 10 + (c.getbyte(0) - "0".getbyte(0))
|
|
332
|
-
saw_digit = true
|
|
333
|
-
pos += 1
|
|
334
|
-
else
|
|
335
|
-
break
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
if !saw_digit
|
|
339
|
-
return 0
|
|
340
|
-
end
|
|
341
|
-
neg ? -n : n
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
# Walk the top-level object looking for the entry whose key matches
|
|
345
|
-
# `target_key`; return the position of the value's first non-ws
|
|
346
|
-
# character. Returns -1 if not found.
|
|
347
|
-
def self.find_value_start(s, target_key)
|
|
348
|
-
pos = Json.skip_ws(s, 0)
|
|
349
|
-
if pos >= s.length || s[pos] != "{"
|
|
350
|
-
return -1
|
|
351
|
-
end
|
|
352
|
-
pos += 1
|
|
353
|
-
while pos < s.length
|
|
354
|
-
pos = Json.skip_ws(s, pos)
|
|
355
|
-
if pos >= s.length
|
|
356
|
-
return -1
|
|
357
|
-
end
|
|
358
|
-
if s[pos] == "}"
|
|
359
|
-
return -1
|
|
360
|
-
end
|
|
361
|
-
# Read a key.
|
|
362
|
-
if s[pos] != "\""
|
|
363
|
-
return -1
|
|
364
|
-
end
|
|
365
|
-
key_start = pos
|
|
366
|
-
pos = Json.skip_str(s, pos)
|
|
367
|
-
if pos < 0
|
|
368
|
-
return -1
|
|
369
|
-
end
|
|
370
|
-
# Decode the key for comparison (handles \" inside keys).
|
|
371
|
-
key = Json.parse_str_value(s, key_start)
|
|
372
|
-
# Skip ws, ":".
|
|
373
|
-
pos = Json.skip_ws(s, pos)
|
|
374
|
-
if pos >= s.length || s[pos] != ":"
|
|
375
|
-
return -1
|
|
376
|
-
end
|
|
377
|
-
pos += 1
|
|
378
|
-
pos = Json.skip_ws(s, pos)
|
|
379
|
-
if key == target_key
|
|
380
|
-
return pos
|
|
381
|
-
end
|
|
382
|
-
# Skip the value, then the comma (if any).
|
|
383
|
-
pos = Json.skip_value(s, pos)
|
|
384
|
-
pos = Json.skip_ws(s, pos)
|
|
385
|
-
if pos < s.length && s[pos] == ","
|
|
386
|
-
pos += 1
|
|
387
|
-
elsif pos < s.length && s[pos] == "}"
|
|
388
|
-
return -1
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
-1
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
end
|
data/lib/spinel_kit/version.rb
DELETED
data/lib/spinel_kit.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# SpinelKit -- the Spinel stdlib-surface gem.
|
|
2
|
-
#
|
|
3
|
-
# A pure-Ruby, Spinel-safe toolkit holding the generic "stdlib substitute"
|
|
4
|
-
# shims that every Spinel-compiled project would otherwise hand-roll. Spinel
|
|
5
|
-
# (the Ruby->native AOT compiler) cannot lower large chunks of CRuby stdlib
|
|
6
|
-
# -- the `json` gem's C-ext fast path and metaprogrammed pure-Ruby fallback,
|
|
7
|
-
# stdlib `Logger`, C-ext git bindings -- and the spinelgems compatibility
|
|
8
|
-
# catalog confirms there is no verified gem to reuse for any of them. So this
|
|
9
|
-
# gem consolidates the shims toy and tep each grew independently:
|
|
10
|
-
#
|
|
11
|
-
# SpinelKit::Json -- JSON encoders (json.rb) + flat-key decoders
|
|
12
|
-
# (json_decoder.rb).
|
|
13
|
-
# SpinelKit::Json::Builder -- incremental ordered-object builder.
|
|
14
|
-
# SpinelKit::Hex -- hex digit/byte encode + decode.
|
|
15
|
-
# SpinelKit::Url -- percent-encode/decode + form-query + URL split.
|
|
16
|
-
# SpinelKit::Git -- git provenance from .git/HEAD (was Toy::Git).
|
|
17
|
-
# SpinelKit::Log -- minimal levelled logger (was Tep::Logger).
|
|
18
|
-
#
|
|
19
|
-
# MINIMAL-SURFACE REQUIRING. This umbrella requires everything for
|
|
20
|
-
# convenience (and for CRuby use). But because Spinel compiles every loaded
|
|
21
|
-
# method with no tree-shaking, a Spinel-compiled consumer should require ONLY
|
|
22
|
-
# the file(s) it uses, to avoid compiling -- and degrading -- code it never
|
|
23
|
-
# calls:
|
|
24
|
-
#
|
|
25
|
-
# require "spinel_kit/json" # encoders
|
|
26
|
-
# require "spinel_kit/json_decoder" # decoders (require alongside json if you decode)
|
|
27
|
-
# require "spinel_kit/json_builder" # builder
|
|
28
|
-
# require "spinel_kit/hex" # hex digit/byte codec
|
|
29
|
-
# require "spinel_kit/url" # percent-codec + query (pulls in hex)
|
|
30
|
-
# require "spinel_kit/git"
|
|
31
|
-
# require "spinel_kit/log"
|
|
32
|
-
#
|
|
33
|
-
# No native extension (spinel-ext.json is []), no runtime dependencies. See
|
|
34
|
-
# docs/adoption.md and docs/spinel-discipline.md.
|
|
35
|
-
require_relative "spinel_kit/version"
|
|
36
|
-
require_relative "spinel_kit/json"
|
|
37
|
-
require_relative "spinel_kit/json_decoder"
|
|
38
|
-
require_relative "spinel_kit/json_builder"
|
|
39
|
-
require_relative "spinel_kit/hex"
|
|
40
|
-
require_relative "spinel_kit/url"
|
|
41
|
-
require_relative "spinel_kit/git"
|
|
42
|
-
require_relative "spinel_kit/log"
|
|
43
|
-
|
|
44
|
-
module SpinelKit
|
|
45
|
-
end
|
data/sig/spinel_kit/json.rbs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Advisory RBS for SpinelKit::Json encoders (lib/spinel_kit/json.rb).
|
|
2
|
-
# Decoders are in json_decoder.rbs; the builder in json_builder.rbs.
|
|
3
|
-
module SpinelKit
|
|
4
|
-
class Json
|
|
5
|
-
def self.escape: (String s) -> String
|
|
6
|
-
def self.hex2: (Integer n) -> String
|
|
7
|
-
def self.quote: (String s) -> String
|
|
8
|
-
def self.encode_pair_str: (String k, String v) -> String
|
|
9
|
-
def self.encode_pair_int: (String k, Integer v) -> String
|
|
10
|
-
def self.from_str_hash: (Hash[String, String] h) -> String
|
|
11
|
-
def self.from_int_hash: (Hash[String, Integer] h) -> String
|
|
12
|
-
def self.from_str_array: (Array[String] a) -> String
|
|
13
|
-
def self.from_int_array: (Array[Integer] a) -> String
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module SpinelKit
|
|
2
|
-
class Json
|
|
3
|
-
class Builder
|
|
4
|
-
def initialize: () -> void
|
|
5
|
-
def add_str: (String key, String value) -> String
|
|
6
|
-
def add_num: (String key, untyped value) -> String
|
|
7
|
-
def add_bool: (String key, bool value) -> String
|
|
8
|
-
def add_raw: (String key, String raw) -> String
|
|
9
|
-
def add_obj: (String key, SpinelKit::Json::Builder child) -> String
|
|
10
|
-
def dump: () -> String
|
|
11
|
-
def comma: () -> void
|
|
12
|
-
|
|
13
|
-
# self-contained escapers (byte-identical to SpinelKit::Json.*)
|
|
14
|
-
def self.quote: (String s) -> String
|
|
15
|
-
def self.escape: (String s) -> String
|
|
16
|
-
def self.hex2: (Integer n) -> String
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Advisory RBS for SpinelKit::Json decoders (lib/spinel_kit/json_decoder.rb).
|
|
2
|
-
module SpinelKit
|
|
3
|
-
class Json
|
|
4
|
-
def self.get_str: (String s, String key) -> String
|
|
5
|
-
def self.get_int: (String s, String key) -> Integer
|
|
6
|
-
def self.get_float: (String s, String key) -> Float
|
|
7
|
-
def self.get_int_array: (String s, String key) -> Array[Integer]
|
|
8
|
-
def self.has_key?: (String s, String key) -> bool
|
|
9
|
-
|
|
10
|
-
# internal walkers
|
|
11
|
-
def self.skip_ws: (String s, Integer pos) -> Integer
|
|
12
|
-
def self.skip_str: (String s, Integer pos) -> Integer
|
|
13
|
-
def self.skip_value: (String s, Integer pos) -> Integer
|
|
14
|
-
def self.skip_container: (String s, Integer pos) -> Integer
|
|
15
|
-
def self.parse_str_value: (String s, Integer pos) -> String
|
|
16
|
-
def self.hex_nibble: (String c) -> Integer
|
|
17
|
-
def self.byte_to_chr: (Integer n) -> String
|
|
18
|
-
def self.parse_int_value: (String s, Integer pos) -> Integer
|
|
19
|
-
def self.find_value_start: (String s, String target_key) -> Integer
|
|
20
|
-
end
|
|
21
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|