glossarist 2.8.18 → 2.9.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +1 -0
  3. data/.rubocop_todo.yml +175 -8
  4. data/Rakefile +4 -0
  5. data/config.yml +2 -3
  6. data/data/concept-model/README.md +35 -0
  7. data/data/concept-model/SOURCE.json +5 -0
  8. data/data/concept-model/glossarist.context.jsonld +261 -0
  9. data/data/concept-model/glossarist.ttl +1327 -0
  10. data/data/concept-model/prefixes.ttl +27 -0
  11. data/data/concept-model/shapes/glossarist.shacl.ttl +815 -0
  12. data/glossarist.gemspec +2 -0
  13. data/lib/glossarist/asset_reference.rb +2 -0
  14. data/lib/glossarist/bibliographic_reference.rb +13 -0
  15. data/lib/glossarist/cli/export_command.rb +130 -7
  16. data/lib/glossarist/cli.rb +11 -2
  17. data/lib/glossarist/concept_document.rb +13 -0
  18. data/lib/glossarist/concept_reference.rb +6 -0
  19. data/lib/glossarist/concept_store.rb +2 -3
  20. data/lib/glossarist/dataset_register.rb +2 -2
  21. data/lib/glossarist/figure_reference.rb +0 -4
  22. data/lib/glossarist/formula_reference.rb +0 -4
  23. data/lib/glossarist/non_verbal_reference.rb +10 -0
  24. data/lib/glossarist/rdf/gloss_detailed_definition.rb +6 -1
  25. data/lib/glossarist/rdf/gloss_localized_concept.rb +65 -0
  26. data/lib/glossarist/rdf/lutaml_ext.rb +33 -0
  27. data/lib/glossarist/rdf.rb +2 -0
  28. data/lib/glossarist/reference.rb +33 -0
  29. data/lib/glossarist/table_reference.rb +0 -4
  30. data/lib/glossarist/tasks/shacl.rake +30 -0
  31. data/lib/glossarist/tasks/sync.rake +14 -0
  32. data/lib/glossarist/tasks/sync_model.rb +96 -0
  33. data/lib/glossarist/transforms/concept_to_gloss_transform.rb +3 -2
  34. data/lib/glossarist/utilities/uuid.rb +5 -1
  35. data/lib/glossarist/validation/rules/related_concept_cycle_rule.rb +15 -1
  36. data/lib/glossarist/validation/shacl_validator.rb +97 -0
  37. data/lib/glossarist/validation.rb +1 -0
  38. data/lib/glossarist/version.rb +1 -1
  39. data/lib/glossarist.rb +1 -0
  40. metadata +42 -2
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "net/http"
6
+ require "uri"
7
+
8
+ module Glossarist
9
+ module Tasks
10
+ # Syncs vendored concept-model data artifacts from glossarist/concept-model.
11
+ #
12
+ # concept-model is a data-only repo (TTL, JSON-LD, YAML schemas). It is
13
+ # not a gem. We vendor the small set of artifacts we need (shapes,
14
+ # context, ontology) and load them at runtime via ShaclValidator.
15
+ module SyncModel
16
+ REPO = "glossarist/concept-model"
17
+ OUT_DIR = File.expand_path("data/concept-model", File.join(__dir__, "..", "..", ".."))
18
+
19
+ TARGETS = {
20
+ "prefixes.ttl" => %w[
21
+ ontologies/prefixes.ttl
22
+ prefixes.ttl
23
+ ].freeze,
24
+ "glossarist.context.jsonld" => %w[
25
+ ontologies/glossarist.context.jsonld
26
+ glossarist.context.jsonld
27
+ ].freeze,
28
+ "glossarist.ttl" => %w[
29
+ ontologies/glossarist.ttl
30
+ glossarist.ttl
31
+ ].freeze,
32
+ "shapes/glossarist.shacl.ttl" => %w[
33
+ ontologies/shapes/glossarist.shacl.ttl
34
+ shapes/glossarist.shacl.ttl
35
+ ].freeze,
36
+ }.freeze
37
+
38
+ class << self
39
+ def call(ref: nil)
40
+ ref ||= latest_tag
41
+ FileUtils.mkdir_p(File.join(OUT_DIR, "shapes"))
42
+
43
+ TARGETS.each do |out_rel, candidates|
44
+ content = fetch_any(ref, candidates)
45
+ out_path = File.join(OUT_DIR, out_rel)
46
+ FileUtils.mkdir_p(File.dirname(out_path))
47
+ File.write(out_path, content)
48
+ puts " ✓ #{out_rel} (#{content.length} bytes)"
49
+ end
50
+
51
+ write_source_manifest(ref)
52
+ puts "\nSynced #{TARGETS.length} file(s) from #{REPO}@#{ref}."
53
+ end
54
+
55
+ private
56
+
57
+ def latest_tag
58
+ url = URI("https://api.github.com/repos/#{REPO}/releases/latest")
59
+ req = Net::HTTP::Get.new(url)
60
+ req["Accept"] = "application/vnd.github+json"
61
+ JSON.parse(Net::HTTP.start(url.hostname, url.port, use_ssl: true) { |http| http.request(req) }.body)
62
+ .fetch("tag_name")
63
+ rescue StandardError => e
64
+ warn "Could not determine latest concept-model tag: #{e.message}"
65
+ exit 1
66
+ end
67
+
68
+ def fetch_any(ref, candidates)
69
+ candidates.each do |path|
70
+ begin
71
+ return fetch_file(ref, path)
72
+ rescue StandardError
73
+ next
74
+ end
75
+ end
76
+ raise "Could not fetch any of: #{candidates.join(', ')}"
77
+ end
78
+
79
+ def fetch_file(ref, path)
80
+ url = URI("https://raw.githubusercontent.com/#{REPO}/#{ref}/#{path}")
81
+ Net::HTTP.get(url)
82
+ end
83
+
84
+ def write_source_manifest(ref)
85
+ manifest = {
86
+ "repo" => REPO,
87
+ "ref" => ref,
88
+ "syncedAt" => Time.now.utc.iso8601,
89
+ }
90
+ File.write(File.join(OUT_DIR, "SOURCE.json"),
91
+ JSON.pretty_generate(manifest) + "\n")
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -122,10 +122,11 @@ module Glossarist
122
122
 
123
123
  dd_attrs = if data
124
124
  data.class.detailed_definition_fields.to_h do |field|
125
- [field, build_gloss_definitions(data.public_send(field))]
125
+ key = field == :definition ? :definitions : field
126
+ [key, build_gloss_definitions(data.public_send(field))]
126
127
  end
127
128
  else
128
- { definition: [], notes: [], examples: [] }
129
+ { definitions: [], notes: [], examples: [] }
129
130
  end
130
131
 
131
132
  sources = build_gloss_sources(data&.sources)
@@ -65,7 +65,11 @@ module Glossarist
65
65
  else
66
66
  match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/)
67
67
 
68
- unless match_data.present?
68
+ # `match_data` is a MatchData on success, nil on failure. Do not
69
+ # use ActiveSupport's `#present?` here — ActiveSupport is not a
70
+ # declared dependency of this gem, and `#present?` raises
71
+ # NoMethodError in bundles where it isn't loaded.
72
+ unless match_data
69
73
  raise ArgumentError,
70
74
  "Only UUIDs are valid namespace identifiers"
71
75
  end
@@ -54,11 +54,25 @@ module Glossarist
54
54
  graph
55
55
  end
56
56
 
57
+ # Returns a graph node identifier for the target of a relationship.
58
+ #
59
+ # For intra-edition references (ref.source is nil), the target is
60
+ # just ref.id — a clause identifier unique within the dataset.
61
+ #
62
+ # Returns nil for cross-edition references (ref.source is a URN,
63
+ # e.g. a +supersedes+ edge pointing at the previous edition's
64
+ # concept with the same clause id). Such edges cannot form
65
+ # cycles within the current dataset and are excluded from the
66
+ # graph. Without this exclusion, a cross-edition edge from
67
+ # concept 3.1.1.1 to its predecessor in another edition (which
68
+ # also has identifier 3.1.1.1) would look like a self-loop and
69
+ # trip the cycle detector with a false positive.
57
70
  def resolve_target_id(rel)
58
71
  ref = rel.ref
59
72
  return nil unless ref
73
+ return nil if ref.source
60
74
 
61
- ref.id || ref.source
75
+ ref.id
62
76
  end
63
77
 
64
78
  def detect_cycles(graph)
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rdf/turtle"
4
+ require "shacl"
5
+ require "pathname"
6
+
7
+ module Glossarist
8
+ module Validation
9
+ # Validates Turtle output against SHACL shapes.
10
+ #
11
+ # Shapes resolution order:
12
+ # 1. Explicit :shapes_path option
13
+ # 2. Vendored shapes at data/concept-model/shapes/glossarist.shacl.ttl
14
+ # (shipped with the gem, relative to the gem root)
15
+ class ShaclValidator
16
+ VENDORED_SHAPES_PATH =
17
+ File.expand_path(
18
+ "../../../data/concept-model/shapes/glossarist.shacl.ttl",
19
+ __dir__
20
+ ).freeze
21
+
22
+ attr_reader :shapes_path
23
+
24
+ def initialize(shapes_path: nil)
25
+ @shapes_path = shapes_path || self.class.default_shapes_path
26
+ end
27
+
28
+ def validate_files(paths)
29
+ validate_graphs(Array(paths).map { |p| load_graph(p) })
30
+ end
31
+
32
+ def validate_graphs(graphs)
33
+ shapes = SHACL::Shapes.from_graph(load_graph(@shapes_path))
34
+ failures = []
35
+
36
+ graphs.each do |graph|
37
+ report = shapes.execute(graph)
38
+ next if report.conform?
39
+
40
+ failures << Report.new(results: report.results)
41
+ end
42
+
43
+ AggregateReport.new(failures: failures)
44
+ end
45
+
46
+ class << self
47
+ def default_shapes_path
48
+ return VENDORED_SHAPES_PATH if File.exist?(VENDORED_SHAPES_PATH)
49
+
50
+ raise ArgumentError,
51
+ "No SHACL shapes path provided and the vendored shapes " \
52
+ "file (#{VENDORED_SHAPES_PATH}) is missing. Pass " \
53
+ "shapes_path: '/path/to/glossarist.shacl.ttl'."
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def load_graph(path)
60
+ return path if path.is_a?(RDF::Graph)
61
+
62
+ graph = RDF::Graph.new
63
+ RDF::Turtle::Reader.new(File.read(path)) do |reader|
64
+ reader.each_statement { |stmt| graph << stmt }
65
+ end
66
+ graph
67
+ end
68
+ end
69
+
70
+ Report = Struct.new(:results, keyword_init: true) do
71
+ def conformant?
72
+ results.empty?
73
+ end
74
+
75
+ def to_s
76
+ " #{results.length} violation(s)"
77
+ end
78
+ end
79
+
80
+ AggregateReport = Struct.new(:failures, keyword_init: true) do
81
+ def conformant?
82
+ failures.empty?
83
+ end
84
+
85
+ def to_s
86
+ lines = ["SHACL validation failed: #{failures.length} file(s) with violations"]
87
+ failures.each do |failure|
88
+ lines << failure.to_s
89
+ failure.results.each do |result|
90
+ lines << " #{result.path}: #{result.message}"
91
+ end
92
+ end
93
+ lines.join("\n")
94
+ end
95
+ end
96
+ end
97
+ end
@@ -8,5 +8,6 @@ module Glossarist
8
8
  autoload :Rules, "glossarist/validation/rules"
9
9
  autoload :BibliographyIndex, "glossarist/validation/bibliography_index"
10
10
  autoload :AssetIndex, "glossarist/validation/asset_index"
11
+ autoload :ShaclValidator, "glossarist/validation/shacl_validator"
11
12
  end
12
13
  end
@@ -4,5 +4,5 @@
4
4
  #
5
5
 
6
6
  module Glossarist
7
- VERSION = "2.8.18"
7
+ VERSION = "2.9.1"
8
8
  end
data/lib/glossarist.rb CHANGED
@@ -23,6 +23,7 @@ module Glossarist
23
23
  autoload :ConceptData, "glossarist/concept_data"
24
24
  autoload :ConceptRef, "glossarist/concept_ref"
25
25
  autoload :ConceptReference, "glossarist/concept_reference"
26
+ autoload :Reference, "glossarist/reference"
26
27
  autoload :ReferenceExtractor, "glossarist/reference_extractor"
27
28
  autoload :ReferenceResolver, "glossarist/reference_resolver"
28
29
  autoload :ResolutionAdapter, "glossarist/resolution_adapter"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glossarist
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.18
4
+ version: 2.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-02 00:00:00.000000000 Z
11
+ date: 2026-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rdf-turtle
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.3'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: relaton
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -92,6 +106,20 @@ dependencies:
92
106
  - - "<"
93
107
  - !ruby/object:Gem::Version
94
108
  version: '3'
109
+ - !ruby/object:Gem::Dependency
110
+ name: shacl
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '0.4'
116
+ type: :runtime
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '0.4'
95
123
  - !ruby/object:Gem::Dependency
96
124
  name: sts
97
125
  requirement: !ruby/object:Gem::Requirement
@@ -170,6 +198,12 @@ files:
170
198
  - README.adoc
171
199
  - Rakefile
172
200
  - config.yml
201
+ - data/concept-model/README.md
202
+ - data/concept-model/SOURCE.json
203
+ - data/concept-model/glossarist.context.jsonld
204
+ - data/concept-model/glossarist.ttl
205
+ - data/concept-model/prefixes.ttl
206
+ - data/concept-model/shapes/glossarist.shacl.ttl
173
207
  - exe/glossarist
174
208
  - glossarist.gemspec
175
209
  - lib/glossarist.rb
@@ -276,6 +310,7 @@ files:
276
310
  - lib/glossarist/rdf/gloss_pronunciation.rb
277
311
  - lib/glossarist/rdf/gloss_reference.rb
278
312
  - lib/glossarist/rdf/localized_literal.rb
313
+ - lib/glossarist/rdf/lutaml_ext.rb
279
314
  - lib/glossarist/rdf/namespaces.rb
280
315
  - lib/glossarist/rdf/namespaces/dcterms_namespace.rb
281
316
  - lib/glossarist/rdf/namespaces/glossarist_namespace.rb
@@ -288,6 +323,7 @@ files:
288
323
  - lib/glossarist/rdf/relationship_predicates.rb
289
324
  - lib/glossarist/rdf/v3.rb
290
325
  - lib/glossarist/rdf/v3/configuration.rb
326
+ - lib/glossarist/reference.rb
291
327
  - lib/glossarist/reference_extractor.rb
292
328
  - lib/glossarist/reference_resolver.rb
293
329
  - lib/glossarist/register_data.rb
@@ -313,6 +349,9 @@ files:
313
349
  - lib/glossarist/sts/term_mapper.rb
314
350
  - lib/glossarist/table.rb
315
351
  - lib/glossarist/table_reference.rb
352
+ - lib/glossarist/tasks/shacl.rake
353
+ - lib/glossarist/tasks/sync.rake
354
+ - lib/glossarist/tasks/sync_model.rb
316
355
  - lib/glossarist/transforms.rb
317
356
  - lib/glossarist/transforms/concept_to_gloss_transform.rb
318
357
  - lib/glossarist/transforms/concept_to_tbx_transform.rb
@@ -402,6 +441,7 @@ files:
402
441
  - lib/glossarist/validation/rules/source_urn_format_rule.rb
403
442
  - lib/glossarist/validation/rules/terms_presence_rule.rb
404
443
  - lib/glossarist/validation/rules/uuid_format_rule.rb
444
+ - lib/glossarist/validation/shacl_validator.rb
405
445
  - lib/glossarist/validation/validation_issue.rb
406
446
  - lib/glossarist/validation_result.rb
407
447
  - lib/glossarist/version.rb