glossarist-new 1.0.0

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