glossarist 1.1.0 → 2.0.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/config.yml +2 -0
- data/glossarist.gemspec +1 -1
- data/lib/glossarist/collections/collection.rb +34 -0
- data/lib/glossarist/collections/designation_collection.rb +15 -0
- data/lib/glossarist/collections.rb +2 -0
- data/lib/glossarist/concept.rb +79 -25
- data/lib/glossarist/concept_date.rb +1 -1
- data/lib/glossarist/concept_manager.rb +54 -4
- data/lib/glossarist/concept_source.rb +1 -1
- data/lib/glossarist/localized_concept.rb +16 -13
- data/lib/glossarist/managed_concept.rb +86 -18
- data/lib/glossarist/managed_concept_collection.rb +6 -4
- data/lib/glossarist/model.rb +9 -1
- data/lib/glossarist/utilities/common_functions.rb +36 -1
- data/lib/glossarist/utilities/uuid.rb +75 -0
- data/lib/glossarist/utilities.rb +1 -0
- data/lib/glossarist/v1_reader.rb +28 -0
- data/lib/glossarist/version.rb +1 -1
- data/lib/glossarist.rb +1 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0462402c995f5b7e9fb01a4ee0d8e1f9ee3c5633c18fa071d9db6aa9f21f99f
|
4
|
+
data.tar.gz: 444e2d92467c4101edc81bc1af65fb6aeecaa0e138f63a49076f2283802baf17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab7f0f87234df6fcd73286969c53e1186fcd247b4d8972a8d305b74cc995a4200785db43215a0d5aae719bc164123035f87a1756c104aa3d36b38775585f2019
|
7
|
+
data.tar.gz: d5f4a8834eb1d8bbb19d8352c13159e4626c8ff65233b5269658b77c4ee753bbcd8dcaf4502981b1ae2556a13e8af8deb45661e86f485cf7df25cf0f9d1cbb57
|
data/config.yml
CHANGED
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.16
|
33
|
+
spec.add_dependency "relaton", "~> 1.16"
|
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
|
data/lib/glossarist/concept.rb
CHANGED
@@ -9,6 +9,8 @@ module Glossarist
|
|
9
9
|
# @return [String]
|
10
10
|
attr_reader :id
|
11
11
|
|
12
|
+
attr_writer :uuid
|
13
|
+
|
12
14
|
# Concept designations.
|
13
15
|
# @todo Alias +terms+ exists only for legacy reasons and will be removed.
|
14
16
|
# @return [Array<Designations::Base>]
|
@@ -42,23 +44,42 @@ module Glossarist
|
|
42
44
|
# Contains list of extended attributes
|
43
45
|
attr_accessor :extension_attributes
|
44
46
|
|
45
|
-
|
47
|
+
attr_accessor :lineage_source
|
48
|
+
attr_accessor :lineage_source_similarity
|
49
|
+
|
50
|
+
attr_accessor :release
|
51
|
+
|
52
|
+
def initialize(*args)
|
46
53
|
@localizations = {}
|
47
|
-
@sources =
|
48
|
-
@related =
|
49
|
-
@
|
50
|
-
@
|
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
|
51
62
|
@extension_attributes = {}
|
52
63
|
|
64
|
+
normalize_args(args)
|
65
|
+
|
53
66
|
super
|
54
67
|
end
|
55
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
|
+
|
56
76
|
def id=(id)
|
57
|
-
raise(Glossarist::Error, "id
|
77
|
+
raise(Glossarist::Error, "Expect id to be a string, Got #{id.class} (#{id})") unless id.is_a?(String) || id.nil?
|
58
78
|
|
59
79
|
@id = id
|
60
80
|
end
|
61
81
|
alias :termid= :id=
|
82
|
+
alias :identifier= :id=
|
62
83
|
|
63
84
|
# List of authorative sources.
|
64
85
|
# @todo Alias +authoritative_source+ exists for legacy reasons and may be
|
@@ -71,33 +92,40 @@ module Glossarist
|
|
71
92
|
attr_reader :dates
|
72
93
|
|
73
94
|
def examples=(examples)
|
74
|
-
@examples
|
95
|
+
@examples.clear!
|
96
|
+
examples&.each { |example| @examples << example }
|
75
97
|
end
|
76
98
|
|
77
99
|
def notes=(notes)
|
78
|
-
@notes
|
100
|
+
@notes.clear!
|
101
|
+
notes&.each { |note| @notes << note }
|
79
102
|
end
|
80
103
|
|
81
104
|
def definition=(definition)
|
82
|
-
@definition
|
105
|
+
@definition.clear!
|
106
|
+
definition&.each { |definition| @definition << definition }
|
83
107
|
end
|
84
108
|
|
85
109
|
def designations=(designations)
|
86
|
-
@designations
|
87
|
-
|
88
|
-
end
|
110
|
+
@designations.clear!
|
111
|
+
designations&.each { |designation| @designations << designation }
|
89
112
|
end
|
90
113
|
|
91
114
|
alias :terms= :designations=
|
92
115
|
|
116
|
+
def preferred_designations
|
117
|
+
@designations.select(&:preferred?)
|
118
|
+
end
|
119
|
+
alias :preferred_terms :preferred_designations
|
120
|
+
|
93
121
|
def dates=(dates)
|
94
|
-
@dates
|
122
|
+
@dates.clear!
|
123
|
+
dates&.each { |date| @dates << date }
|
95
124
|
end
|
96
125
|
|
97
126
|
def sources=(sources)
|
98
|
-
@sources
|
99
|
-
|
100
|
-
end || []
|
127
|
+
@sources.clear!
|
128
|
+
sources&.each { |source| @sources << source }
|
101
129
|
end
|
102
130
|
|
103
131
|
def authoritative_source=(sources)
|
@@ -108,14 +136,19 @@ module Glossarist
|
|
108
136
|
|
109
137
|
def to_h
|
110
138
|
{
|
111
|
-
"
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
119
152
|
end
|
120
153
|
|
121
154
|
# @deprecated For legacy reasons only.
|
@@ -147,7 +180,28 @@ module Glossarist
|
|
147
180
|
end
|
148
181
|
|
149
182
|
def related=(related)
|
150
|
-
@related
|
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
|
151
205
|
end
|
152
206
|
end
|
153
207
|
end
|
@@ -17,7 +17,13 @@ module Glossarist
|
|
17
17
|
collection ||= ManagedConceptCollection.new
|
18
18
|
|
19
19
|
Dir.glob(concepts_glob) do |filename|
|
20
|
-
|
20
|
+
concept = if v1_collection?
|
21
|
+
Glossarist::V1Reader.load_concept_from_file(filename)
|
22
|
+
else
|
23
|
+
load_concept_from_file(filename)
|
24
|
+
end
|
25
|
+
|
26
|
+
collection.store(concept)
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
@@ -27,20 +33,64 @@ module Glossarist
|
|
27
33
|
end
|
28
34
|
|
29
35
|
def load_concept_from_file(filename)
|
30
|
-
|
36
|
+
concept_hash = Psych.safe_load(File.read(filename), permitted_classes: [Date])
|
37
|
+
concept_hash["uuid"] = concept_hash["id"] || File.basename(filename, ".*")
|
38
|
+
|
39
|
+
concept = ManagedConcept.new(concept_hash)
|
40
|
+
concept.localized_concepts.each do |_lang, id|
|
41
|
+
localized_concept = load_localized_concept(id)
|
42
|
+
concept.add_l10n(localized_concept)
|
43
|
+
end
|
44
|
+
|
45
|
+
concept
|
46
|
+
rescue Psych::SyntaxError => e
|
47
|
+
raise Glossarist::ParseError.new(filename: filename, line: e.line)
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_localized_concept(id)
|
51
|
+
concept_hash = Psych.safe_load(
|
52
|
+
File.read(localized_concept_path(id)),
|
53
|
+
permitted_classes: [Date],
|
54
|
+
)
|
55
|
+
concept_hash["uuid"] = id
|
56
|
+
|
57
|
+
Config.class_for(:localized_concept).new(concept_hash)
|
31
58
|
rescue Psych::SyntaxError => e
|
32
59
|
raise Glossarist::ParseError.new(filename: filename, line: e.line)
|
33
60
|
end
|
34
61
|
|
35
62
|
def save_concept_to_file(concept)
|
36
|
-
|
63
|
+
concept_dir = File.join(path, "concept")
|
64
|
+
localized_concept_dir = File.join(path, "localized_concept")
|
65
|
+
|
66
|
+
Dir.mkdir(concept_dir) unless Dir.exist?(concept_dir)
|
67
|
+
Dir.mkdir(localized_concept_dir) unless Dir.exist?(localized_concept_dir)
|
68
|
+
|
69
|
+
filename = File.join(concept_dir, "#{concept.uuid}.yaml")
|
37
70
|
File.write(filename, Psych.dump(concept.to_h))
|
71
|
+
|
72
|
+
concept.localized_concepts.each do |lang, uuid|
|
73
|
+
filename = File.join(localized_concept_dir, "#{uuid}.yaml")
|
74
|
+
File.write(filename, Psych.dump(concept.localization(lang).to_h))
|
75
|
+
end
|
38
76
|
end
|
39
77
|
|
40
78
|
private
|
41
79
|
|
42
80
|
def concepts_glob
|
43
|
-
|
81
|
+
if v1_collection?
|
82
|
+
File.join(path, "concept-*.{yaml,yml}")
|
83
|
+
else
|
84
|
+
File.join(path, "concept", "*.{yaml,yml}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def localized_concept_path(id)
|
89
|
+
Dir.glob(File.join(path, "localized_concept", "#{id}.{yaml,yml}"))&.first
|
90
|
+
end
|
91
|
+
|
92
|
+
def v1_collection?
|
93
|
+
@v1_collection ||= !Dir.glob(File.join(path, "concept-*.{yaml,yml}")).empty?
|
44
94
|
end
|
45
95
|
end
|
46
96
|
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
|
@@ -24,15 +24,14 @@ module Glossarist
|
|
24
24
|
# @return [String]
|
25
25
|
attr_accessor :classification
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
attr_accessor
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
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
|
+
]
|
36
35
|
|
37
36
|
def language_code=(language_code)
|
38
37
|
if language_code.is_a?(String) && language_code.length == 3
|
@@ -42,10 +41,12 @@ module Glossarist
|
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
45
|
-
def to_h # rubocop:disable Metrics/MethodLength
|
46
|
-
super
|
47
|
-
|
44
|
+
def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
45
|
+
hash = super
|
46
|
+
|
47
|
+
hash["data"].merge!({
|
48
48
|
"domain" => domain,
|
49
|
+
"language_code" => language_code,
|
49
50
|
"entry_status" => entry_status,
|
50
51
|
"sources" => sources.empty? ? nil : sources&.map(&:to_h),
|
51
52
|
"classification" => classification,
|
@@ -54,11 +55,13 @@ module Glossarist
|
|
54
55
|
"review_decision_date" => review_decision_date,
|
55
56
|
"review_decision_event" => review_decision_event,
|
56
57
|
}.compact).merge(@extension_attributes)
|
58
|
+
|
59
|
+
hash
|
57
60
|
end
|
58
61
|
|
59
62
|
def self.from_h(hash)
|
60
63
|
terms = hash["terms"]&.map { |h| Designation::Base.from_h(h) } || []
|
61
|
-
sources = hash["authoritative_source"]&.each { |source| source.merge({ "type" => "authoritative"}) }
|
64
|
+
sources = hash["authoritative_source"]&.each { |source| source.merge({ "type" => "authoritative" }) }
|
62
65
|
|
63
66
|
super(hash.merge({"terms" => terms, "sources" => sources}))
|
64
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
|
-
|
35
|
+
attr_reader :localizations
|
33
36
|
|
34
37
|
def initialize(attributes = {})
|
35
38
|
@localizations = {}
|
36
|
-
|
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
|
-
|
40
|
-
end
|
44
|
+
@uuid = attributes[:uuid]
|
41
45
|
|
42
|
-
|
43
|
-
|
46
|
+
data = attributes.delete(:data) || {}
|
47
|
+
data["groups"] = attributes[:groups]
|
44
48
|
|
45
|
-
|
46
|
-
add_l10n(l)
|
47
|
-
end
|
49
|
+
data = symbolize_keys(data.compact)
|
48
50
|
|
49
|
-
|
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
|
-
"
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}.
|
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
|
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
|
-
|
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.
|
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.
|
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
|
data/lib/glossarist/model.rb
CHANGED
@@ -18,7 +18,11 @@ module Glossarist
|
|
18
18
|
def set_attribute(name, value)
|
19
19
|
public_send("#{name}=", value)
|
20
20
|
rescue NoMethodError
|
21
|
-
|
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
|
data/lib/glossarist/utilities.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Glossarist
|
4
|
+
# An adapter to read concepts in V1 format, converts them to v2 format and
|
5
|
+
# load into glossarist concept model.
|
6
|
+
class V1Reader
|
7
|
+
def self.load_concept_from_file(filename)
|
8
|
+
new.load_concept_from_file(filename)
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_concept_from_file(filename)
|
12
|
+
concept_hash = Psych.safe_load(File.read(filename), permitted_classes: [Date])
|
13
|
+
ManagedConcept.new(generate_v2_concept_hash(concept_hash))
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def generate_v2_concept_hash(concept_hash)
|
19
|
+
v2_concept = { "groups" => concept_hash["groups"] }
|
20
|
+
v2_concept["data"] = {
|
21
|
+
"identifier" => concept_hash["termid"],
|
22
|
+
"localized_concepts" => concept_hash.values.grep(Hash),
|
23
|
+
}
|
24
|
+
|
25
|
+
v2_concept
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/glossarist/version.rb
CHANGED
data/lib/glossarist.rb
CHANGED
@@ -26,6 +26,7 @@ require_relative "glossarist/managed_concept_collection"
|
|
26
26
|
require_relative "glossarist/concept_manager"
|
27
27
|
require_relative "glossarist/managed_concept"
|
28
28
|
require_relative "glossarist/non_verb_rep"
|
29
|
+
require_relative "glossarist/v1_reader"
|
29
30
|
|
30
31
|
require_relative "glossarist/collections"
|
31
32
|
|
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:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-28 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.16
|
19
|
+
version: '1.16'
|
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.16
|
26
|
+
version: '1.16'
|
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
|
@@ -142,6 +144,8 @@ files:
|
|
142
144
|
- lib/glossarist/utilities/enum/class_methods.rb
|
143
145
|
- lib/glossarist/utilities/enum/enum_collection.rb
|
144
146
|
- lib/glossarist/utilities/enum/instance_methods.rb
|
147
|
+
- lib/glossarist/utilities/uuid.rb
|
148
|
+
- lib/glossarist/v1_reader.rb
|
145
149
|
- lib/glossarist/version.rb
|
146
150
|
homepage: https://github.com/glossarist/glossarist-ruby
|
147
151
|
licenses:
|