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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fca876eda8d9aaf1f9c931e93b47865f618f37991b317cf9a9b1aecaee4f60cf
4
+ data.tar.gz: 9af3d0caefb3ecf7c1cc8958b4440641d279126bcc4a2888feb91ee4bb2ccf42
5
+ SHA512:
6
+ metadata.gz: 35fd58588549adf1b1978b6688b21257f8a9f094246daa69365f6e031a0c9e6de9ddbd57835692f80b4fbbe0a4f8cd40af8f60e2dd237cb397d8fb515824bd21
7
+ data.tar.gz: 9f0843c03da02ffde2f6defad7f6d42732842ecb32cbbd560283d6e2ac5700073e7573ecf5d74e0764b781fedcb9fe48caae1d9a3de80c5e7d0a89976a603c8e
data/.editorconfig ADDED
@@ -0,0 +1,15 @@
1
+ # EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # Unix-style newlines with a newline ending every file
7
+ [*]
8
+ charset = utf-8
9
+ end_of_line = lf
10
+
11
+ [{*.adoc,*.html,*.js,*.json,*.rake,*.rb,*.rf,*.yaml,*.yml,Rakefile,rakefile}]
12
+ indent_style = space
13
+ indent_size = 2
14
+ insert_final_newline = true
15
+ trim_trailing_whitespace = true
@@ -0,0 +1,34 @@
1
+ name: Test
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+ - workflow_dispatch
7
+
8
+ jobs:
9
+ build:
10
+ name: Ruby ${{ matrix.ruby }}
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ ruby:
17
+ - "3.0"
18
+ - "2.7"
19
+ - "2.6"
20
+ - "2.5"
21
+ - "2.4"
22
+
23
+ steps:
24
+ - name: Fetch source code
25
+ uses: actions/checkout@v2
26
+
27
+ - name: Set up Ruby
28
+ uses: ruby/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ bundler-cache: true
32
+
33
+ - name: Run tests
34
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ /Gemfile.lock
11
+
12
+ # RSpec failure tracking
13
+ .rspec_status
14
+
15
+ # RuboCop caches
16
+ .rubocop-http---*
17
+ .rubocop-https---*
18
+
data/.hound.yml ADDED
@@ -0,0 +1,3 @@
1
+ ruby:
2
+ enabled: true
3
+ config_file: .rubocop.yml
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,35 @@
1
+ inherit_from:
2
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
3
+
4
+ # local repo-specific modifications
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.4
8
+
9
+ # This one breaks plenty of RSpec idioms.
10
+ Lint/AmbiguousBlockAssociation:
11
+ Exclude:
12
+ - "spec/**/*"
13
+
14
+ # Defining a constant or class inside RSpec.describe block
15
+ # sometimes makes sense.
16
+ Lint/ConstantDefinitionInBlock:
17
+ Exclude:
18
+ - "spec/**/*"
19
+
20
+ # This requirement is silly to me.
21
+ Style/AccessModifierDeclarations:
22
+ Enabled: false
23
+
24
+ # Makes little sense for Glossarist models, as these attr_accessors should all
25
+ # be documented eventually.
26
+ Style/AccessorGrouping:
27
+ Enabled: false
28
+
29
+ # This requirement is silly to me.
30
+ Style/ClassCheck:
31
+ Enabled: false
32
+
33
+ # This requirement is silly to me.
34
+ Style/SingleArgumentDig:
35
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2021, Ribose
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc ADDED
@@ -0,0 +1,11 @@
1
+ = Glossarist
2
+
3
+ == Credits
4
+
5
+ This gem is developed, maintained and funded by
6
+ https://www.ribose.com[Ribose Inc.]
7
+
8
+ == License
9
+
10
+ The gem is available as open source under the terms of the
11
+ https://opensource.org/licenses/BSD-2-Clause[2-Clause BSD License].
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/config.yml ADDED
@@ -0,0 +1,83 @@
1
+ # This file can be used to configure enum attributes in the model.
2
+ # For example to add different allowed values for Abbreviation::Type
3
+ # you can change the values under abbreviation => type
4
+ #
5
+ # NOTE: All the keys in this file are needed for the script to run
6
+ # and should not be removed. You can change the values but atleast
7
+ # one option should be present in each list.
8
+
9
+ concept_source:
10
+ status:
11
+ - identical
12
+ - similar
13
+ - modified
14
+ - restyle
15
+ - context_added
16
+ - generalisation
17
+ - specialisation
18
+ - unspecified
19
+ - related
20
+ - not_equal
21
+ type:
22
+ - authoritative
23
+ - lineage
24
+
25
+ designation:
26
+ base:
27
+ normative_status:
28
+ - preferred
29
+ - deprecated
30
+ - admitted
31
+ - <símbolo> # in iev-data => '<symbol>'
32
+ - 티에스 # in iev-data => translates to 'TS' I think it is a synonym and not status.
33
+ - prąd startowy # in iev-data => 'starting current' I think it is a synonym and not status.
34
+
35
+ related_concept:
36
+ type:
37
+ - deprecates
38
+ - supersedes
39
+ - superseded_by
40
+ - narrower
41
+ - broader
42
+ - equivalent
43
+ - compare
44
+ - contrast
45
+ - see
46
+
47
+ abbreviation:
48
+ type:
49
+ - truncation
50
+ - acronym
51
+ - initialism
52
+
53
+ grammar_info:
54
+ boolean_attribute:
55
+ - preposition
56
+ - participle
57
+ - adj
58
+ - verb
59
+ - adverb
60
+ - noun
61
+ gender:
62
+ - m
63
+ - f
64
+ - n
65
+ - c
66
+ number:
67
+ - singular
68
+ - dual
69
+ - plural
70
+
71
+ concept:
72
+ status:
73
+ - draft
74
+ - not_valid
75
+ - valid
76
+ - superseded
77
+ - retired
78
+
79
+ concept_date:
80
+ type:
81
+ - accepted
82
+ - amended
83
+ - retired
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/glossarist/version"
4
+
5
+ all_files_in_git = Dir.chdir(File.expand_path(__dir__)) do
6
+ `git ls-files -z`.split("\x0")
7
+ end
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = "glossarist-new"
11
+ spec.version = Glossarist::VERSION
12
+ spec.authors = ["Ribose"]
13
+ spec.email = ["open.source@ribose.com"]
14
+
15
+ spec.summary =
16
+ "Concept models for terminology glossaries conforming ISO 10241-1."
17
+ spec.homepage = "https://github.com/glossarist/glossarist-ruby"
18
+ spec.license = "BSD-2-Clause"
19
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = spec.homepage
23
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ spec.files = all_files_in_git
27
+ .reject { |f| f.match(%r{\A(?:test|spec|features|bin|\.)/}) }
28
+
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "pry", "~> 0.14.0"
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
+ spec.add_development_dependency "rspec", "~> 3.10"
36
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright 2021 Ribose Inc.
4
+ #
5
+
6
+ module Glossarist
7
+ class Citation < Model
8
+ # Unstructured (plain text) reference.
9
+ # @return [String]
10
+ attr_accessor :text
11
+
12
+ # Source in structured reference.
13
+ # @return [String]
14
+ attr_accessor :source
15
+
16
+ # Document ID in structured reference.
17
+ # @return [String]
18
+ attr_accessor :id
19
+
20
+ # Document version in structured reference.
21
+ # @return [String]
22
+ attr_accessor :version
23
+
24
+ # @return [String]
25
+ # Referred clause of the document.
26
+ attr_accessor :clause
27
+
28
+ # Link to document.
29
+ # @return [String]
30
+ attr_accessor :link
31
+
32
+ # Original ref text before parsing.
33
+ # @return [String]
34
+ # @note This attribute is likely to be removed or reworked in future.
35
+ # It is arguably not relevant to Glossarist itself.
36
+ attr_accessor :original
37
+
38
+ # Whether it is a plain text ref.
39
+ # @return [Boolean]
40
+ def plain?
41
+ (source && id && version).nil?
42
+ end
43
+
44
+ # Whether it is a structured ref.
45
+ # @return [Boolean]
46
+ def structured?
47
+ !plain?
48
+ end
49
+
50
+ def to_h
51
+ {
52
+ "ref" => ref_to_h,
53
+ "clause" => clause,
54
+ "link" => link,
55
+ "original" => original,
56
+ }.compact
57
+ end
58
+
59
+ def self.from_h(hash)
60
+ hash = hash.dup
61
+
62
+ ref_val = hash.delete("ref")
63
+ hash.merge!(Hash === ref_val ? ref_val : {"text" => ref_val})
64
+ hash.compact!
65
+
66
+ super(hash)
67
+ end
68
+
69
+ def ref=(ref)
70
+ @source = ref["source"]
71
+ @id = ref["id"]
72
+ @version = ref["version"]
73
+ end
74
+
75
+ private
76
+
77
+ def ref_to_h
78
+ if structured?
79
+ { "source" => source, "id" => id, "version" => version }.compact
80
+ else
81
+ text
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright 2021 Ribose Inc.
4
+ #
5
+
6
+ module Glossarist
7
+ # @todo Add support for lazy concept loading.
8
+ # @todo Consider extracting persistence backend to a separate class.
9
+ class Collection
10
+ include Enumerable
11
+
12
+ # Path to concepts directory.
13
+ # @return [String]
14
+ attr_accessor :path
15
+
16
+ # @param path [String]
17
+ # concepts directory path, either absolute or relative to CWD
18
+ def initialize(path: nil)
19
+ @path = path
20
+ @index = {}
21
+ end
22
+
23
+ def each(&block)
24
+ @index.each_value(&block)
25
+ end
26
+
27
+ # Returns concept with given ID, if it is present in collection, or +nil+
28
+ # otherwise.
29
+ #
30
+ # @param id [String]
31
+ # Concept ID
32
+ # @return [Concept, nil]
33
+ def fetch(id)
34
+ @index[id]
35
+ end
36
+
37
+ alias :[] :fetch
38
+
39
+ # If concept with given ID is present in this collection, returns that
40
+ # concept. Otherwise, instantiates a new concept, adds it to
41
+ # the collection, and returns it.
42
+ #
43
+ # @param id [String]
44
+ # Concept ID
45
+ # @return [Concept]
46
+ def fetch_or_initialize(id)
47
+ fetch(id) or store(Concept.new(id: id))
48
+ end
49
+
50
+ # Adds concept to the collection. If collection contains a concept with
51
+ # the same ID already, that concept is replaced.
52
+ #
53
+ # @param concept [Concept]
54
+ # concept about to be added
55
+ def store(concept)
56
+ @index[concept.id] = concept
57
+ end
58
+
59
+ alias :<< :store
60
+
61
+ # Reads all concepts from files.
62
+ def load_concepts
63
+ Dir.glob(concepts_glob) do |filename|
64
+ store(load_concept_from_file(filename))
65
+ end
66
+ end
67
+
68
+ private def load_concept_from_file(filename)
69
+ Concept.from_h(Psych.safe_load(File.read(filename)))
70
+ end
71
+
72
+ # Writes all concepts to files.
73
+ def save_concepts
74
+ @index.each_value &method(:save_concept_to_file)
75
+ end
76
+
77
+ private def save_concept_to_file(concept)
78
+ filename = File.join(path, "concept-#{concept.id}.yaml")
79
+ File.write(filename, Psych.dump(concept.to_h))
80
+ end
81
+
82
+ private def concepts_glob
83
+ File.join(path, "concept-*.{yaml,yml}")
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) Copyright 2021 Ribose Inc.
4
+ #
5
+
6
+ module Glossarist
7
+ class Concept < Model
8
+ # Concept ID.
9
+ # @return [String]
10
+ attr_accessor :id
11
+ alias :termid= :id=
12
+
13
+ # Concept designations.
14
+ # @todo Alias +terms+ exists only for legacy reasons and will be removed.
15
+ # @return [Array<Designations::Base>]
16
+ attr_reader :designations
17
+ alias :terms :designations
18
+
19
+ # <<BasicDocument>>LocalizedString
20
+ # @return [String]
21
+ attr_accessor :domain
22
+
23
+ # <<BasicDocument>>LocalizedString
24
+ # @return [String]
25
+ attr_accessor :subject
26
+
27
+ # Concept definition.
28
+ # @return [Array<DetailedDefinition>]
29
+ attr_reader :definition
30
+
31
+ # Non verbal representation of the concept.
32
+ # @return [NonVerbRep]
33
+ attr_accessor :non_verb_rep
34
+
35
+ # Concept notes
36
+ # @return [Array<DetailedDefinition>]
37
+ attr_reader :notes
38
+
39
+ # Concept examples
40
+ # @return [Array<DetailedDefinition>]
41
+ attr_reader :examples
42
+
43
+ def initialize(*)
44
+ @localizations = {}
45
+ @sources = []
46
+ @related = []
47
+ @notes = []
48
+ @designations = []
49
+
50
+ super
51
+ end
52
+
53
+ # List of authorative sources.
54
+ # @todo Alias +authoritative_source+ exists for legacy reasons and may be
55
+ # removed.
56
+ # @return [Array<ConceptSource>]
57
+ attr_reader :sources
58
+ alias :authoritative_source :sources
59
+
60
+ # return [Array<ConceptDate>]
61
+ attr_reader :dates
62
+
63
+ def examples=(examples)
64
+ @examples = examples&.map { |e| DetailedDefinition.new(e) }
65
+ end
66
+
67
+ def notes=(notes)
68
+ @notes = notes&.map { |n| DetailedDefinition.new(n) }
69
+ end
70
+
71
+ def definition=(definition)
72
+ @definition = definition&.map { |d| DetailedDefinition.new(d) }
73
+ end
74
+
75
+ def designations=(designations)
76
+ @designations = designations&.map do |designation|
77
+ Designation::Base.from_h(designation)
78
+ end
79
+ end
80
+
81
+ alias :terms= :designations=
82
+
83
+ def dates=(dates)
84
+ @dates = dates&.map { |d| ConceptDate.new(d) }
85
+ end
86
+
87
+ def sources=(sources)
88
+ @sources = sources&.map do |source|
89
+ ConceptSource.new(source)
90
+ end || []
91
+ end
92
+
93
+ def authoritative_source=(sources)
94
+ self.sources = sources&.map do |source|
95
+ source.merge({ "type" => "authoritative" })
96
+ end
97
+ end
98
+
99
+ def to_h
100
+ {
101
+ "id" => id,
102
+ "related" => related&.map(&:to_h),
103
+ "terms" => (terms&.map(&:to_h) || []),
104
+ "definition" => definition&.map(&:to_h),
105
+ "notes" => notes&.map(&:to_h),
106
+ "examples" => examples&.map(&:to_h),
107
+ }
108
+ .compact
109
+ end
110
+
111
+ # @deprecated For legacy reasons only.
112
+ # Implicit conversion (i.e. {#to_hash} alias) will be removed soon.
113
+ alias :to_hash :to_h
114
+
115
+ # rubocop:disable Metrics/AbcSize, Style/RescueModifier
116
+ def self.from_h(hash)
117
+ new.tap do |concept|
118
+ concept.id = hash.dig("termid")
119
+ concept.sources = hash.dig("sources")
120
+ concept.related = hash.dig("related")
121
+ concept.definition = hash.dig("definition")
122
+
123
+ hash.values
124
+ .grep(Hash)
125
+ .map { |subhash| LocalizedConcept.from_h(subhash) rescue nil }
126
+ .compact
127
+
128
+ concept.related = hash.dig("related") || []
129
+ end
130
+ end
131
+ # rubocop:enable Metrics/AbcSize, Style/RescueModifier
132
+
133
+ # All Related Concepts
134
+ # @return [Array<RelatedConcept>]
135
+ def related
136
+ @related.empty? ? nil : @related
137
+ end
138
+
139
+ def related=(related)
140
+ @related = related&.map { |r| RelatedConcept.new(r) } || []
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptDate < Model
5
+ include Glossarist::Utilities::Enum
6
+
7
+ # Iso8601 date
8
+ # @return [String]
9
+ attr_accessor :date
10
+
11
+ register_enum :type, Glossarist::GlossaryDefinition::CONCEPT_DATE_TYPES
12
+
13
+ def to_h
14
+ {
15
+ "type" => type,
16
+ "date" => date,
17
+ }.compact
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptManager
5
+ # Path to concepts directory.
6
+ # @return [String]
7
+ attr_accessor :path
8
+
9
+ # @param path [String]
10
+ # concepts directory path, either absolute or relative to CWD
11
+ def initialize(path: nil)
12
+ @path = path
13
+ end
14
+
15
+ # Reads all concepts from files.
16
+ def load_from_files(collection: nil)
17
+ collection ||= ManagedConceptCollection.new
18
+
19
+ Dir.glob(concepts_glob) do |filename|
20
+ collection.store(load_concept_from_file(filename))
21
+ end
22
+ end
23
+
24
+ # Writes all concepts to files.
25
+ def save_to_files(managed_concepts)
26
+ managed_concepts.each_value &method(:save_concept_to_file)
27
+ end
28
+
29
+ private
30
+
31
+ def load_concept_from_file(filename)
32
+ ManagedConcept.new(Psych.safe_load(File.read(filename)))
33
+ end
34
+
35
+ def save_concept_to_file(concept)
36
+ filename = File.join(path, "concept-#{concept.id}.yaml")
37
+ File.write(filename, Psych.dump(concept.to_h))
38
+ end
39
+
40
+ def concepts_glob
41
+ File.join(path, "concept-*.{yaml,yml}")
42
+ end
43
+ end
44
+ end