glossarist 1.0.9 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8fa4ca87465329dfee265b0c813794fbb857d5bbd174c2f7f28f6b4e678f372
4
- data.tar.gz: c83f9967b06603b4e756f47a8c9bd78ad89b0c1ac8201d6bfcd34c2efc0c5db4
3
+ metadata.gz: 2545436241f3fc01daf4879682439d3f4650a44a45af855cc5260ad445caca0b
4
+ data.tar.gz: c62fb0db9a7d1dc9ec4ad4ea9015a53a413bfbc922be77db276e4739926615a6
5
5
  SHA512:
6
- metadata.gz: 06207f39595d5ba32421f836268c71a081b028b32183062096c994fef1d794007f32093992716c97a39e91ccda07879405d934d7ea4e9746cf14470fd6e48993
7
- data.tar.gz: 53b67a4ed78221ea53e2d4d498a39bf01b487d1ec7682a39da16a00ae17dfabc0f162a4b39ea25a6a8dc4259c30e3f0c38835743fbda821b4f53c4dd77ea8318
6
+ metadata.gz: d611e1a09d99e64b7ad5e745d729fba2fe59d707f7f0469aa43fa3def04286c91128f5a65088f28943df550ef7eb6099d3354cadbafa7c68a93b08873512e2c1
7
+ data.tar.gz: c1e0ec90c6a41c9a4c26f8db587d0cfbb986fb3c2086494554fbde8dfc829abf0406d2bf4e123d85710c2e23c699ac82b0db9c5328f3498f3c378bd509617c81
data/config.yml CHANGED
@@ -71,7 +71,9 @@ grammar_info:
71
71
  concept:
72
72
  status:
73
73
  - draft
74
+ - submitted
74
75
  - not_valid
76
+ - invalid
75
77
  - valid
76
78
  - superseded
77
79
  - retired
data/glossarist.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_dependency "relaton", "~> 1.15.0"
33
+ spec.add_dependency "relaton", "~> 1.16.0"
34
34
  spec.add_dependency "thor"
35
35
 
36
36
  spec.add_development_dependency "pry", "~> 0.14.0"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module Collections
5
+ class Collection
6
+ include Enumerable
7
+
8
+ attr_reader :collection
9
+
10
+ alias :size :count
11
+
12
+ def initialize(klass:)
13
+ @klass = klass
14
+ @collection = []
15
+ end
16
+
17
+ def <<(object)
18
+ @collection << @klass.new(object)
19
+ end
20
+
21
+ def each(&block)
22
+ @collection.each(&block)
23
+ end
24
+
25
+ def empty?
26
+ @collection.empty?
27
+ end
28
+
29
+ def clear!
30
+ @collection = []
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module Collections
5
+ class DesignationCollection < Collection
6
+ def initialize
7
+ super(klass: Designation::Base)
8
+ end
9
+
10
+ def <<(object)
11
+ @collection << @klass.from_h(object)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,2 +1,4 @@
1
1
  require_relative "collections/asset_collection"
2
2
  require_relative "collections/bibliography_collection"
3
+ require_relative "collections/collection"
4
+ require_relative "collections/designation_collection"
@@ -7,8 +7,9 @@ module Glossarist
7
7
  class Concept < Model
8
8
  # Concept ID.
9
9
  # @return [String]
10
- attr_accessor :id
11
- alias :termid= :id=
10
+ attr_reader :id
11
+
12
+ attr_writer :uuid
12
13
 
13
14
  # Concept designations.
14
15
  # @todo Alias +terms+ exists only for legacy reasons and will be removed.
@@ -43,17 +44,43 @@ module Glossarist
43
44
  # Contains list of extended attributes
44
45
  attr_accessor :extension_attributes
45
46
 
46
- def initialize(*)
47
+ attr_accessor :lineage_source
48
+ attr_accessor :lineage_source_similarity
49
+
50
+ attr_accessor :release
51
+
52
+ def initialize(*args)
47
53
  @localizations = {}
48
- @sources = []
49
- @related = []
50
- @notes = []
51
- @designations = []
54
+ @sources = Glossarist::Collections::Collection.new(klass: ConceptSource)
55
+ @related = Glossarist::Collections::Collection.new(klass: RelatedConcept)
56
+ @definition = Glossarist::Collections::Collection.new(klass: DetailedDefinition)
57
+ @notes = Glossarist::Collections::Collection.new(klass: DetailedDefinition)
58
+ @examples = Glossarist::Collections::Collection.new(klass: DetailedDefinition)
59
+ @dates = Glossarist::Collections::Collection.new(klass: ConceptDate)
60
+
61
+ @designations = Glossarist::Collections::DesignationCollection.new
52
62
  @extension_attributes = {}
53
63
 
64
+ normalize_args(args)
65
+
54
66
  super
55
67
  end
56
68
 
69
+ def uuid
70
+ @uuid ||= Glossarist::Utilities::UUID.uuid_v5(
71
+ Glossarist::Utilities::UUID::OID_NAMESPACE,
72
+ to_h.to_yaml,
73
+ )
74
+ end
75
+
76
+ def id=(id)
77
+ raise(Glossarist::Error, "Expect id to be a string, Got #{id.class} (#{id})") unless id.is_a?(String) || id.nil?
78
+
79
+ @id = id
80
+ end
81
+ alias :termid= :id=
82
+ alias :identifier= :id=
83
+
57
84
  # List of authorative sources.
58
85
  # @todo Alias +authoritative_source+ exists for legacy reasons and may be
59
86
  # removed.
@@ -65,33 +92,40 @@ module Glossarist
65
92
  attr_reader :dates
66
93
 
67
94
  def examples=(examples)
68
- @examples = examples&.map { |e| DetailedDefinition.new(e) }
95
+ @examples.clear!
96
+ examples&.each { |example| @examples << example }
69
97
  end
70
98
 
71
99
  def notes=(notes)
72
- @notes = notes&.map { |n| DetailedDefinition.new(n) }
100
+ @notes.clear!
101
+ notes&.each { |note| @notes << note }
73
102
  end
74
103
 
75
104
  def definition=(definition)
76
- @definition = definition&.map { |d| DetailedDefinition.new(d) }
105
+ @definition.clear!
106
+ definition&.each { |definition| @definition << definition }
77
107
  end
78
108
 
79
109
  def designations=(designations)
80
- @designations = designations&.map do |designation|
81
- Designation::Base.from_h(designation)
82
- end
110
+ @designations.clear!
111
+ designations&.each { |designation| @designations << designation }
83
112
  end
84
113
 
85
114
  alias :terms= :designations=
86
115
 
116
+ def preferred_designations
117
+ @designations.select(&:preferred?)
118
+ end
119
+ alias :preferred_terms :preferred_designations
120
+
87
121
  def dates=(dates)
88
- @dates = dates&.map { |d| ConceptDate.new(d) }
122
+ @dates.clear!
123
+ dates&.each { |date| @dates << date }
89
124
  end
90
125
 
91
126
  def sources=(sources)
92
- @sources = sources&.map do |source|
93
- ConceptSource.new(source)
94
- end || []
127
+ @sources.clear!
128
+ sources&.each { |source| @sources << source }
95
129
  end
96
130
 
97
131
  def authoritative_source=(sources)
@@ -102,14 +136,19 @@ module Glossarist
102
136
 
103
137
  def to_h
104
138
  {
105
- "id" => id,
106
- "related" => related&.map(&:to_h),
107
- "terms" => (terms&.map(&:to_h) || []),
108
- "definition" => definition&.map(&:to_h),
109
- "notes" => notes&.map(&:to_h),
110
- "examples" => examples&.map(&:to_h),
111
- }
112
- .compact
139
+ "data" => {
140
+ "dates" => dates&.map(&:to_h),
141
+ "definition" => definition&.map(&:to_h),
142
+ "examples" => examples&.map(&:to_h),
143
+ "id" => id,
144
+ "lineage_source_similarity" => lineage_source_similarity,
145
+ "notes" => notes&.map(&:to_h),
146
+ "release" => release,
147
+ "sources" => sources.empty? ? nil : sources&.map(&:to_h),
148
+ "terms" => (terms&.map(&:to_h) || []),
149
+ "related" => related&.map(&:to_h),
150
+ }.compact,
151
+ }.compact
113
152
  end
114
153
 
115
154
  # @deprecated For legacy reasons only.
@@ -119,7 +158,7 @@ module Glossarist
119
158
  # rubocop:disable Metrics/AbcSize, Style/RescueModifier
120
159
  def self.from_h(hash)
121
160
  new.tap do |concept|
122
- concept.id = hash.dig("termid")
161
+ concept.id = hash.dig("termid") || hash.dig("id")
123
162
  concept.sources = hash.dig("sources")
124
163
  concept.related = hash.dig("related")
125
164
  concept.definition = hash.dig("definition")
@@ -141,7 +180,28 @@ module Glossarist
141
180
  end
142
181
 
143
182
  def related=(related)
144
- @related = related&.map { |r| RelatedConcept.new(r) } || []
183
+ @related.clear!
184
+ related&.each { |r| @related << r }
185
+ end
186
+
187
+ Glossarist::GlossaryDefinition::CONCEPT_DATE_TYPES.each do |type|
188
+ # Sets the ConceptDate and add it to dates list of the specified type.
189
+ define_method("date_#{type}=") do |date|
190
+ date_hash = {
191
+ "type" => type,
192
+ "date" => date,
193
+ }
194
+ @dates ||= []
195
+ @dates << ConceptDate.new(date_hash)
196
+ end
197
+ end
198
+
199
+ def normalize_args(args)
200
+ args.each do |arg|
201
+ data = arg.delete("data")
202
+
203
+ arg.merge!(data) if data
204
+ end
145
205
  end
146
206
  end
147
207
  end
@@ -12,8 +12,8 @@ module Glossarist
12
12
 
13
13
  def to_h
14
14
  {
15
- "type" => type,
16
15
  "date" => date,
16
+ "type" => type,
17
17
  }.compact
18
18
  end
19
19
  end
@@ -17,7 +17,13 @@ module Glossarist
17
17
  collection ||= ManagedConceptCollection.new
18
18
 
19
19
  Dir.glob(concepts_glob) do |filename|
20
- collection.store(load_concept_from_file(filename))
20
+ concept = load_concept_from_file(filename)
21
+ concept.localized_concepts.each do |_lang, id|
22
+ localized_concept = load_localized_concept(id)
23
+ concept.add_l10n(localized_concept)
24
+ end
25
+
26
+ collection.store(concept)
21
27
  end
22
28
  end
23
29
 
@@ -27,20 +33,49 @@ module Glossarist
27
33
  end
28
34
 
29
35
  def load_concept_from_file(filename)
30
- ManagedConcept.new(Psych.safe_load(File.read(filename)))
36
+ concept_hash = Psych.safe_load(File.read(filename), permitted_classes: [Date])
37
+ concept_hash["uuid"] = concept_hash["id"] || File.basename(filename, ".*")
38
+ ManagedConcept.new(concept_hash)
39
+ rescue Psych::SyntaxError => e
40
+ raise Glossarist::ParseError.new(filename: filename, line: e.line)
41
+ end
42
+
43
+ def load_localized_concept(id)
44
+ concept_hash = Psych.safe_load(
45
+ File.read(localized_concept_path(id)),
46
+ permitted_classes: [Date],
47
+ )
48
+ concept_hash["uuid"] = id
49
+
50
+ Config.class_for(:localized_concept).new(concept_hash)
31
51
  rescue Psych::SyntaxError => e
32
52
  raise Glossarist::ParseError.new(filename: filename, line: e.line)
33
53
  end
34
54
 
35
55
  def save_concept_to_file(concept)
36
- filename = File.join(path, "concept-#{concept.id}.yaml")
56
+ concept_dir = File.join(path, "concept")
57
+ localized_concept_dir = File.join(path, "localized_concept")
58
+
59
+ Dir.mkdir(concept_dir) unless Dir.exist?(concept_dir)
60
+ Dir.mkdir(localized_concept_dir) unless Dir.exist?(localized_concept_dir)
61
+
62
+ filename = File.join(concept_dir, "#{concept.uuid}.yaml")
37
63
  File.write(filename, Psych.dump(concept.to_h))
64
+
65
+ concept.localized_concepts.each do |lang, uuid|
66
+ filename = File.join(localized_concept_dir, "#{uuid}.yaml")
67
+ File.write(filename, Psych.dump(concept.localization(lang).to_h))
68
+ end
38
69
  end
39
70
 
40
71
  private
41
72
 
42
73
  def concepts_glob
43
- File.join(path, "concept-*.{yaml,yml}")
74
+ File.join(path, "concept", "*.{yaml,yml}")
75
+ end
76
+
77
+ def localized_concept_path(id)
78
+ Dir.glob(File.join(path, "localized_concept", "#{id}.{yaml,yml}"))&.first
44
79
  end
45
80
  end
46
81
  end
@@ -37,9 +37,9 @@ module Glossarist
37
37
  origin_hash = self.origin.to_h.empty? ? nil : self.origin.to_h
38
38
 
39
39
  {
40
+ "origin" => origin_hash,
40
41
  "type" => type.to_s,
41
42
  "status" => status&.to_s,
42
- "origin" => origin_hash,
43
43
  "modification" => modification,
44
44
  }.compact
45
45
  end
@@ -0,0 +1,15 @@
1
+ module Glossarist
2
+ class InvalidLanguageCodeError < Error
3
+ attr_reader :code
4
+
5
+ def initialize(code:)
6
+ @code = code
7
+
8
+ super()
9
+ end
10
+
11
+ def to_s
12
+ "Invalid value for language_code: `#{code}`. It must be 3 characters long string."
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module Glossarist
2
+ class InvalidTypeError < Error
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module Glossarist
2
+ class ParseError < Error
3
+ attr_accessor :line, :filename
4
+
5
+ def initialize(filename:, line: nil)
6
+ @filename = filename
7
+ @line = line
8
+
9
+ super()
10
+ end
11
+
12
+ def to_s
13
+ "Unable to parse file: #{filename}, error on line: #{line}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "error/invalid_type_error"
2
+ require_relative "error/invalid_language_code_error"
3
+ require_relative "error/parse_error"
4
+
5
+ module Glossarist
6
+ class Error < StandardError
7
+ end
8
+ end
@@ -8,7 +8,7 @@ module Glossarist
8
8
  # ISO 639-2 code for terminology.
9
9
  # @see https://www.loc.gov/standards/iso639-2/php/code_list.php code list
10
10
  # @return [String]
11
- attr_accessor :language_code
11
+ attr_reader :language_code
12
12
 
13
13
  # Must be one of the following:
14
14
  # +notValid+, +valid+, +superseded+, +retired+.
@@ -24,20 +24,29 @@ module Glossarist
24
24
  # @return [String]
25
25
  attr_accessor :classification
26
26
 
27
- attr_accessor :review_date
28
- attr_accessor :review_decision_date
29
- attr_accessor :review_decision_event
27
+ # Temporary fields
28
+ # @todo Need to remove these once the isotc211-glossary is fixed
29
+ attr_accessor *%i[
30
+ review_date
31
+ review_decision_date
32
+ review_decision_event
33
+ review_type
34
+ ]
30
35
 
31
- def initialize(*)
32
- @examples = []
33
-
34
- super
36
+ def language_code=(language_code)
37
+ if language_code.is_a?(String) && language_code.length == 3
38
+ @language_code = language_code
39
+ else
40
+ raise Glossarist::InvalidLanguageCodeError.new(code: language_code)
41
+ end
35
42
  end
36
43
 
37
- def to_h # rubocop:disable Metrics/MethodLength
38
- super.merge({
39
- "language_code" => language_code,
44
+ def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
45
+ hash = super
46
+
47
+ hash["data"].merge!({
40
48
  "domain" => domain,
49
+ "language_code" => language_code,
41
50
  "entry_status" => entry_status,
42
51
  "sources" => sources.empty? ? nil : sources&.map(&:to_h),
43
52
  "classification" => classification,
@@ -46,11 +55,13 @@ module Glossarist
46
55
  "review_decision_date" => review_decision_date,
47
56
  "review_decision_event" => review_decision_event,
48
57
  }.compact).merge(@extension_attributes)
58
+
59
+ hash
49
60
  end
50
61
 
51
62
  def self.from_h(hash)
52
63
  terms = hash["terms"]&.map { |h| Designation::Base.from_h(h) } || []
53
- sources = hash["authoritative_source"]&.each { |source| source.merge({ "type" => "authoritative"}) }
64
+ sources = hash["authoritative_source"]&.each { |source| source.merge({ "type" => "authoritative" }) }
54
65
 
55
66
  super(hash.merge({"terms" => terms, "sources" => sources}))
56
67
  end
@@ -8,6 +8,9 @@ module Glossarist
8
8
  # @return [String]
9
9
  attr_accessor :id
10
10
  alias :termid= :id=
11
+ alias :identifier= :id=
12
+
13
+ attr_accessor :uuid
11
14
 
12
15
  # @return [Array<RelatedConcept>]
13
16
  attr_reader :related
@@ -29,24 +32,27 @@ module Glossarist
29
32
  #
30
33
  # Keys are language codes and values are instances of {LocalizedConcept}.
31
34
  # @return [Hash<String, LocalizedConcept>]
32
- attr_accessor :localizations
35
+ attr_reader :localizations
33
36
 
34
37
  def initialize(attributes = {})
35
38
  @localizations = {}
36
- self.localized_concepts = attributes.values.grep(Hash)
39
+ @localized_concepts = {}
40
+ @localized_concept_class = Config.class_for(:localized_concept)
41
+ @uuid_namespace = Glossarist::Utilities::UUID::OID_NAMESPACE
37
42
 
38
43
  attributes = symbolize_keys(attributes)
39
- super(slice_keys(attributes, managed_concept_attributes))
40
- end
44
+ @uuid = attributes[:uuid]
41
45
 
42
- def localized_concepts=(localized_concepts_hash)
43
- @localized_concepts = localized_concepts_hash.map { |l| Config.class_for(:localized_concept).new(l) }.compact
46
+ data = attributes.delete(:data) || {}
47
+ data["groups"] = attributes[:groups]
44
48
 
45
- @localized_concepts.each do |l|
46
- add_l10n(l)
47
- end
49
+ data = symbolize_keys(data.compact)
48
50
 
49
- @localized_concepts
51
+ super(slice_keys(data, managed_concept_attributes))
52
+ end
53
+
54
+ def uuid
55
+ @uuid ||= Glossarist::Utilities::UUID.uuid_v5(@uuid_namespace, to_h.to_yaml)
50
56
  end
51
57
 
52
58
  def related=(related)
@@ -63,10 +69,51 @@ module Glossarist
63
69
  @groups = groups.is_a?(Array) ? groups : [groups]
64
70
  end
65
71
 
72
+ def localized_concepts=(localized_concepts)
73
+ return unless localized_concepts
74
+
75
+ if localized_concepts.is_a?(Hash)
76
+ @localized_concepts = stringify_keys(localized_concepts)
77
+ else
78
+ localized_concepts.each do |localized_concept|
79
+ lang = localized_concept["language_code"].to_s
80
+
81
+ @localized_concepts[lang] = Glossarist::Utilities::UUID.uuid_v5(@uuid_namespace, localized_concept.to_h.to_yaml)
82
+
83
+ add_localization(
84
+ @localized_concept_class.new(localized_concept["data"] || localized_concept),
85
+ )
86
+ end
87
+ end
88
+ end
89
+
90
+ def localizations=(localizations)
91
+ return unless localizations
92
+
93
+ @localizations = {}
94
+
95
+ localizations.each do |localized_concept|
96
+ unless localized_concept.is_a?(@localized_concept_class)
97
+ localized_concept = @localized_concept_class.new(
98
+ localized_concept["data"] || localized_concept,
99
+ )
100
+ end
101
+
102
+ add_l10n(localized_concept)
103
+ end
104
+ end
105
+
106
+ def localizations_hash
107
+ @localizations.map do |key, localized_concept|
108
+ [key, localized_concept.to_h]
109
+ end.to_h
110
+ end
111
+
66
112
  # Adds concept localization.
67
113
  # @param localized_concept [LocalizedConcept]
68
114
  def add_localization(localized_concept)
69
115
  lang = localized_concept.language_code
116
+ @localized_concepts[lang] = @localized_concepts[lang] || localized_concept.uuid
70
117
  localizations.store(lang, localized_concept)
71
118
  end
72
119
 
@@ -83,17 +130,18 @@ module Glossarist
83
130
 
84
131
  def to_h
85
132
  {
86
- "termid" => id,
87
- "term" => default_designation,
88
- "related" => related&.map(&:to_h),
89
- "dates" => dates&.empty? ? nil : dates&.map(&:to_h),
90
- "groups" => groups,
91
- }.merge(localizations.transform_values(&:to_h)).compact
133
+ "data" => {
134
+ "identifier" => id,
135
+ "localized_concepts" => localized_concepts.empty? ? nil : localized_concepts,
136
+ "groups" => groups,
137
+ }.compact,
138
+ }.compact
92
139
  end
93
140
 
94
141
  def default_designation
95
142
  localized = localization("eng") || localizations.values.first
96
- localized&.terms&.first&.designation
143
+ terms = localized&.preferred_terms&.first || localized&.terms&.first
144
+ terms&.designation
97
145
  end
98
146
 
99
147
  def default_definition
@@ -105,14 +153,34 @@ module Glossarist
105
153
  localization("eng") || localizations.values.first
106
154
  end
107
155
 
156
+ def date_accepted=(date)
157
+ date_hash = {
158
+ "type" => "accepted",
159
+ "date" => date,
160
+ }
161
+
162
+ @dates ||= []
163
+ @dates << ConceptDate.new(date_hash)
164
+ end
165
+
166
+ def date_accepted
167
+ @dates.find { |date| date.accepted? }
168
+ end
169
+
108
170
  def managed_concept_attributes
109
171
  %i[
172
+ data
110
173
  id
111
- termid
174
+ identifier
175
+ uuid
112
176
  related
113
177
  status
114
178
  dates
179
+ date_accepted
180
+ dateAccepted
115
181
  localized_concepts
182
+ localizedConcepts
183
+ localizations
116
184
  groups
117
185
  ].compact
118
186
  end
@@ -6,6 +6,7 @@ module Glossarist
6
6
 
7
7
  def initialize
8
8
  @managed_concepts = {}
9
+ @managed_concepts_ids = {}
9
10
  @concept_manager = ConceptManager.new
10
11
  end
11
12
 
@@ -39,7 +40,7 @@ module Glossarist
39
40
  # ManagedConcept ID
40
41
  # @return [ManagedConcept, nil]
41
42
  def fetch(id)
42
- @managed_concepts[id]
43
+ @managed_concepts[id] || @managed_concepts[@managed_concepts_ids[id]]
43
44
  end
44
45
 
45
46
  alias :[] :fetch
@@ -52,16 +53,17 @@ module Glossarist
52
53
  # ManagedConcept ID
53
54
  # @return [ManagedConcept]
54
55
  def fetch_or_initialize(id)
55
- fetch(id) or store(ManagedConcept.new(id: id))
56
+ fetch(id) or store(ManagedConcept.new(data: { id: id }))
56
57
  end
57
58
 
58
- # Adds concept to the collection. If collection contains a concept with
59
+ # Adds concept to the collection. If collection contains a concept with
59
60
  # the same ID already, that concept is replaced.
60
61
  #
61
62
  # @param managed_concept [ManagedConcept]
62
63
  # ManagedConcept about to be added
63
64
  def store(managed_concept)
64
- @managed_concepts[managed_concept.id] = managed_concept
65
+ @managed_concepts[managed_concept.uuid] = managed_concept
66
+ @managed_concepts_ids[managed_concept.id] = managed_concept.uuid if managed_concept.id
65
67
  end
66
68
 
67
69
  alias :<< :store
@@ -18,7 +18,11 @@ module Glossarist
18
18
  def set_attribute(name, value)
19
19
  public_send("#{name}=", value)
20
20
  rescue NoMethodError
21
- if Config.extension_attributes.include?(name)
21
+ # adding support for camel case
22
+ if name.match?(/[A-Z]/)
23
+ name = snake_case(name.to_s).to_sym
24
+ retry
25
+ elsif Config.extension_attributes.include?(name)
22
26
  extension_attributes[name] = value
23
27
  else
24
28
  raise ArgumentError, "#{self.class.name} does not have " +
@@ -29,5 +33,9 @@ module Glossarist
29
33
  def self.from_h(hash)
30
34
  new(hash)
31
35
  end
36
+
37
+ def snake_case(str)
38
+ str.gsub(/([A-Z])/) { "_#{$1.downcase}" }
39
+ end
32
40
  end
33
41
  end
@@ -9,7 +9,26 @@ module Glossarist
9
9
  def symbolize_keys(hash)
10
10
  result = {}
11
11
  hash.each_pair do |key, value|
12
- result[key.to_sym] = value
12
+ result[key.to_sym] = if value.is_a?(Hash)
13
+ symbolize_keys(value)
14
+ else
15
+ value
16
+ end
17
+ end
18
+ result
19
+ end
20
+
21
+ # Hash#transform_keys is not available in Ruby 2.4
22
+ # so we have to do this ourselves :(
23
+ # symbolize hash keys
24
+ def stringify_keys(hash)
25
+ result = {}
26
+ hash.each_pair do |key, value|
27
+ result[key.to_s] = if value.is_a?(Hash)
28
+ stringify_keys(value)
29
+ else
30
+ value
31
+ end
13
32
  end
14
33
  result
15
34
  end
@@ -24,6 +43,22 @@ module Glossarist
24
43
  end
25
44
  result
26
45
  end
46
+
47
+ def convert_keys_to_snake_case(hash)
48
+ result = {}
49
+ hash.each_pair do |key, value|
50
+ result[snake_case(key)] = if value.is_a?(Hash)
51
+ convert_keys_to_snake_case(value)
52
+ else
53
+ value
54
+ end
55
+ end
56
+ result
57
+ end
58
+
59
+ def snake_case(str)
60
+ str.gsub(/([A-Z])/) { "_#{$1.downcase}" }
61
+ end
27
62
  end
28
63
  end
29
64
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # extracted from https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/digest/uuid.rb
4
+ # to generate uuid_v5 for concept files
5
+
6
+ require "securerandom"
7
+ require "openssl"
8
+
9
+ module Glossarist
10
+ module Utilities
11
+ module UUID
12
+ DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
13
+ URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
14
+ OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
15
+ X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
16
+
17
+ # Generates a v5 non-random UUID (Universally Unique IDentifier).
18
+ #
19
+ # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs.
20
+ # uuid_from_hash always generates the same UUID for a given name and namespace combination.
21
+ #
22
+ # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
23
+ def self.uuid_from_hash(hash_class, namespace, name)
24
+ if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5
25
+ version = 3
26
+ elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1
27
+ version = 5
28
+ else
29
+ raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}."
30
+ end
31
+
32
+ uuid_namespace = pack_uuid_namespace(namespace)
33
+
34
+ hash = hash_class.new
35
+ hash.update(uuid_namespace)
36
+ hash.update(name)
37
+
38
+ ary = hash.digest.unpack("NnnnnN")
39
+ ary[2] = (ary[2] & 0x0FFF) | (version << 12)
40
+ ary[3] = (ary[3] & 0x3FFF) | 0x8000
41
+
42
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
43
+ end
44
+
45
+ # Convenience method for uuid_from_hash using OpenSSL::Digest::MD5.
46
+ def self.uuid_v3(uuid_namespace, name)
47
+ uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name)
48
+ end
49
+
50
+ # Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1.
51
+ def self.uuid_v5(uuid_namespace, name)
52
+ uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name)
53
+ end
54
+
55
+ # Convenience method for SecureRandom.uuid.
56
+ def self.uuid_v4
57
+ SecureRandom.uuid
58
+ end
59
+
60
+ def self.pack_uuid_namespace(namespace)
61
+ if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace)
62
+ namespace
63
+ else
64
+ match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/)
65
+
66
+ raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present?
67
+
68
+ match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN")
69
+ end
70
+ end
71
+
72
+ private_class_method :pack_uuid_namespace
73
+ end
74
+ end
75
+ end
@@ -3,3 +3,4 @@
3
3
  require_relative "utilities/enum"
4
4
  require_relative "utilities/boolean_attributes"
5
5
  require_relative "utilities/common_functions"
6
+ require_relative "utilities/uuid"
@@ -4,5 +4,5 @@
4
4
  #
5
5
 
6
6
  module Glossarist
7
- VERSION = "1.0.9"
7
+ VERSION = "2.0.0"
8
8
  end
data/lib/glossarist.rb CHANGED
@@ -30,28 +30,9 @@ require_relative "glossarist/non_verb_rep"
30
30
  require_relative "glossarist/collections"
31
31
 
32
32
  require_relative "glossarist/config"
33
+ require_relative "glossarist/error"
33
34
 
34
35
  module Glossarist
35
- class Error < StandardError; end
36
-
37
- class InvalidTypeError < StandardError; end
38
-
39
- class ParseError < StandardError
40
- attr_accessor :line, :filename
41
-
42
- def initialize(filename:, line: nil)
43
- @filename = filename
44
- @line = line
45
-
46
- super()
47
- end
48
-
49
- def to_s
50
- "Unable to parse file: #{filename}, error on line: #{line}"
51
- end
52
- end
53
- # Your code goes here...
54
-
55
36
  def self.configure
56
37
  config = Glossarist::Config.instance
57
38
 
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: 1.0.9
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-12 00:00:00.000000000 Z
11
+ date: 2023-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: relaton
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.15.0
19
+ version: 1.16.0
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: 1.15.0
26
+ version: 1.16.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: thor
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +109,8 @@ files:
109
109
  - lib/glossarist/collections.rb
110
110
  - lib/glossarist/collections/asset_collection.rb
111
111
  - lib/glossarist/collections/bibliography_collection.rb
112
+ - lib/glossarist/collections/collection.rb
113
+ - lib/glossarist/collections/designation_collection.rb
112
114
  - lib/glossarist/concept.rb
113
115
  - lib/glossarist/concept_date.rb
114
116
  - lib/glossarist/concept_manager.rb
@@ -124,6 +126,10 @@ files:
124
126
  - lib/glossarist/designation/letter_symbol.rb
125
127
  - lib/glossarist/designation/symbol.rb
126
128
  - lib/glossarist/detailed_definition.rb
129
+ - lib/glossarist/error.rb
130
+ - lib/glossarist/error/invalid_language_code_error.rb
131
+ - lib/glossarist/error/invalid_type_error.rb
132
+ - lib/glossarist/error/parse_error.rb
127
133
  - lib/glossarist/glossary_definition.rb
128
134
  - lib/glossarist/localized_concept.rb
129
135
  - lib/glossarist/managed_concept.rb
@@ -138,6 +144,7 @@ files:
138
144
  - lib/glossarist/utilities/enum/class_methods.rb
139
145
  - lib/glossarist/utilities/enum/enum_collection.rb
140
146
  - lib/glossarist/utilities/enum/instance_methods.rb
147
+ - lib/glossarist/utilities/uuid.rb
141
148
  - lib/glossarist/version.rb
142
149
  homepage: https://github.com/glossarist/glossarist-ruby
143
150
  licenses: