glossarist 2.13.7 → 2.13.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4d7af45a5f0e045d0f3a18cdcca5791a85bfcb6a16a642af0c1e840b68ef6e8
4
- data.tar.gz: 8fd502d531f3c862c340b3aa19bb1fc4e4b205e7217ba4c79ee6bc1809db0d35
3
+ metadata.gz: 439b6f94f23e6018ec771d6032a05f83aac496fb4a03c8d33cebc793886cc8fa
4
+ data.tar.gz: 8462a0e92e3e0ff0a9b3447fe94ae1ce9d8f3b59ce10955daeb746a4c0b514d7
5
5
  SHA512:
6
- metadata.gz: aedc22ebce41ec42d6bc93e96abef82078b7834b66f08a0e219b08f4b2e0eded91561bcc82af7498ab50aed4e8da32d0cabc23c37b7eb56390ec7028e8b731ba
7
- data.tar.gz: fe063d88bf1297f1c31e72ce545a9a9e32f7deb89f6d24cd6580572f830f605288e57feed04ed3af4ebb9c777320def6c5fc87e9ec82a93ef12c0a070a7f42b7
6
+ metadata.gz: e6be9e8613779b1e7a791cc40df512e0592e9d3aa8d27d2339f80f04dc832e001024d412a71f20571e366b7be952581834e12e09dede444a8bf604a3b556e865
7
+ data.tar.gz: 37170de8e503b351d2e609fd91e294e10bd48f4ae53d20c81b3224737a6413cb53e593bab170e2ea9683f5583cc7969447492bd136ba1dcb2c559d707a9fe3eb
@@ -8,7 +8,14 @@ module Glossarist
8
8
  # deterministic_id from GlossNaryMember. The `rdf do` block
9
9
  # re-declares the predicates because lutaml-model's `rdf` DSL
10
10
  # replaces the parent mapping (not extends).
11
+ #
12
+ # Carries `gloss:characteristic` — the delimiting characteristic
13
+ # text per ISO 704:2022 §5.5.4.2.1 (e.g., "detecting movement by
14
+ # means of light sensors"). Each species in a coordinate set has
15
+ # its own characteristic under the parent hyperedge's criterion.
11
16
  class GlossGenericMember < GlossNaryMember
17
+ attribute :characteristic, :hash
18
+
12
19
  rdf do
13
20
  namespace Namespaces::GlossaristNamespace
14
21
 
@@ -26,8 +33,8 @@ module Glossarist
26
33
  to: :presence
27
34
  predicate :count, namespace: Namespaces::GlossaristNamespace,
28
35
  to: :count
29
- predicate :isDelimiting, namespace: Namespaces::GlossaristNamespace,
30
- to: :is_delimiting
36
+ predicate :characteristic, namespace: Namespaces::GlossaristNamespace,
37
+ to: :characteristic
31
38
  end
32
39
  end
33
40
  end
@@ -4,27 +4,27 @@ require "lutaml/model"
4
4
 
5
5
  module Glossarist
6
6
  module Rdf
7
- # Shared base for typed n-ary relation member RDF views
7
+ # Shared base for typed n-ary hyperedge member RDF views
8
8
  # (GlossPartitiveMember, GlossGenericMember, future leaves).
9
9
  #
10
10
  # Carries the shared attributes and the deterministic_id algorithm.
11
- # Concrete leaves declare their own `rdf do` block because
12
- # lutaml-model's `rdf` DSL replaces (not extends) the parent's
13
- # mapping predicate declarations stay in each leaf.
11
+ # Concrete leaves declare their own `rdf do` block AND their own
12
+ # type-specific attributes — `is_delimiting` on GlossPartitiveMember
13
+ # (binary role, ISO 704 §5.5.4.2.2); `characteristic` on
14
+ # GlossGenericMember (delimiting text, ISO 704 §5.5.4.2.1).
14
15
  #
15
16
  # Subject identity is content-derived so the same member in the
16
- # same relation produces the same URI across runs (and across
17
+ # same hyperedge produces the same URI across runs (and across
17
18
  # processes). Two members with identical ref + dimensions share a
18
19
  # subject — that is intentional: it exposes model-level
19
- # inconsistency if the same partitive concept is given different
20
- # dimensions inside the same dataset.
20
+ # inconsistency if the same concept is given different dimensions
21
+ # inside the same dataset.
21
22
  class GlossNaryMember < Lutaml::Model::Serializable
22
23
  attribute :ref_id, :string
23
24
  attribute :ref_source, :string
24
25
  attribute :ref_text, :string
25
26
  attribute :presence, :string
26
27
  attribute :count, :string
27
- attribute :is_delimiting, :boolean
28
28
 
29
29
  # Human-readable SOURCE:ID when both are present; SHA-256 / 16
30
30
  # hex chars fallback otherwise. Same content → same slug across
@@ -33,10 +33,19 @@ module Glossarist
33
33
  readable = [member.ref_source, member.ref_id].compact.reject(&:empty?)
34
34
  return readable.join(":") unless readable.empty?
35
35
 
36
- DeterministicSlug.from_parts(
37
- member.ref_source, member.ref_id, member.ref_text,
38
- member.presence, member.count, member.is_delimiting
39
- )
36
+ parts = [member.ref_source, member.ref_id, member.ref_text,
37
+ member.presence, member.count]
38
+ # Type-specific discriminator: include the delimiting flag for
39
+ # partitive members (binary role) or the characteristic text
40
+ # for generic members (so members of different criteria don't
41
+ # collide on the same hash input).
42
+ case member
43
+ when GlossPartitiveMember
44
+ parts << member.is_delimiting
45
+ when GlossGenericMember
46
+ parts << member.characteristic
47
+ end
48
+ DeterministicSlug.from_parts(*parts)
40
49
  end
41
50
  end
42
51
  end
@@ -8,7 +8,12 @@ module Glossarist
8
8
  # deterministic_id from GlossNaryMember. The `rdf do` block
9
9
  # re-declares the predicates because lutaml-model's `rdf` DSL
10
10
  # replaces the parent mapping (not extends).
11
+ #
12
+ # Carries `gloss:isDelimiting` — the binary role flag per
13
+ # ISO 704:2022 §5.5.4.2.2 (a part is or is not a delimiting part).
11
14
  class GlossPartitiveMember < GlossNaryMember
15
+ attribute :is_delimiting, :boolean
16
+
12
17
  rdf do
13
18
  namespace Namespaces::GlossaristNamespace
14
19
 
@@ -136,12 +136,12 @@ module Glossarist
136
136
  partitive_relations: build_gloss_hyperedges(
137
137
  relation_list, V3::PartitiveHyperedge,
138
138
  :build_gloss_partitive_member, :partitive_member_ids,
139
- :partitive_members, Rdf::GlossPartitiveRelation, identifier,
139
+ :partitive_members, Rdf::GlossPartitiveRelation, identifier
140
140
  ),
141
141
  generic_relations: build_gloss_hyperedges(
142
142
  relation_list, V3::GenericHyperedge,
143
143
  :build_gloss_generic_member, :generic_member_ids,
144
- :generic_members, Rdf::GlossGenericRelation, identifier,
144
+ :generic_members, Rdf::GlossGenericRelation, identifier
145
145
  ),
146
146
  **rel_targets,
147
147
  )
@@ -156,7 +156,7 @@ module Glossarist
156
156
  member_ids_attr, members_attr,
157
157
  gloss_relation_class, identifier)
158
158
  Array(relations)
159
- .select { |rel| rel.is_a?(hyperedge_class) }
159
+ .grep(hyperedge_class)
160
160
  .map { |rel| build_one_gloss_hyperedge(rel, member_builder, member_ids_attr, members_attr, gloss_relation_class, identifier) }
161
161
  end
162
162
 
@@ -192,6 +192,8 @@ module Glossarist
192
192
 
193
193
  # Shared member builder. Per-class metadata (the rdf view class
194
194
  # is dispatched by the caller) keeps the duplication to one place.
195
+ # Per-type fields are dispatched by class — PartitiveMember
196
+ # carries `is_delimiting`; GenericMember carries `characteristic`.
195
197
  def build_gloss_nary_member(gloss_member_class, member)
196
198
  ref = member.ref
197
199
  ref_attrs = if ref.is_a?(Glossarist::ConceptRef)
@@ -200,11 +202,21 @@ module Glossarist
200
202
  {}
201
203
  end
202
204
 
205
+ type_attrs =
206
+ case member
207
+ when V3::PartitiveMember
208
+ { is_delimiting: member.is_delimiting }
209
+ when V3::GenericMember
210
+ { characteristic: member.characteristic }
211
+ else
212
+ {}
213
+ end
214
+
203
215
  gloss_member_class.new(
204
216
  **ref_attrs,
205
217
  presence: member.presence,
206
218
  count: member.count,
207
- is_delimiting: member.is_delimiting,
219
+ **type_attrs,
208
220
  )
209
221
  end
210
222
 
@@ -114,8 +114,8 @@ module Glossarist
114
114
  parts = [comprehensive.source, comprehensive.id].compact.reject(&:empty?)
115
115
  return nil if parts.empty?
116
116
 
117
- parts.join("-").downcase.gsub(/[^a-z0-9\-]/, "-")
118
- .gsub(/-{2,}/, "-").gsub(/\A-|-\z/, "")
117
+ parts.join("-").downcase.gsub(/[^a-z0-9-]/, "-")
118
+ .gsub(/-{2,}/, "-").gsub(/\A-|-\z/, "")
119
119
  end
120
120
 
121
121
  # Kebab-case slug derived from the English criterion. Falls back
@@ -2,16 +2,51 @@
2
2
 
3
3
  module Glossarist
4
4
  module V3
5
- # GenericMember — one member of a GenericHyperedge. Carries the same
6
- # MECE dimensions as PartitiveMember; the comprehensive of its
7
- # parent GenericHyperedge denotes the genus concept.
5
+ # GenericMember — one member (species) of a GenericHyperedge. The
6
+ # `comprehensive` of its parent GenericHyperedge denotes the genus
7
+ # concept; this member denotes one species.
8
8
  #
9
- # See docs/design/generic-relation.md and docs/design/abstract-nary-relation.md
10
- # (concept-model repo).
9
+ # Per ISO 704:2022 §5.5.4.2.1, the intension of a species includes
10
+ # the intension of the genus plus at least one additional
11
+ # DELIMITING CHARACTERISTIC. Each species in a coordinate set
12
+ # (siblings sharing the same criterion of subdivision) carries its
13
+ # own delimiting characteristic value.
14
+ #
15
+ # Example (ISO 704 §5.5.4.2.1 Example 3):
16
+ # Hyperedge comprehensive: computer mouse
17
+ # Hyperedge criterion (of subdivision): means of movement detection
18
+ # Members:
19
+ # - mechanical mouse
20
+ # characteristic: "detecting movement by means of rollers"
21
+ # - optomechanical mouse
22
+ # characteristic: "detecting movement by means of rollers and light sensors"
23
+ # - optical mouse
24
+ # characteristic: "detecting movement by means of light sensors"
25
+ #
26
+ # Multidimensionality (ISO 704 §5.6.3): the same genus can have
27
+ # multiple GenericHyperedges, each with a different criterion
28
+ # (e.g., means of movement detection vs computer connection). The
29
+ # `characteristic` on each member is meaningful only within its
30
+ # parent hyperedge's criterion context.
31
+ #
32
+ # Distinct from PartitiveMember: partitive members are binary
33
+ # delimiting (a part is or isn't delimiting); generic members are
34
+ # textually delimiting (each species has its own characteristic
35
+ # phrase that distinguishes it from coordinate concepts).
11
36
  class GenericMember < HyperedgeMember
12
- # Inherits all attributes and validations from HyperedgeMember.
13
- # Declared as a distinct class for type safety and to leave room
14
- # for future generic-specific extensions.
37
+ # The delimiting characteristic of this species under the parent
38
+ # hyperedge's criterion of subdivision. Localized different
39
+ # languages may express the same characteristic differently.
40
+ # Required for semantic completeness per ISO 704 §5.5.4.2.1, but
41
+ # the model permits nil to allow incremental population.
42
+ attribute :characteristic, :hash
43
+
44
+ key_value do
45
+ map :ref, to: :ref
46
+ map :presence, to: :presence
47
+ map :count, to: :count
48
+ map :characteristic, to: :characteristic
49
+ end
15
50
  end
16
51
  end
17
52
  end
@@ -3,14 +3,25 @@
3
3
  module Glossarist
4
4
  module V3
5
5
  # HyperedgeMember — abstract base shape for members of any
6
- # n-ary concept-system relation (PartitiveMember, GenericMember,
6
+ # n-ary concept-system hyperedge (PartitiveMember, GenericMember,
7
7
  # future AssociativeMember, SequentialMember).
8
8
  #
9
- # Carries the shared ISO 704:2022 MECE dimensions: presence × count,
10
- # plus the orthogonal is_delimiting flag.
9
+ # Carries the shared ISO 704:2022 MECE dimensions on every member:
10
+ # presence required (default) | optional
11
+ # count — exactly_one (default) | at_least_one | multiple
11
12
  #
12
- # Concrete leaf classes inherit and may add type-specific fields.
13
- # See docs/design/abstract-nary-relation.md (concept-model repo).
13
+ # Type-specific extensions live on the leaves:
14
+ # PartitiveMember `is_delimiting` (Boolean): per ISO 704
15
+ # §5.5.4.2.2, a part is or is not a delimiting part. Binary role.
16
+ # GenericMember — `characteristic` (LocalizedString): per ISO 704
17
+ # §5.5.4.2.1, each species carries a delimiting characteristic
18
+ # text (e.g., "detecting movement by means of light sensors")
19
+ # that distinguishes it from coordinate concepts under the
20
+ # hyperedge's criterion of subdivision.
21
+ #
22
+ # The combination (optional, at_least_one) is invalid and collapses
23
+ # to (optional, multiple). Multiplicity is the SSOT for the
24
+ # validation; this class delegates to it.
14
25
  class HyperedgeMember < Lutaml::Model::Serializable
15
26
  DEFAULT_PRESENCE = "required"
16
27
  DEFAULT_COUNT = "exactly_one"
@@ -22,13 +33,11 @@ module Glossarist
22
33
  attribute :count, :string,
23
34
  values: Glossarist::GlossaryDefinition::MEMBER_COUNT_VALUES,
24
35
  default: -> { DEFAULT_COUNT }
25
- attribute :is_delimiting, :boolean, default: -> { false }
26
36
 
27
37
  key_value do
28
38
  map :ref, to: :ref
29
39
  map :presence, to: :presence
30
40
  map :count, to: :count
31
- map :is_delimiting, to: :is_delimiting
32
41
  end
33
42
 
34
43
  def initialize(*)
@@ -55,10 +64,6 @@ module Glossarist
55
64
  presence == "optional"
56
65
  end
57
66
 
58
- def delimiting?
59
- is_delimiting == true
60
- end
61
-
62
67
  private
63
68
 
64
69
  def validate_ref!
@@ -15,10 +15,12 @@ module Glossarist
15
15
  # validator, RDF emitter, RelationLoader) — they all iterate or
16
16
  # resolve through this registry.
17
17
  module HyperedgeRegistry
18
- # Mutable: register adds entries at load time. DO NOT freeze.
18
+ # rubocop:disable Style/MutableConstant these MUST stay
19
+ # mutable; register() adds entries at load time.
19
20
  BY_WIRE_KEY = {}
20
21
  BY_TYPE_TAG = {}
21
22
  BY_RDF_TYPE = {}
23
+ # rubocop:enable Style/MutableConstant
22
24
 
23
25
  Mutex = ::Mutex.new
24
26
 
@@ -45,8 +45,10 @@ module Glossarist
45
45
  hyperedge.is_a?(AbstractHyperedge)
46
46
 
47
47
  path = hyperedge.file_path(@relations_dir)
48
- raise WriteError,
49
- "cannot derive file path — comprehensive must be non-empty" unless path
48
+ unless path
49
+ raise WriteError,
50
+ "cannot derive file path — comprehensive must be non-empty"
51
+ end
50
52
 
51
53
  FileUtils.mkdir_p(File.dirname(path))
52
54
  File.write(path, serialize(hyperedge))
@@ -72,7 +74,7 @@ module Glossarist
72
74
  ordered["type"] = hash.delete("type")
73
75
  ordered["status"] = hash.delete("status") if hash.key?("status")
74
76
  ordered["comprehensive"] = hash.delete("comprehensive")
75
- ordered["members"] = hash.delete("members")
77
+ ordered["members"] = hash.delete("members")
76
78
  ordered["completeness"] = hash.delete("completeness") if hash.key?("completeness")
77
79
  ordered["criterion"] = hash.delete("criterion") if hash.key?("criterion")
78
80
  ordered["sources"] = hash.delete("sources") if hash.key?("sources")
@@ -2,15 +2,33 @@
2
2
 
3
3
  module Glossarist
4
4
  module V3
5
- # PartitiveMember — one member of a PartitiveHyperedge. Inherits
6
- # the ISO 704:2022 MECE shape from HyperedgeMember; the
5
+ # PartitiveMember — one member of a PartitiveHyperedge. The
7
6
  # `comprehensive` of its parent PartitiveHyperedge denotes the
8
- # whole concept.
7
+ # whole concept; this member denotes one of its parts.
9
8
  #
10
- # Distinct leaf class for type safety and to leave room for
11
- # partitive-specific extensions if needed.
9
+ # Per ISO 704:2022 §5.5.4.2.2, a part is either delimiting or not:
10
+ # a delimiting part behaves like a delimiting characteristic — it
11
+ # distinguishes the comprehensive (whole) from coordinate concepts.
12
+ # Example: for "optomechanical mouse", the delimiting parts are
13
+ # mouse ball, x/y-axis rollers, infrared emitter/sensor — they
14
+ # distinguish it from "mechanical mouse" and "optical mouse".
15
+ # Mouse button is NOT delimiting (all computer mice have buttons).
16
+ #
17
+ # The binary role is sufficient — no per-member delimiting text is
18
+ # needed because the part itself IS the delimiting marker.
12
19
  class PartitiveMember < HyperedgeMember
13
- # Inherits all attributes and validations from HyperedgeMember.
20
+ attribute :is_delimiting, :boolean, default: -> { false }
21
+
22
+ key_value do
23
+ map :ref, to: :ref
24
+ map :presence, to: :presence
25
+ map :count, to: :count
26
+ map :is_delimiting, to: :is_delimiting
27
+ end
28
+
29
+ def delimiting?
30
+ is_delimiting == true
31
+ end
14
32
  end
15
33
  end
16
34
  end
@@ -4,5 +4,5 @@
4
4
  #
5
5
 
6
6
  module Glossarist
7
- VERSION = "2.13.7"
7
+ VERSION = "2.13.8"
8
8
  end
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.13.7
4
+ version: 2.13.8
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-29 00:00:00.000000000 Z
11
+ date: 2026-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model