lutaml-path 0.1.0 → 0.1.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 +4 -4
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +30 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +7 -0
- data/README.adoc +651 -36
- data/lib/lutaml/path/abstract_path.rb +24 -0
- data/lib/lutaml/path/condition.rb +100 -0
- data/lib/lutaml/path/condition_evaluator.rb +109 -0
- data/lib/lutaml/path/element_path.rb +66 -8
- data/lib/lutaml/path/errors.rb +12 -0
- data/lib/lutaml/path/instance_path.rb +63 -0
- data/lib/lutaml/path/parser.rb +164 -13
- data/lib/lutaml/path/path_segment.rb +31 -5
- data/lib/lutaml/path/resolver.rb +154 -0
- data/lib/lutaml/path/step.rb +50 -0
- data/lib/lutaml/path/transformer.rb +63 -21
- data/lib/lutaml/path/version.rb +1 -1
- data/lib/lutaml/path.rb +34 -3
- data/lutaml-path.gemspec +2 -1
- data/spec/lutaml/path_spec.rb +674 -29
- data/spec/lutaml/resolver_spec.rb +182 -0
- metadata +29 -6
- data/.github/workflows/main.yml +0 -27
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# The resolution specs exercise real lutaml-model instances. The gem
|
|
6
|
+
# works without lutaml-model loaded (the resolver's model branch is
|
|
7
|
+
# defined?-guarded), so when the dependency cannot load — an adapter
|
|
8
|
+
# gem missing on an exotic platform, say — the suite skips instead of
|
|
9
|
+
# failing at file load.
|
|
10
|
+
begin
|
|
11
|
+
require "lutaml/model"
|
|
12
|
+
LUTAML_MODEL_AVAILABLE = true
|
|
13
|
+
rescue LoadError, StandardError
|
|
14
|
+
LUTAML_MODEL_AVAILABLE = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Conformance corpus for RS 3002 "LutaML path for instance data",
|
|
18
|
+
# resolved against real lutaml-model instances shaped like the
|
|
19
|
+
# specification's own examples (contributor / organization / role /
|
|
20
|
+
# docidentifier / date).
|
|
21
|
+
# The model-fixture stack mirrors the specification's example model
|
|
22
|
+
# (contributor / organization / role / docidentifier); it is the
|
|
23
|
+
# conformance corpus, not incidental setup.
|
|
24
|
+
RSpec.describe "Lutaml::Path.resolve over lutaml-model instances" do
|
|
25
|
+
before do
|
|
26
|
+
skip "lutaml-model is not loadable in this bundle" unless LUTAML_MODEL_AVAILABLE
|
|
27
|
+
end
|
|
28
|
+
let(:role_class) do
|
|
29
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
30
|
+
attribute :type, :string
|
|
31
|
+
|
|
32
|
+
json { map "type", to: :type }
|
|
33
|
+
end
|
|
34
|
+
stub_const("LpSpec::Role", klass)
|
|
35
|
+
klass
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
let(:organization_class) do
|
|
39
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
40
|
+
attribute :name, :string
|
|
41
|
+
attribute :type, :string
|
|
42
|
+
|
|
43
|
+
json do
|
|
44
|
+
map "name", to: :name
|
|
45
|
+
map "type", to: :type
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
stub_const("LpSpec::Organization", klass)
|
|
49
|
+
klass
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
let(:contributor_class) do
|
|
53
|
+
role = role_class
|
|
54
|
+
org = organization_class
|
|
55
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
56
|
+
attribute :role, role
|
|
57
|
+
attribute :organization, org
|
|
58
|
+
|
|
59
|
+
json do
|
|
60
|
+
map "role", to: :role
|
|
61
|
+
map "organization", to: :organization
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
stub_const("LpSpec::Contributor", klass)
|
|
65
|
+
klass
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
let(:docidentifier_class) do
|
|
69
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
70
|
+
attribute :type, :string
|
|
71
|
+
attribute :value, :string
|
|
72
|
+
|
|
73
|
+
json do
|
|
74
|
+
map "type", to: :type
|
|
75
|
+
map "id", to: :value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
stub_const("LpSpec::Docidentifier", klass)
|
|
79
|
+
klass
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
let(:document_class) do
|
|
83
|
+
contributor = contributor_class
|
|
84
|
+
identifier = docidentifier_class
|
|
85
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
86
|
+
attribute :title, :string
|
|
87
|
+
attribute :contributor, contributor, collection: true
|
|
88
|
+
attribute :docidentifier, identifier, collection: true
|
|
89
|
+
|
|
90
|
+
json do
|
|
91
|
+
map "title", to: :title
|
|
92
|
+
map "contributor", to: :contributor
|
|
93
|
+
map "docidentifier", to: :docidentifier
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
stub_const("LpSpec::Document", klass)
|
|
97
|
+
klass
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
let(:document) do
|
|
101
|
+
document_class.from_json(
|
|
102
|
+
<<~JSON
|
|
103
|
+
{
|
|
104
|
+
"title": "Standard X",
|
|
105
|
+
"contributor": [
|
|
106
|
+
{"role": {"type": "publisher"},
|
|
107
|
+
"organization": {"name": "ISO", "type": "standards"}},
|
|
108
|
+
{"role": {"type": "author"},
|
|
109
|
+
"organization": {"name": "ACME", "type": "company"}},
|
|
110
|
+
{"role": {"type": "author"},
|
|
111
|
+
"organization": {"name": "IEC", "type": "standards"}}
|
|
112
|
+
],
|
|
113
|
+
"docidentifier": [
|
|
114
|
+
{"type": "ISBN", "id": "978-1"},
|
|
115
|
+
{"type": "ISSN", "id": "1234-5"},
|
|
116
|
+
{"type": "DOI", "id": "10.1/x"}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
JSON
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def resolve(path)
|
|
124
|
+
Lutaml::Path.resolve(document, path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "resolves a simple attribute chain to a single value" do
|
|
128
|
+
expect(resolve("title")).to eq("Standard X")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "resolves a collection attribute to its elements" do
|
|
132
|
+
expect(resolve("contributor").length).to eq(3)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "resolves a nested chain through a collection element" do
|
|
136
|
+
expect(resolve("contributor[role.type='publisher'].organization.name"))
|
|
137
|
+
.to eq(["ISO"])
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "filters with multiple conditions" do
|
|
141
|
+
expect(resolve("contributor[role.type='author' && organization.type='standards'].organization.name")
|
|
142
|
+
.sort).to eq(%w[IEC])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "filters with list membership" do
|
|
146
|
+
expect(resolve("docidentifier[type in ('ISBN','ISSN')].value").sort)
|
|
147
|
+
.to eq(%w[1234-5 978-1])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "filters with negation" do
|
|
151
|
+
expect(resolve("docidentifier[type!='ISBN'].value").sort)
|
|
152
|
+
.to eq(["10.1/x", "1234-5"])
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "filters with existence" do
|
|
156
|
+
expect(resolve("contributor[exists].organization.name").sort)
|
|
157
|
+
.to eq(%w[ACME IEC ISO])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "matches wildcard condition values" do
|
|
161
|
+
expect(resolve("contributor[organization.type='stand*'].organization.name").sort)
|
|
162
|
+
.to eq(%w[IEC ISO])
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "resolves wildcard segments across the collection" do
|
|
166
|
+
expect(resolve("*.type").compact.sort)
|
|
167
|
+
.to eq(%w[DOI ISBN ISSN])
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "resolves a deep hierarchy wildcard with a filter" do
|
|
171
|
+
expect(resolve("contributor.**[name='ISO']")).to contain_exactly(
|
|
172
|
+
an_object_having_attributes(name: "ISO")
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it "resolves against plain hashes the same way" do
|
|
177
|
+
data = { "contributor" => [{ "role" => { "type" => "publisher" } }] }
|
|
178
|
+
|
|
179
|
+
expect(Lutaml::Path.resolve(data, "contributor[role.type='publisher']")).not_to be_empty
|
|
180
|
+
expect(Lutaml::Path.resolve(data, "contributor.role.type")).to eq("publisher")
|
|
181
|
+
end
|
|
182
|
+
end
|
metadata
CHANGED
|
@@ -1,29 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-path
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: lutaml-model
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
19
|
+
version: '0.8'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
26
|
+
version: '0.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: parslet
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2'
|
|
27
41
|
description: Parser for element paths
|
|
28
42
|
email:
|
|
29
43
|
- open.source@ribose.com
|
|
@@ -31,7 +45,8 @@ executables: []
|
|
|
31
45
|
extensions: []
|
|
32
46
|
extra_rdoc_files: []
|
|
33
47
|
files:
|
|
34
|
-
- ".github/workflows/
|
|
48
|
+
- ".github/workflows/rake.yml"
|
|
49
|
+
- ".github/workflows/release.yml"
|
|
35
50
|
- ".gitignore"
|
|
36
51
|
- ".rspec"
|
|
37
52
|
- ".rubocop.yml"
|
|
@@ -43,14 +58,22 @@ files:
|
|
|
43
58
|
- bin/console
|
|
44
59
|
- bin/setup
|
|
45
60
|
- lib/lutaml/path.rb
|
|
61
|
+
- lib/lutaml/path/abstract_path.rb
|
|
62
|
+
- lib/lutaml/path/condition.rb
|
|
63
|
+
- lib/lutaml/path/condition_evaluator.rb
|
|
46
64
|
- lib/lutaml/path/element_path.rb
|
|
65
|
+
- lib/lutaml/path/errors.rb
|
|
66
|
+
- lib/lutaml/path/instance_path.rb
|
|
47
67
|
- lib/lutaml/path/parser.rb
|
|
48
68
|
- lib/lutaml/path/path_segment.rb
|
|
69
|
+
- lib/lutaml/path/resolver.rb
|
|
70
|
+
- lib/lutaml/path/step.rb
|
|
49
71
|
- lib/lutaml/path/transformer.rb
|
|
50
72
|
- lib/lutaml/path/version.rb
|
|
51
73
|
- lutaml-path.gemspec
|
|
52
74
|
- sig/lutaml/path.rbs
|
|
53
75
|
- spec/lutaml/path_spec.rb
|
|
76
|
+
- spec/lutaml/resolver_spec.rb
|
|
54
77
|
- spec/spec_helper.rb
|
|
55
78
|
homepage: https://github.com/lutaml/lutaml-path
|
|
56
79
|
licenses:
|
data/.github/workflows/main.yml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
name: Ruby
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
pull_request:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
name: Ruby ${{ matrix.ruby }}
|
|
14
|
-
strategy:
|
|
15
|
-
matrix:
|
|
16
|
-
ruby:
|
|
17
|
-
- '3.3.2'
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v4
|
|
21
|
-
- name: Set up Ruby
|
|
22
|
-
uses: ruby/setup-ruby@v1
|
|
23
|
-
with:
|
|
24
|
-
ruby-version: ${{ matrix.ruby }}
|
|
25
|
-
bundler-cache: true
|
|
26
|
-
- name: Run the default task
|
|
27
|
-
run: bundle exec rake
|