jsonata 2.2.2.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 52797dede85591f15e5e3ad10670d556d3b4683471c5b5e00f0ff0602e147694
4
+ data.tar.gz: 0d235b9161ee1d2a45a1f492c813c5a2b04f438aa91af7707f9144c48e39724b
5
+ SHA512:
6
+ metadata.gz: 945f2e2fca929e067dd684ce171edc66f8d233047e3bea1b6e3b1513022e662a05342b1d393f585d43e2ed0892cb824a77896df04a19cb56922fe5fb6fc7216a
7
+ data.tar.gz: 1d960e3b290979f87f8f71509fcd1bbbd76a95081a08a701df7a1d33c57270857fe4f3d908530796cdc56c747857b95b53d3d9b13616a739d343dabe942ffd80
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ inherit_gem:
2
+ manageiq-style: ".rubocop_base.yml"
3
+ inherit_from:
4
+ - ".rubocop_local.yml"
@@ -0,0 +1,52 @@
1
+ # The minimum supported Ruby version (see the gemspec); manageiq-style targets a newer one.
2
+ AllCops:
3
+ TargetRubyVersion: 3.1
4
+
5
+ # This is not a Rails project, and the suggested corrections (e.g. blank?) need ActiveSupport.
6
+ Rails:
7
+ Enabled: false
8
+
9
+ # lib/ is ported from jsonata-js and keeps the structure, names and comments of the JS code, so it stays
10
+ # diffable with upstream (see AGENTS.md). These cops restructure the code or rename things away from the JS.
11
+ Lint/DuplicateBranch:
12
+ Enabled: false
13
+ Naming/MethodParameterName:
14
+ Enabled: false
15
+ Naming/PredicateMethod:
16
+ Enabled: false
17
+ Style/CaseLikeIf:
18
+ Enabled: false
19
+ Style/CommentAnnotation:
20
+ Enabled: false
21
+ Style/ConditionalAssignment:
22
+ Enabled: false
23
+ Style/EmptyElse:
24
+ AllowComments: true
25
+ Style/GuardClause:
26
+ Enabled: false
27
+ Style/IdenticalConditionalBranches:
28
+ Enabled: false
29
+ Style/IfUnlessModifier:
30
+ Enabled: false
31
+ Style/ItAssignment:
32
+ Enabled: false
33
+ Style/MultilineBlockChain:
34
+ Enabled: false
35
+ Style/MultipleComparison:
36
+ Enabled: false
37
+ Style/NegatedIfElseCondition:
38
+ Enabled: false
39
+ Style/RedundantAssignment:
40
+ Enabled: false
41
+
42
+ # Built-in function parameters are positional, as their arity (JS Function.length) is used when invoking them.
43
+ Style/OptionalBooleanParameter:
44
+ Enabled: false
45
+
46
+ # Array#sum uses Kahan-Babuska summation, which gives different results from the JS loop (see AGENTS.md).
47
+ Performance/Sum:
48
+ Enabled: false
49
+
50
+ # Jsonata::Error is raised with a code and fields, e.g. raise Jsonata::Error.new('T0410', :index => 1).
51
+ Style/RaiseArgs:
52
+ EnforcedStyle: compact
data/AGENTS.md ADDED
@@ -0,0 +1,171 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for agents working on jsonata-rb. Read the README first; this file covers what the README does not:
4
+ the decisions behind the port and how JavaScript semantics are emulated in Ruby.
5
+
6
+ ## Ground rules
7
+
8
+ - **Port, don't invent.** Every change to `lib/` should correspond to code in `vendor/jsonata-js/src`. When a
9
+ test fails, find how jsonata-js does it and port that, rather than writing a new solution that happens to pass.
10
+ - **Keep the structure diffable.** Ruby files mirror the JS files: same function order, same names (snake_case),
11
+ same comments (including the JSDoc text and TODOs). Ruby-only code is marked with a comment such as
12
+ `(Ruby specific)` or `(jsonata-js: <original construct>)`, so it is clear what is a deviation.
13
+ - **Keep the upstream copyright headers.** Each ported file starts with the IBM copyright header of the JS file it
14
+ ports (in ASCII, `(c)` for the copyright sign), as required by the MIT license. Copy the header when porting a
15
+ new file, and update the years if upstream changes them.
16
+ - **Tests come from upstream only.** Language behavior is tested exclusively by the jsonata-js test suite via
17
+ `spec/test_suite_spec.rb`. Do not add hand-written specs for language features. `spec/jsonata_spec.rb` is only
18
+ for the Ruby API (conversion of values, `assign`, `register_function`, errors, ...).
19
+ - **Never edit `vendor/jsonata-js`.** It is a git submodule tracking jsonata-js master. Bump it with
20
+ `rake jsonata_js:update` and commit the submodule bump together with the ported `lib/` changes.
21
+ - **ASCII only.** Code, comments and commit messages must not contain non-ASCII characters, including (but not
22
+ limited to) em-dashes and en-dashes, curly quotes, the section sign, Greek letters (e.g. lambda) and
23
+ mathematical symbols (e.g. less-than-or-equal). Use `"\uXXXX"` escapes in string literals that
24
+ need non-ASCII characters, and ASCII equivalents in comments.
25
+ - **LF line endings.** Enforced by `.gitattributes`; never commit CRLF.
26
+ - **Optimize within Ruby, not by changing the algorithm.** Performance work may change how the Ruby is written
27
+ (e.g. caching in the Ruby-only helpers in `utils.rb`), but must not change the logic ported from jsonata-js.
28
+ Algorithmic improvements should be proposed upstream first, and then ported.
29
+ - **Ruby >= 3.1.** Do not use syntax or core methods newer than 3.1.
30
+ - **Rubocop must pass.** The style is ManageIQ's (`manageiq-style` gem), with project overrides in
31
+ `.rubocop_local.yml`. Cops that would restructure ported code away from the JS are disabled there; prefer adding
32
+ to that list (with the reason) over rewriting ported code to satisfy a cop. Review unsafe autocorrections by
33
+ hand - some are wrong here (e.g. `Style/HashExcept` on `#parameters`, or `Style/BitwisePredicate`).
34
+
35
+ ## Porting workflow for jsonata-js changes
36
+
37
+ The `jsonata-js sync` workflow (`.github/workflows/jsonata-js-sync.yaml`) runs these steps daily with Claude and
38
+ opens a PR from the `jsonata-js-sync` branch; they also apply when porting by hand.
39
+
40
+ 1. `bundle exec rake jsonata_js:update` - moves the submodule to jsonata-js master and lists new commits.
41
+ 2. `bundle exec rake jsonata_js:diff` - shows the `src/` and `test/` changes since the commit recorded in this repo.
42
+ 3. Port each `src/` hunk to the corresponding Ruby file (see the mapping table in the README), at the same place
43
+ in the file.
44
+ 4. `bundle exec rspec` (or `GROUP=<group> bundle exec rspec` while iterating) and `bundle exec rubocop` until
45
+ everything passes.
46
+ 5. If `test/run-test-suite.js` changed, port that change to `spec/test_suite_spec.rb`.
47
+ 6. Changes to the JS-only mocha tests (`test/*.js` other than `run-test-suite.js`) are not run by this repo;
48
+ only port them if they reveal behavior worth covering in `spec/jsonata_spec.rb`.
49
+
50
+ ## JavaScript vs Ruby: decisions made
51
+
52
+ ### undefined and null
53
+
54
+ - `nil` is JavaScript `undefined`. `Jsonata::NULL` (a frozen sentinel) is JSON `null`. This was chosen because
55
+ JS code tests for `undefined` far more often than `null`, and Ruby methods return `nil` implicitly.
56
+ - Values are converted at the API boundary only: `Utils.from_ruby` (nested `nil` -> `NULL`, Symbol keys -> String
57
+ keys) on the way in, `Utils.to_ruby` (`NULL` -> `nil`, `JArray` -> `Array`) on the way out. A top-level `nil`
58
+ input or binding value means `undefined`.
59
+ - `Expression#evaluate_raw` returns the internal representation; the test harness uses it to tell `null` from
60
+ `undefined`.
61
+
62
+ ### Truthiness and typeof
63
+
64
+ - JS truthiness (`if (x)`, `!x`, `x || y`) on values must use `Utils.truthy?` - in Ruby `0`, `""` and `NULL` are
65
+ truthy. Only use plain Ruby truthiness where the JS value is known to be a boolean/object/undefined.
66
+ - `typeof x` maps to `Utils.typeof(x)`. Remember `typeof null === 'object'` and arrays are objects in JS:
67
+ `Utils.keys` returns index strings for arrays, as `Object.keys` does (needed by the wildcard operator).
68
+
69
+ ### Objects, arrays and sequences
70
+
71
+ - JSON objects are Hashes with String keys (insertion ordered, like JS objects).
72
+ - jsonata-js attaches properties to arrays (`sequence`, `keepSingleton`, `tupleStream`, `cons`, `outerWrapper`).
73
+ These live on `Jsonata::JArray < Array`. Read them with `x.is_a?(JArray) && x.flag` (or `Utils.sequence?`),
74
+ because many arrays are plain `Array`s. Before setting a flag on an array that may be plain, wrap it in a
75
+ `JArray`.
76
+ - In Ruby 3, `Array#+`, `#map`, `#select`, `#[]` with ranges etc. on a `JArray` return a plain `Array`. This
77
+ matches JS, where `concat`/`slice`/`map` do not copy the custom properties.
78
+ - `createSequence` (with the `sequence` length limit) is `Frame#create_sequence` / `Focus#create_sequence`.
79
+ `create_sequence(nil)` pushes `nil` (as JS does with an explicit `undefined` argument), while
80
+ `create_sequence()` creates an empty sequence - keep the distinction.
81
+ - Beware Ruby negative indexes: `arr[-1]` is the last element, whereas JS returns `undefined`. Guard indexes that
82
+ may be negative.
83
+
84
+ ### Numbers
85
+
86
+ - JS numbers are doubles; here they are Integers when integral (and within +/-2^53) and Floats otherwise.
87
+ - Do arithmetic in Float and pass results through `Utils.normalize_number`, so integer overflow/precision behaves
88
+ like JS and integral results come back as Integers.
89
+ - Do not use `Array#sum` - it uses Kahan-Babuska summation and gives different results from JS's naive loop.
90
+ - `Math.floor` on large values: use `Utils.js_floor`, which keeps the result a Float beyond 2^53.
91
+ - Formatting: `Utils.number_to_s` (Number::toString), `Utils.to_fixed` (toFixed), `Utils.to_precision`
92
+ (toPrecision) are exact (via Rational), including JS's round-half-up tie breaking.
93
+ - `Math.round` is `(x.to_r + 1/2).floor` (ties toward +infinity), not Ruby's `round`.
94
+ - JS parsing helpers: `Utils.parse_int` (returns `nil` for NaN), `Utils.js_number` (`Number(str)`).
95
+
96
+ ### Strings
97
+
98
+ - Ruby strings are indexed by code point, JS strings by UTF-16 code unit. The port uses code points throughout
99
+ (consistent within Ruby); this only matters for characters outside the BMP.
100
+ - Ruby cannot hold UTF-16 surrogates. The tokenizer combines `\uD8xx\uDCxx` escape pairs into one character; a
101
+ lone surrogate is stored as invalid UTF-8 bytes (`Utils.from_char_code`), which `encode_uri` rejects and
102
+ `json_stringify` re-escapes as `\udxxx`.
103
+ - JS string helpers with different Ruby semantics: `Utils.js_slice`, `Utils.js_split` (keeps empty strings),
104
+ `Utils.json_stringify` (JSON.stringify, including the replacer and indentation used by `$string`),
105
+ `Utils.encode_uri` / `Utils.decode_uri`.
106
+
107
+ ### Regular expressions
108
+
109
+ - Regex literals are parsed into `Parser::RegexLiteral` (source + flags) and translated by `Utils.js_regexp`:
110
+ `^`/`$` become `\A`/`\z` unless the `m` flag is set, `[]` and `[^]` are rewritten, and `(?a)` is prepended so
111
+ `\w`, `\d`, `\b` are ASCII-only as in JS.
112
+ - `RegExp.lastIndex` is emulated with a `last_index` variable captured by the matcher closure in
113
+ `evaluate_regex`.
114
+ - The jsonata-js `RegexEngine` option is the `:regex_engine` option: a callable `(source, flags)` returning an
115
+ object that responds to `match(str, pos)`.
116
+
117
+ ### Functions
118
+
119
+ - Function values: `Jsonata::Lambda` (`_jsonata_lambda`), `Jsonata::NativeFunction` (`_jsonata_function`), and
120
+ plain callables (`Proc`, `Method`, `Jsonata::Closure` - the JS function with an `arity` property).
121
+ `Utils.function?` covers all of them; `Utils.callable?` is `typeof x === 'function'`.
122
+ - Built-in functions are instance methods of `Jsonata::Functions` and `Jsonata::DateTime`, which are included in
123
+ `Jsonata::Focus`. A `Focus` is the JS `this` (`environment`, `input`, `options`, `create_sequence`), so
124
+ `fn.lookup.call(focus, ...)` becomes `focus.lookup(...)`. Built-ins are registered with
125
+ `Focus.instance_method(:name)`.
126
+ - `Utils.invoke` calls a Ruby callable with JS semantics: missing arguments become `nil` and extra arguments are
127
+ dropped. Within built-ins, call function arguments with `call_function`, which also handles `Lambda#apply`.
128
+ - Arity (JS `Function.length`) is the number of *required* Ruby parameters. Declare built-in parameters as
129
+ required (no defaults) unless the JS function has a default (e.g. `string(arg, prettify = false)`), otherwise
130
+ higher-order functions like `$map` pass the wrong number of arguments.
131
+ - Partial application of native functions reads parameter names from `#parameters` (JS parses
132
+ `func.toString()`).
133
+ - `not` is a valid Ruby method name and is used for `$not`.
134
+ - JS `async`/`await` is dropped; evaluation is synchronous. `Promise.all` loops run sequentially but still set
135
+ `environment.is_parallel_call`, which the evaluate entry/exit hooks rely on. `$sort`'s recursive merge is
136
+ iterative to avoid deep Ruby recursion.
137
+ - The `Symbol.for('jsonata.__evaluate_entry')` style hook names are Ruby Symbols, e.g.
138
+ `:'jsonata.__evaluate_entry'`; `Expression#assign` keeps such Symbols as-is.
139
+
140
+ ### Errors
141
+
142
+ - JS `throw { code: ... }` is `raise Jsonata::Error.new('CODE', field: ...)`; the fields use Ruby names
143
+ (`function_name` for `functionName`) and `Error#to_h` maps them back. Messages are filled in by
144
+ `Evaluator.populate_message` from `ERROR_CODES`.
145
+ - JS `catch (err)` catches everything; the port rescues only `Jsonata::Error`, so Ruby bugs surface as Ruby
146
+ exceptions instead of being annotated as JSONata errors.
147
+ - `SystemStackError` is reported as `D1011` (Ruby specific). Non-tail recursion in an expression overflows the
148
+ Ruby stack at roughly 600-1000 levels, as each JSONata call uses about a dozen Ruby frames; tail calls are
149
+ handled by the trampoline in `Evaluator.apply`.
150
+
151
+ ### Parser
152
+
153
+ - AST nodes are Hashes with Symbol keys (`:keep_array` for `keepArray`, etc.). Symbol keys keep AST nodes
154
+ distinguishable from user data, which has String keys (e.g. the `?` placeholder check in partial application).
155
+ - JS nodes inherit `nud`/`led`/`lbp`/`id` from the symbol table via the prototype chain; here a
156
+ `compare_by_identity` Hash maps each node to its `Sym`. `JSON.parse(JSON.stringify(node))` is `deep_clone`,
157
+ which drops that mapping just as the JS clone drops the prototype.
158
+ - `nud`/`led` are blocks: use `next`, not `return`, to return early from them.
159
+
160
+ ### Dates
161
+
162
+ - JS `Date` is a UTC Ruby `Time`. `DateTime.date_utc` reproduces `Date.UTC` (month overflow, truncation, years
163
+ 0-99 mapping to 1900+), and `DateTime.date_parse` reproduces `Date.parse` for the ISO 8601 subset accepted by
164
+ `$toMillis`. Timestamps without an offset are treated as UTC.
165
+
166
+ ### Test harness
167
+
168
+ - `spec/test_suite_spec.rb` mirrors `run-test-suite.js`. Results are compared with `evaluate_raw` against the
169
+ expected JSON converted with `Utils.from_ruby`, so `null` and `undefined` are distinguished.
170
+ - Ruby's JSON parser rejects lone surrogates, so test files containing them are re-parsed with the surrogates
171
+ escaped; they reach the tokenizer as `\uXXXX` in `expr`, and are decoded with `Utils.from_char_code` elsewhere.
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and the version follows the jsonata-js version it is ported from (see [Versioning](README.md#versioning)).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Ruby port of jsonata-js: parser, evaluator, function library, date/time functions and signature validation
12
+ - The full JSONata language and function library
13
+ - `Jsonata::Error` with the jsonata-js error codes and messages
14
+ - `Expression#assign`, `#register_function`, `#ast`, `#errors` and `#evaluate_raw`
15
+ - `recover`, `timeout`, `stack`, `sequence` and `regex_engine` options
16
+ - The jsonata-js test suite, via a git submodule at `vendor/jsonata-js`
17
+ - `rake jsonata_js:update` and `rake jsonata_js:diff` tasks for tracking jsonata-js
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016-2018 IBM Corp.
4
+ Copyright (c) 2026 jsonata-rb contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # JSONata-RB
2
+
3
+ A Ruby port of [jsonata-js](https://github.com/jsonata-js/jsonata), the reference implementation of
4
+ [JSONata](http://jsonata.org/) - a lightweight query and transformation language for JSON data.
5
+
6
+ This is a faithful, file-by-file port of the JavaScript implementation, and it is tested against the
7
+ jsonata-js test suite itself (included as a git submodule), so it supports the full JSONata language and
8
+ function library.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'jsonata'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ ```bash
21
+ bundle install
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```ruby
27
+ require 'jsonata'
28
+
29
+ data = {
30
+ example: [
31
+ { value: 4 },
32
+ { value: 7 },
33
+ { value: 13 }
34
+ ]
35
+ }
36
+
37
+ # Compile an expression, then evaluate it
38
+ expression = Jsonata.expression('$sum(example.value)')
39
+ expression.evaluate(data) # => 24
40
+
41
+ # Or use the convenience method
42
+ Jsonata.evaluate('$sum(example.value)', data) # => 24
43
+ ```
44
+
45
+ See the [JSONata documentation](http://docs.jsonata.org/) for the language and function library.
46
+
47
+ ### Variable bindings
48
+
49
+ ```ruby
50
+ Jsonata.evaluate('value > $threshold', { value: 150 }, { threshold: 100 }) # => true
51
+
52
+ expr = Jsonata.expression('value > $threshold')
53
+ expr.assign('threshold', 100)
54
+ expr.evaluate({ value: 150 }) # => true
55
+ ```
56
+
57
+ ### Custom functions
58
+
59
+ Any Ruby callable can be bound as a function, optionally with a
60
+ [signature](http://docs.jsonata.org/embedding-extending#function-signature-syntax) to validate its arguments:
61
+
62
+ ```ruby
63
+ expr = Jsonata.expression('$double(value)')
64
+ expr.assign('double', ->(x) { x * 2 })
65
+ expr.evaluate({ value: 21 }) # => 42
66
+
67
+ expr = Jsonata.expression('$add(1, 2)')
68
+ expr.register_function('add', ->(a, b) { a + b }, '<nn:n>')
69
+ expr.evaluate # => 3
70
+
71
+ expr = Jsonata.expression('$greet("World")')
72
+ expr.register_function('greet', '<s:s>') { |name| "Hello, #{name}" }
73
+ expr.evaluate # => "Hello, World"
74
+ ```
75
+
76
+ ### Errors
77
+
78
+ Syntax and evaluation errors raise `Jsonata::Error`, which carries the same fields as the error objects
79
+ thrown by jsonata-js (`code`, `position`, `token`, `value`, ...):
80
+
81
+ ```ruby
82
+ begin
83
+ Jsonata.evaluate('1 +')
84
+ rescue Jsonata::Error => e
85
+ e.code # => "S0207"
86
+ e.position # => 3
87
+ e.message # => "Unexpected end of expression"
88
+ end
89
+ ```
90
+
91
+ ### Options
92
+
93
+ `Jsonata.expression(expr, options)` accepts the same options as jsonata-js:
94
+
95
+ | Option | Description |
96
+ |-----------------|-------------------------------------------------------------------------------------|
97
+ | `:recover` | Attempt to recover on parse errors (see `Expression#errors`) |
98
+ | `:timeout` | Evaluation timeout in milliseconds (error `D1012`) |
99
+ | `:stack` | Maximum evaluation depth (error `D1011`) |
100
+ | `:sequence` | Maximum sequence length (error `D2015`) |
101
+ | `:regex_engine` | A callable `(source, flags)` returning an object that responds to `match(str, pos)` |
102
+
103
+ ### Mapping between JSON and Ruby values
104
+
105
+ * JSON objects are Hashes. Symbol keys in the input are converted to Strings; results always have String keys.
106
+ * JSON `null` and JavaScript `undefined` are both returned as `nil`. In the input, a `nil` nested inside a Hash
107
+ or Array is JSON `null`, while a top-level `nil` input means there is no input (`undefined`).
108
+ `Expression#evaluate_raw` returns the internal representation, in which `null` is `Jsonata::NULL` and
109
+ `undefined` is `nil`.
110
+ * Numbers are Integers when they are integral (and within the range of a JavaScript safe integer), and Floats
111
+ otherwise. Arithmetic follows JavaScript (IEEE 754 double) semantics.
112
+ * Functions (lambdas defined in an expression, or built-in functions) are returned as `Jsonata::Lambda` and
113
+ `Jsonata::NativeFunction` objects.
114
+
115
+ ### Differences from jsonata-js
116
+
117
+ * Evaluation is synchronous (jsonata-js `evaluate` returns a Promise).
118
+ * Regular expressions use Ruby's regex engine. JavaScript regex syntax is translated where the two differ
119
+ (e.g. `^`/`$` anchors), but some exotic constructs may behave differently.
120
+ * Strings are indexed by Unicode code point, so `$match` indexes differ from jsonata-js (which uses UTF-16 code
121
+ units) for strings containing characters outside the Basic Multilingual Plane.
122
+
123
+ ## Development
124
+
125
+ The jsonata-js repository is included as a git submodule at `vendor/jsonata-js`, so clone with:
126
+
127
+ ```bash
128
+ git clone --recurse-submodules <repo-url>
129
+ # or, in an existing clone
130
+ git submodule update --init
131
+ ```
132
+
133
+ Then:
134
+
135
+ ```bash
136
+ bundle install
137
+ bundle exec rspec # run all tests
138
+ GROUP=hof-map,regex bundle exec rspec # run specific test-suite groups
139
+ bundle exec rake console # interactive console
140
+ bundle exec rubocop # lint
141
+ ```
142
+
143
+ ### Structure of the port
144
+
145
+ Each Ruby file is a port of the corresponding jsonata-js source file, keeping the same structure, function
146
+ names (in snake_case), and comments, so that upstream changes can be applied to the Ruby code directly.
147
+
148
+ | jsonata-js | jsonata-rb |
149
+ |-----------------------|-----------------------------------------------------------------|
150
+ | `src/jsonata.js` | `lib/jsonata/evaluator.rb` |
151
+ | `src/parser.js` | `lib/jsonata/parser.rb` |
152
+ | `src/functions.js` | `lib/jsonata/functions.rb` |
153
+ | `src/datetime.js` | `lib/jsonata/datetime.rb` |
154
+ | `src/signature.js` | `lib/jsonata/signature.rb` |
155
+ | `src/utils.js` | `lib/jsonata/utils.rb` (plus helpers emulating JavaScript) |
156
+ | `test/run-test-suite.js` | `spec/test_suite_spec.rb` |
157
+
158
+ ### Tracking jsonata-js
159
+
160
+ The tests come from the jsonata-js test suite (`vendor/jsonata-js/test/test-suite`). To pick up jsonata-js
161
+ changes:
162
+
163
+ ```bash
164
+ bundle exec rake jsonata_js:update # move the submodule to the latest jsonata-js master, and list the new commits
165
+ bundle exec rake jsonata_js:diff # show the src/ and test/ changes to port
166
+ bundle exec rspec # port the src/ changes until the tests pass
167
+ git add vendor/jsonata-js lib # commit the submodule bump together with the ported changes
168
+ ```
169
+
170
+ The `jsonata-js sync` workflow (`.github/workflows/jsonata-js-sync.yaml`) does this daily: when jsonata-js
171
+ master moves, it bumps the submodule, has Claude port any `src/` and `test/` changes, and opens (or updates) a
172
+ pull request from the `jsonata-js-sync` branch. The PR is a draft if the tests or rubocop fail. The steps are in
173
+ `bin/jsonata-js-sync`; `DRY_RUN=1 bin/jsonata-js-sync publish` shows the commit message and PR description
174
+ for the current submodule checkout without pushing.
175
+
176
+ ## Versioning
177
+
178
+ For consistency, the gem's version follows the jsonata-js version it is ported from, so `jsonata` 2.2.2
179
+ implements the same language and passes the same test suite as jsonata-js 2.2.2. Releases that contain only
180
+ Ruby-specific changes, between upstream releases, add a fourth segment (e.g. 2.2.2.1).
181
+
182
+ ## License
183
+
184
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
185
+
186
+ ## Acknowledgments
187
+
188
+ This is a Ruby port of the [JSONata JavaScript library](https://github.com/jsonata-js/jsonata) created by
189
+ Andrew Coleman and contributors.
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+
10
+ desc 'Run RSpec tests'
11
+ task :test => :spec
12
+
13
+ desc 'Open an interactive console'
14
+ task :console do
15
+ require 'irb'
16
+ require_relative 'lib/jsonata'
17
+ ARGV.clear
18
+ IRB.start
19
+ end
20
+
21
+ JSONATA_JS_DIR = 'vendor/jsonata-js'
22
+
23
+ namespace :jsonata_js do
24
+ # The jsonata-js commit recorded in the superproject (HEAD, falling back to the index)
25
+ def recorded_jsonata_js_commit
26
+ commit = `git ls-tree HEAD #{JSONATA_JS_DIR} 2>/dev/null`.split[2]
27
+ commit || `git ls-files -s #{JSONATA_JS_DIR}`.split[1]
28
+ end
29
+
30
+ desc 'Update the jsonata-js submodule to the latest jsonata-js master'
31
+ task :update do
32
+ sh "git -C #{JSONATA_JS_DIR} fetch origin"
33
+ sh "git -C #{JSONATA_JS_DIR} checkout --quiet origin/master"
34
+ sh "git -C #{JSONATA_JS_DIR} log --oneline #{recorded_jsonata_js_commit}..HEAD -- src test"
35
+ end
36
+
37
+ desc 'Show the jsonata-js source and test changes that have not yet been committed to this repo'
38
+ task :diff do
39
+ sh "git -C #{JSONATA_JS_DIR} diff #{recorded_jsonata_js_commit} HEAD -- src test"
40
+ end
41
+ end