glossarist-new 1.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +15 -0
  3. data/.github/workflows/test.yml +34 -0
  4. data/.gitignore +18 -0
  5. data/.hound.yml +3 -0
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +35 -0
  8. data/Gemfile +5 -0
  9. data/LICENSE.txt +23 -0
  10. data/README.adoc +11 -0
  11. data/Rakefile +8 -0
  12. data/config.yml +83 -0
  13. data/glossarist.gemspec +36 -0
  14. data/lib/glossarist/citation.rb +85 -0
  15. data/lib/glossarist/collection.rb +86 -0
  16. data/lib/glossarist/concept.rb +143 -0
  17. data/lib/glossarist/concept_date.rb +20 -0
  18. data/lib/glossarist/concept_manager.rb +44 -0
  19. data/lib/glossarist/concept_source.rb +62 -0
  20. data/lib/glossarist/designation/abbreviation.rb +23 -0
  21. data/lib/glossarist/designation/base.rb +37 -0
  22. data/lib/glossarist/designation/expression.rb +50 -0
  23. data/lib/glossarist/designation/grammar_info.rb +58 -0
  24. data/lib/glossarist/designation/graphical_symbol.rb +17 -0
  25. data/lib/glossarist/designation/letter_symbol.rb +19 -0
  26. data/lib/glossarist/designation/symbol.rb +21 -0
  27. data/lib/glossarist/designation.rb +27 -0
  28. data/lib/glossarist/detailed_definition.rb +31 -0
  29. data/lib/glossarist/glossary_definition.rb +29 -0
  30. data/lib/glossarist/localized_concept.rb +61 -0
  31. data/lib/glossarist/managed_concept.rb +107 -0
  32. data/lib/glossarist/managed_concept_collection.rb +79 -0
  33. data/lib/glossarist/model.rb +29 -0
  34. data/lib/glossarist/non_verb_rep.rb +18 -0
  35. data/lib/glossarist/related_concept.rb +31 -0
  36. data/lib/glossarist/utilities/boolean_attributes.rb +35 -0
  37. data/lib/glossarist/utilities/common_functions.rb +29 -0
  38. data/lib/glossarist/utilities/enum/class_methods.rb +99 -0
  39. data/lib/glossarist/utilities/enum/enum_collection.rb +45 -0
  40. data/lib/glossarist/utilities/enum/instance_methods.rb +55 -0
  41. data/lib/glossarist/utilities/enum.rb +21 -0
  42. data/lib/glossarist/utilities.rb +5 -0
  43. data/lib/glossarist/version.rb +8 -0
  44. data/lib/glossarist.rb +31 -0
  45. metadata +131 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptSource < Model
5
+ include Glossarist::Utilities::Enum
6
+ include Glossarist::Utilities::CommonFunctions
7
+
8
+ register_enum :status, Glossarist::GlossaryDefinition::CONCEPT_SOURCE_STATUSES
9
+ register_enum :type, Glossarist::GlossaryDefinition::CONCEPT_SOURCE_TYPES
10
+
11
+ attr_reader :origin
12
+ alias_method :ref, :origin
13
+
14
+ attr_accessor :modification
15
+
16
+ def initialize(attributes = {})
17
+ if rel = attributes.delete("relationship")
18
+ self.status = rel["type"]
19
+ self.modification = rel["modification"]
20
+ end
21
+
22
+ self.origin = slice_keys(attributes, ref_param_names)
23
+
24
+ remaining_attributes = attributes.dup
25
+ ref_param_names.each { |k| remaining_attributes.delete(k) }
26
+
27
+ super(remaining_attributes)
28
+ end
29
+
30
+ def origin=(origin)
31
+ @origin = Citation.new(origin)
32
+ end
33
+
34
+ alias_method :ref=, :origin=
35
+
36
+ def to_h
37
+ origin_hash = self.origin.to_h.empty? ? nil : self.origin.to_h
38
+
39
+ {
40
+ "type" => type.to_s,
41
+ "status" => status&.to_s,
42
+ "origin" => origin_hash,
43
+ "modification" => modification,
44
+ }.compact
45
+ end
46
+
47
+ private
48
+
49
+ def ref_param_names
50
+ %w[
51
+ ref
52
+ text
53
+ source
54
+ id
55
+ version
56
+ clause
57
+ link
58
+ original
59
+ ]
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "expression"
4
+ require_relative "../utilities"
5
+
6
+ module Glossarist
7
+ module Designation
8
+ class Abbreviation < Expression
9
+ include Glossarist::Utilities::Enum
10
+
11
+ register_enum :type, Glossarist::GlossaryDefinition::ABBREVIATION_TYPES
12
+
13
+ attr_accessor :international
14
+
15
+ def to_h
16
+ super().merge({
17
+ "type" => type.to_s,
18
+ "international" => international,
19
+ }).compact
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module Designation
5
+ class Base < Model
6
+ include Glossarist::Utilities::Enum
7
+
8
+ # @note This is not entirely aligned with agreed schema and may be
9
+ # changed.
10
+ attr_accessor :designation
11
+
12
+ attr_accessor :geographical_area
13
+ register_enum :normative_status, Glossarist::GlossaryDefinition::DESIGNATION_BASE_NORMATIVE_STATUSES
14
+
15
+ def self.from_h(hash)
16
+ type = hash["type"]
17
+
18
+ if type.nil? || /\w/ !~ type
19
+ raise ArgumentError, "designation type is missing"
20
+ end
21
+
22
+ designation_subclass = SERIALIZED_TYPES[type]
23
+
24
+ if self == Base
25
+ # called on Base class, delegate it to proper subclass
26
+ SERIALIZED_TYPES[type].from_h(hash)
27
+ else
28
+ # called on subclass, instantiate object
29
+ unless SERIALIZED_TYPES[self] == type
30
+ raise ArgumentError, "unexpected designation type: #{type}"
31
+ end
32
+ super(hash.reject { |k, _| k == "type" })
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Glossarist
6
+ module Designation
7
+ class Expression < Base
8
+ attr_accessor :prefix
9
+ attr_accessor :usage_info
10
+
11
+ # List of grammar info.
12
+ # @return [Array<GrammarInfo>]
13
+ attr_reader :grammar_info
14
+
15
+ def grammar_info=(grammar_info)
16
+ @grammar_info = grammar_info.map { |g| GrammarInfo.new(g) }
17
+ end
18
+
19
+ # @todo Added to cater for current iev-data implementation,
20
+ # might be removed in the future.
21
+ def self.from_h(hash)
22
+ gender = hash.delete("gender") || hash.delete(:gender)
23
+ number = hash.delete("plurality") || hash.delete(:plurality)
24
+ part_of_speech = hash.delete("part_of_speech") || hash.delete(:part_of_speech)
25
+
26
+ if gender || number || part_of_speech
27
+ hash["grammar_info"] = [{
28
+ "gender" => gender,
29
+ "number" => number,
30
+ part_of_speech => part_of_speech,
31
+ }.compact]
32
+ end
33
+
34
+ super
35
+ end
36
+
37
+ def to_h
38
+ {
39
+ "type" => "expression",
40
+ "prefix" => prefix,
41
+ "normative_status" => normative_status,
42
+ "usage_info" => usage_info,
43
+ "designation" => designation,
44
+ "geographical_area" => geographical_area,
45
+ "grammar_info" => grammar_info&.map(&:to_h),
46
+ }.compact
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../utilities"
4
+
5
+ module Glossarist
6
+ module Designation
7
+ class GrammarInfo
8
+ include Glossarist::Utilities::Enum
9
+ include Glossarist::Utilities::BooleanAttributes
10
+ include Glossarist::Utilities::CommonFunctions
11
+
12
+ register_enum :gender, Glossarist::GlossaryDefinition::GRAMMAR_INFO_GENDERS, multiple: true
13
+ register_enum :number, Glossarist::GlossaryDefinition::GRAMMAR_INFO_NUMBERS, multiple: true
14
+
15
+ register_boolean_attributes Glossarist::GlossaryDefinition::GRAMMAR_INFO_BOOLEAN_ATTRIBUTES
16
+
17
+ def initialize(options = {})
18
+ sanitized_options(options).each do |attr, value|
19
+ public_send("#{attr}=", value)
20
+ end
21
+ end
22
+
23
+ def part_of_speech=(pos)
24
+ public_send("#{pos}=", pos)
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ "preposition" => preposition?,
30
+ "participle" => participle?,
31
+ "adj" => adj?,
32
+ "verb" => verb?,
33
+ "adverb" => adverb?,
34
+ "noun" => noun?,
35
+ "gender" => gender,
36
+ "number" => number,
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ def sanitized_options(options)
43
+ hash = symbolize_keys(options)
44
+ slice_keys(hash, [
45
+ :gender,
46
+ :number,
47
+ :preposition,
48
+ :participle,
49
+ :adj,
50
+ :verb,
51
+ :adverb,
52
+ :noun,
53
+ :part_of_speech,
54
+ ])
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module Designation
5
+ class GraphicalSymbol < Symbol
6
+ attr_accessor :text
7
+ attr_accessor :image
8
+
9
+ def to_h
10
+ super.merge(
11
+ "text" => text,
12
+ "image" => image,
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module Designation
5
+ class LetterSymbol < Symbol
6
+ attr_accessor :text
7
+ attr_accessor :language
8
+ attr_accessor :script
9
+
10
+ def to_h
11
+ super.merge(
12
+ "text" => text,
13
+ "language" => language,
14
+ "script" => script,
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Glossarist
6
+ module Designation
7
+ class Symbol < Base
8
+ attr_accessor :international
9
+
10
+ def to_h
11
+ {
12
+ "type" => Glossarist::Designation::SERIALIZED_TYPES[self.class],
13
+ "normative_status" => normative_status,
14
+ "geographical_area" => geographical_area,
15
+ "designation" => designation,
16
+ "international" => international,
17
+ }.compact
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright 2021 Ribose Inc.
4
+ #
5
+
6
+ require_relative "designation/abbreviation"
7
+ require_relative "designation/base"
8
+ require_relative "designation/expression"
9
+ require_relative "designation/grammar_info"
10
+ require_relative "designation/symbol"
11
+ require_relative "designation/graphical_symbol"
12
+ require_relative "designation/letter_symbol"
13
+
14
+ module Glossarist
15
+ module Designation
16
+ # Bi-directional class-to-string mapping for STI-like serialization.
17
+ SERIALIZED_TYPES = {
18
+ Expression => "expression",
19
+ Symbol => "symbol",
20
+ Abbreviation => "abbreviation",
21
+ GraphicalSymbol => "graphical_symbol",
22
+ LetterSymbol => "letter_symbol",
23
+ }
24
+ .tap { |h| h.merge!(h.invert) }
25
+ .freeze
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class DetailedDefinition < Model
5
+
6
+ def initialize(attributes = {})
7
+ if attributes.is_a?(Hash)
8
+ super
9
+ else
10
+ self.content = attributes
11
+ end
12
+ end
13
+
14
+ # @return [String]
15
+ attr_accessor :content
16
+
17
+ # @return [Array<ConceptSource>]
18
+ attr_reader :sources
19
+
20
+ def sources=(sources)
21
+ @sources = sources.map { |s| ConceptSource.new(s) }
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ "content" => content,
27
+ "sources" => sources&.map(&:to_h),
28
+ }.compact
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Glossarist
6
+ module GlossaryDefinition
7
+ config = YAML.load_file(File.expand_path("../../../config.yml", __FILE__)) || {}
8
+
9
+ CONCEPT_SOURCE_STATUSES = config.dig("concept_source", "status").freeze
10
+
11
+ CONCEPT_SOURCE_TYPES = config.dig("concept_source", "type").freeze
12
+
13
+ RELATED_CONCEPT_TYPES = config.dig("related_concept", "type").freeze
14
+
15
+ ABBREVIATION_TYPES = config.dig("abbreviation", "type").freeze
16
+
17
+ GRAMMAR_INFO_BOOLEAN_ATTRIBUTES = config.dig("grammar_info", "boolean_attribute").freeze
18
+
19
+ GRAMMAR_INFO_GENDERS = config.dig("grammar_info", "gender").freeze
20
+
21
+ GRAMMAR_INFO_NUMBERS = config.dig("grammar_info", "number").freeze
22
+
23
+ DESIGNATION_BASE_NORMATIVE_STATUSES = config.dig("designation", "base", "normative_status").freeze
24
+
25
+ CONCEPT_DATE_TYPES = config.dig("concept_date", "type").freeze
26
+
27
+ CONCEPT_STATUSES = config.dig("concept", "status").freeze
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright 2021 Ribose Inc.
4
+ #
5
+
6
+ module Glossarist
7
+ class LocalizedConcept < Concept
8
+ # ISO 639-2 code for terminology.
9
+ # @see https://www.loc.gov/standards/iso639-2/php/code_list.php code list
10
+ # @return [String]
11
+ attr_accessor :language_code
12
+
13
+ # Must be one of the following:
14
+ # +notValid+, +valid+, +superseded+, +retired+.
15
+ # @todo Proper type checking.
16
+ # @note Works with strings, but soon they may be replaced with symbols.
17
+ # @return [String]
18
+ attr_accessor :entry_status
19
+
20
+ # Must be one of the following:
21
+ # +preferred+, +admitted+, +deprecated+.
22
+ # @todo Proper type checking.
23
+ # @note Works with strings, but soon they may be replaced with symbols.
24
+ # @return [String]
25
+ attr_accessor :classification
26
+
27
+ attr_accessor :review_date
28
+ attr_accessor :review_decision_date
29
+ attr_accessor :review_decision_event
30
+
31
+ def initialize(*)
32
+ @examples = []
33
+
34
+ super
35
+ end
36
+
37
+ def to_h # rubocop:disable Metrics/MethodLength
38
+ super.merge({
39
+ "language_code" => language_code,
40
+ "entry_status" => entry_status,
41
+ "sources" => sources.empty? ? nil : sources&.map(&:to_h),
42
+ "classification" => classification,
43
+ "dates" => dates&.map(&:to_h),
44
+ "review_date" => review_date,
45
+ "review_decision_date" => review_decision_date,
46
+ "review_decision_event" => review_decision_event,
47
+ }.compact)
48
+ end
49
+
50
+ def self.from_h(hash)
51
+ terms = hash["terms"]&.map { |h| Designation::Base.from_h(h) } || []
52
+ sources = hash["authoritative_source"]&.each { |source| source.merge({ "type" => "authoritative"}) }
53
+
54
+ super(hash.merge({"terms" => terms, "sources" => sources}))
55
+ end
56
+
57
+ # @deprecated For legacy reasons only.
58
+ # Implicit conversion (i.e. {#to_hash} alias) will be removed soon.
59
+ alias :to_hash :to_h
60
+ end
61
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ManagedConcept < Model
5
+ include Glossarist::Utilities::Enum
6
+ include Glossarist::Utilities::CommonFunctions
7
+
8
+ # @return [String]
9
+ attr_accessor :id
10
+ alias :termid= :id=
11
+
12
+ # @return [Array<RelatedConcept>]
13
+ attr_reader :related
14
+
15
+ # @return [String]
16
+ register_enum :status, Glossarist::GlossaryDefinition::CONCEPT_STATUSES
17
+
18
+ # return [Array<ConceptDate>]
19
+ attr_reader :dates
20
+
21
+ # return [Array<LocalizedConcept>]
22
+ attr_reader :localized_concepts
23
+
24
+ # All localizations for this concept.
25
+ #
26
+ # Keys are language codes and values are instances of {LocalizedConcept}.
27
+ # @return [Hash<String, LocalizedConcept>]
28
+ attr_accessor :localizations
29
+
30
+ def initialize(attributes = {})
31
+ @localizations = {}
32
+ self.localized_concepts = attributes.values.grep(Hash)
33
+
34
+ attributes = symbolize_keys(attributes)
35
+ super(slice_keys(attributes, managed_concept_attributes))
36
+ end
37
+
38
+ def localized_concepts=(localized_concepts_hash)
39
+ @localized_concepts = localized_concepts_hash.map { |l| LocalizedConcept.new(l) }.compact
40
+
41
+ @localized_concepts.each do |l|
42
+ add_l10n(l)
43
+ end
44
+
45
+ @localized_concepts
46
+ end
47
+
48
+ def related=(related)
49
+ @related = related&.map { |r| RelatedConcept.new(r) }
50
+ end
51
+
52
+ def dates=(dates)
53
+ @dates = dates&.map { |d| ConceptDate.new(d) }
54
+ end
55
+
56
+ # Adds concept localization.
57
+ # @param localized_concept [LocalizedConcept]
58
+ def add_localization(localized_concept)
59
+ lang = localized_concept.language_code
60
+ localizations.store(lang, localized_concept)
61
+ end
62
+
63
+ alias :add_l10n :add_localization
64
+
65
+ # Returns concept localization.
66
+ # @param lang [String] language code
67
+ # @return [LocalizedConcept]
68
+ def localization(lang)
69
+ localizations[lang]
70
+ end
71
+
72
+ alias :l10n :localization
73
+
74
+ def to_h
75
+ {
76
+ "termid" => id,
77
+ "term" => default_designation,
78
+ "related" => related&.map(&:to_h),
79
+ "dates" => dates&.empty? ? nil : dates&.map(&:to_h),
80
+ }.merge(localizations.transform_values(&:to_h)).compact
81
+ end
82
+
83
+ def default_designation
84
+ localized = localization("eng") || localizations.values.first
85
+ localized&.terms&.first&.designation
86
+ end
87
+
88
+ def managed_concept_attributes
89
+ %i[
90
+ id
91
+ termid
92
+ related
93
+ status
94
+ dates
95
+ localized_concepts
96
+ ].compact
97
+ end
98
+
99
+ Glossarist::GlossaryDefinition::RELATED_CONCEPT_TYPES.each do |type|
100
+ # List of related concepts of the specified type.
101
+ # @return [Array<RelatedConcept>]
102
+ define_method("#{type}_concepts") do
103
+ related&.select { |concept| concept.type == type.to_s } || []
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ManagedConceptCollection
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @managed_concepts = {}
9
+ @concept_manager = ConceptManager.new
10
+ end
11
+
12
+ # @return [Array<ManagedConcept>]
13
+ def managed_concepts
14
+ @managed_concepts.values
15
+ end
16
+
17
+ def managed_concepts=(managed_concepts = [])
18
+ managed_concepts.each do |managed_concept|
19
+ store(ManagedConcept.new(managed_concept))
20
+ end
21
+
22
+ @managed_concepts.values
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ "managed_concepts" => managed_concepts.map(&:to_h),
28
+ }.compact
29
+ end
30
+
31
+ def each(&block)
32
+ @managed_concepts.each_value(&block)
33
+ end
34
+
35
+ # Returns concept with given ID, if it is present in collection, or +nil+
36
+ # otherwise.
37
+ #
38
+ # @param id [String]
39
+ # ManagedConcept ID
40
+ # @return [ManagedConcept, nil]
41
+ def fetch(id)
42
+ @managed_concepts[id]
43
+ end
44
+
45
+ alias :[] :fetch
46
+
47
+ # If ManagedConcept with given ID is present in this collection, then
48
+ # returns it. Otherwise, instantiates a new ManagedConcept, adds it to
49
+ # the collection, and returns it.
50
+ #
51
+ # @param id [String]
52
+ # ManagedConcept ID
53
+ # @return [ManagedConcept]
54
+ def fetch_or_initialize(id)
55
+ fetch(id) or store(ManagedConcept.new(id: id))
56
+ end
57
+
58
+ # Adds concept to the collection. If collection contains a concept with
59
+ # the same ID already, that concept is replaced.
60
+ #
61
+ # @param managed_concept [ManagedConcept]
62
+ # ManagedConcept about to be added
63
+ def store(managed_concept)
64
+ @managed_concepts[managed_concept.id] = managed_concept
65
+ end
66
+
67
+ alias :<< :store
68
+
69
+ def load_from_files(path)
70
+ @concept_manager.path = path
71
+ @concept_manager.load_from_files(collection: self)
72
+ end
73
+
74
+ def save_to_files(path)
75
+ @concept_manager.path = path
76
+ @concept_manager.save_to_files(@managed_concepts)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright 2021 Ribose Inc.
4
+ #
5
+
6
+ module Glossarist
7
+ class Model
8
+ def self.new(params = {})
9
+ return params if params.is_a?(self)
10
+
11
+ super
12
+ end
13
+
14
+ def initialize(attributes = {})
15
+ attributes.each_pair { |k, v| set_attribute(k, v) }
16
+ end
17
+
18
+ def set_attribute(name, value)
19
+ public_send("#{name}=", value)
20
+ rescue NoMethodError
21
+ raise ArgumentError, "#{self.class.name} does not have " +
22
+ "attribute #{name} defined or the attribute is read only."
23
+ end
24
+
25
+ def self.from_h(hash)
26
+ new(hash)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class NonVerbRep
5
+ attr_accessor :image
6
+ attr_accessor :table
7
+ attr_accessor :formula
8
+
9
+ # @return [Array<ConceptSource>]
10
+ attr_reader :sources
11
+
12
+ def sources=(sources)
13
+ @sources = sources&.map do |source|
14
+ ConceptSource.new(source)
15
+ end
16
+ end
17
+ end
18
+ end