glossarist 1.1.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|