glossarist 2.5.0 → 2.5.2
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/.gitignore +1 -0
- data/.rubocop_todo.yml +50 -146
- data/CLAUDE.md +33 -7
- data/Gemfile +20 -19
- data/README.adoc +383 -7
- data/TODO.integration/01-gcr-package-cli.md +180 -0
- data/exe/glossarist +1 -53
- data/glossarist.gemspec +1 -0
- data/lib/glossarist/asset.rb +1 -1
- data/lib/glossarist/citation.rb +1 -1
- data/lib/glossarist/cli/package_command.rb +32 -0
- data/lib/glossarist/cli/upgrade_command.rb +34 -0
- data/lib/glossarist/cli/validate_command.rb +56 -0
- data/lib/glossarist/cli.rb +105 -0
- data/lib/glossarist/collection_config.rb +23 -0
- data/lib/glossarist/collections.rb +15 -8
- data/lib/glossarist/concept.rb +1 -1
- data/lib/glossarist/concept_collector.rb +153 -0
- data/lib/glossarist/concept_data.rb +3 -1
- data/lib/glossarist/concept_date.rb +1 -1
- data/lib/glossarist/concept_document.rb +29 -0
- data/lib/glossarist/concept_enricher.rb +34 -0
- data/lib/glossarist/concept_manager.rb +31 -49
- data/lib/glossarist/concept_reference.rb +45 -0
- data/lib/glossarist/concept_source.rb +1 -1
- data/lib/glossarist/concept_validator.rb +101 -0
- data/lib/glossarist/custom_locality.rb +1 -1
- data/lib/glossarist/dataset_validator.rb +69 -0
- data/lib/glossarist/designation/abbreviation.rb +1 -1
- data/lib/glossarist/designation/base.rb +11 -4
- data/lib/glossarist/designation/expression.rb +1 -1
- data/lib/glossarist/designation/grammar_info.rb +1 -1
- data/lib/glossarist/designation/graphical_symbol.rb +1 -1
- data/lib/glossarist/designation/letter_symbol.rb +1 -1
- data/lib/glossarist/designation/symbol.rb +2 -2
- data/lib/glossarist/detailed_definition.rb +1 -1
- data/lib/glossarist/gcr_metadata.rb +87 -0
- data/lib/glossarist/gcr_package.rb +223 -0
- data/lib/glossarist/gcr_statistics.rb +35 -0
- data/lib/glossarist/gcr_validator.rb +98 -0
- data/lib/glossarist/locality.rb +1 -1
- data/lib/glossarist/localized_concept.rb +12 -1
- data/lib/glossarist/managed_concept.rb +1 -1
- data/lib/glossarist/managed_concept_data.rb +5 -2
- data/lib/glossarist/non_verb_rep.rb +1 -1
- data/lib/glossarist/reference_extractor.rb +227 -0
- data/lib/glossarist/reference_resolver.rb +169 -0
- data/lib/glossarist/register_data.rb +39 -0
- data/lib/glossarist/related_concept.rb +1 -1
- data/lib/glossarist/resolution_adapter/local.rb +73 -0
- data/lib/glossarist/resolution_adapter/package.rb +22 -0
- data/lib/glossarist/resolution_adapter/remote.rb +60 -0
- data/lib/glossarist/resolution_adapter/route.rb +34 -0
- data/lib/glossarist/resolution_adapter.rb +14 -0
- data/lib/glossarist/schema_migration.rb +334 -0
- data/lib/glossarist/urn_resolver.rb +71 -0
- data/lib/glossarist/v1/concept.rb +81 -0
- data/lib/glossarist/v1/cross_references.rb +41 -0
- data/lib/glossarist/v1/register.rb +50 -0
- data/lib/glossarist/v1.rb +9 -0
- data/lib/glossarist/validation_result.rb +38 -0
- data/lib/glossarist/version.rb +1 -1
- data/lib/glossarist.rb +29 -4
- data/relaton-bib-2.0.0.gem +0 -0
- data/relaton-bib-2.1.0.gem +0 -0
- data/relaton-cen-2.0.0.gem +0 -0
- data/relaton-iec-2.0.0.gem +0 -0
- data/relaton-iso-2.0.0.gem +0 -0
- data/relaton-itu-2.0.0.gem +0 -0
- metadata +60 -7
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class CLI
|
|
5
|
+
class UpgradeCommand
|
|
6
|
+
def initialize(source_dir, options)
|
|
7
|
+
@source_dir = source_dir
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
result = SchemaMigration.upgrade_directory(
|
|
13
|
+
@source_dir,
|
|
14
|
+
output: @options[:output],
|
|
15
|
+
target_version: @options[:target_version],
|
|
16
|
+
cross_references: @options[:cross_references],
|
|
17
|
+
dry_run: @options[:dry_run],
|
|
18
|
+
)
|
|
19
|
+
report(result)
|
|
20
|
+
rescue ArgumentError => e
|
|
21
|
+
warn "Error: #{e.message}"
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def report(result)
|
|
28
|
+
puts "Upgraded #{result[:count]} concepts " \
|
|
29
|
+
"from schema v#{result[:source_version]} to v#{result[:target_version]}"
|
|
30
|
+
puts "Output: #{result[:output]}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class CLI
|
|
5
|
+
class ValidateCommand
|
|
6
|
+
def initialize(path, options)
|
|
7
|
+
@path = path
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
result = DatasetValidator.new.validate(
|
|
13
|
+
@path,
|
|
14
|
+
strict: @options[:strict],
|
|
15
|
+
reference_path: @options[:reference_path],
|
|
16
|
+
)
|
|
17
|
+
report(result)
|
|
18
|
+
exit_code = result.errors.any? || (@options[:strict] && result.warnings.any?) ? 1 : 0
|
|
19
|
+
exit(exit_code) unless exit_code.zero?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def report(result)
|
|
25
|
+
case @options[:format]
|
|
26
|
+
when "json"
|
|
27
|
+
require "json"
|
|
28
|
+
puts JSON.pretty_generate(result.to_h)
|
|
29
|
+
when "yaml"
|
|
30
|
+
require "yaml"
|
|
31
|
+
puts YAML.dump(result.to_h)
|
|
32
|
+
else
|
|
33
|
+
report_text(result)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def report_text(result)
|
|
38
|
+
if result.valid?
|
|
39
|
+
puts "Valid."
|
|
40
|
+
else
|
|
41
|
+
puts "Invalid."
|
|
42
|
+
result.errors.each { |e| puts " ERROR: #{e}" }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if result.warnings.any?
|
|
46
|
+
result.warnings.each do |w|
|
|
47
|
+
puts " WARNING: #{w}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
total = result.errors.length + result.warnings.length
|
|
52
|
+
puts "#{total} issue(s) found."
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Glossarist
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
desc "generate_latex", "Convert Concepts to Latex format"
|
|
8
|
+
|
|
9
|
+
option :concepts_path, aliases: :p, required: true,
|
|
10
|
+
desc: "Path to yaml concepts directory"
|
|
11
|
+
option :latex_concepts, aliases: :l,
|
|
12
|
+
desc: "File path having list of concepts that should be converted to LATEX format. If not provided all the concepts will be converted to the latex format"
|
|
13
|
+
option :output_file, aliases: :o,
|
|
14
|
+
desc: "Output file path. By default the output will pe printed to the console"
|
|
15
|
+
option :extra_attributes, aliases: :e, type: :array,
|
|
16
|
+
desc: "List of extra attributes that are not in standard Glossarist Concept model"
|
|
17
|
+
def generate_latex
|
|
18
|
+
assets = []
|
|
19
|
+
|
|
20
|
+
if options[:extra_attributes]
|
|
21
|
+
Glossarist.configure do |config|
|
|
22
|
+
config.register_extension_attributes(options[:extra_attributes])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
concept_set = Glossarist::ConceptSet.new(options[:concepts_path], assets)
|
|
27
|
+
latex_str = concept_set.to_latex(options[:latex_concepts])
|
|
28
|
+
output_latex(latex_str)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc "upgrade SOURCE_DIR", "Upgrade dataset to current schema version"
|
|
32
|
+
option :output, aliases: :o, required: true,
|
|
33
|
+
desc: "Output directory or .gcr file path"
|
|
34
|
+
option :target_version, type: :string, default: Glossarist::SchemaMigration::CURRENT_SCHEMA_VERSION,
|
|
35
|
+
desc: "Target schema version (default: current)"
|
|
36
|
+
option :cross_references, type: :string,
|
|
37
|
+
desc: "Path to datasets.yml for cross-reference maps"
|
|
38
|
+
option :dry_run, type: :boolean, default: false,
|
|
39
|
+
desc: "Show what would change without writing"
|
|
40
|
+
def upgrade(source_dir)
|
|
41
|
+
require_relative "cli/upgrade_command"
|
|
42
|
+
Glossarist::CLI::UpgradeCommand.new(source_dir, options).run
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "package DIR", "Create a .gcr ZIP archive from a schema v1 dataset"
|
|
46
|
+
option :output, aliases: :o, required: true,
|
|
47
|
+
desc: "Output .gcr file path"
|
|
48
|
+
option :shortname, type: :string, required: true,
|
|
49
|
+
desc: "Machine-readable dataset ID"
|
|
50
|
+
option :version, type: :string, required: true,
|
|
51
|
+
desc: "Semantic version (e.g. 1.0.0)"
|
|
52
|
+
option :uri_prefix, type: :string,
|
|
53
|
+
desc: "URI namespace prefix (e.g. urn:iec:std:iec:60050)"
|
|
54
|
+
option :title, type: :string, desc: "Dataset title"
|
|
55
|
+
option :description, type: :string, desc: "Dataset description"
|
|
56
|
+
option :owner, type: :string, desc: "Dataset owner"
|
|
57
|
+
option :register_yaml, type: :string,
|
|
58
|
+
desc: "Path to register.yaml to include in package"
|
|
59
|
+
option :tags, type: :array, desc: "Tags for the dataset"
|
|
60
|
+
def package(dir)
|
|
61
|
+
require_relative "cli/package_command"
|
|
62
|
+
Glossarist::CLI::PackageCommand.new(dir, options).run
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
desc "validate PATH",
|
|
66
|
+
"Validate dataset directory or .gcr for schema compliance"
|
|
67
|
+
option :strict, type: :boolean, default: false,
|
|
68
|
+
desc: "Treat warnings as errors"
|
|
69
|
+
option :format, type: :string, default: "text",
|
|
70
|
+
enum: %w[text json yaml],
|
|
71
|
+
desc: "Output format for validation results"
|
|
72
|
+
option :reference_path, type: :string,
|
|
73
|
+
desc: "Path to directory of .gcr files for cross-dataset reference validation"
|
|
74
|
+
def validate(path)
|
|
75
|
+
require_relative "cli/validate_command"
|
|
76
|
+
Glossarist::CLI::ValidateCommand.new(path, options).run
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def method_missing(*args)
|
|
80
|
+
warn "No method found named: #{args[0]}"
|
|
81
|
+
warn "Run with `--help` or `-h` to see available options"
|
|
82
|
+
exit 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def respond_to_missing?
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.exit_on_failure?
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def output_latex(latex_str)
|
|
96
|
+
output_file_path = options[:output_file]
|
|
97
|
+
|
|
98
|
+
if output_file_path
|
|
99
|
+
File.open(output_file_path, "w") { |file| file.puts latex_str }
|
|
100
|
+
else
|
|
101
|
+
puts latex_str
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class CollectionConfig < Lutaml::Model::Serializable
|
|
5
|
+
attribute :packages, :hash, collection: true, default: -> { [] }
|
|
6
|
+
attribute :routes, :hash, collection: true, default: -> { [] }
|
|
7
|
+
attribute :remotes, :hash, collection: true, default: -> { [] }
|
|
8
|
+
|
|
9
|
+
yaml do
|
|
10
|
+
map :packages, to: :packages
|
|
11
|
+
map :routes, to: :routes
|
|
12
|
+
map :remote, to: :remotes
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.from_file(path)
|
|
16
|
+
return nil unless File.exist?(path)
|
|
17
|
+
|
|
18
|
+
from_yaml(File.read(path))
|
|
19
|
+
rescue Psych::SyntaxError, Lutaml::Model::InvalidFormatError
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module Glossarist
|
|
4
4
|
module Collections
|
|
5
|
-
autoload :AssetCollection,
|
|
6
|
-
|
|
7
|
-
autoload :
|
|
8
|
-
|
|
9
|
-
autoload :
|
|
10
|
-
autoload :
|
|
11
|
-
|
|
12
|
-
autoload :
|
|
5
|
+
autoload :AssetCollection,
|
|
6
|
+
"glossarist/collections/asset_collection"
|
|
7
|
+
autoload :BibliographyCollection,
|
|
8
|
+
"glossarist/collections/bibliography_collection"
|
|
9
|
+
autoload :Collection, "glossarist/collections/collection"
|
|
10
|
+
autoload :ConceptSourceCollection,
|
|
11
|
+
"glossarist/collections/concept_source_collection"
|
|
12
|
+
autoload :DesignationCollection,
|
|
13
|
+
"glossarist/collections/designation_collection"
|
|
14
|
+
autoload :DetailedDefinitionCollection,
|
|
15
|
+
"glossarist/collections/detailed_definition_collection"
|
|
16
|
+
autoload :LocalizationCollection,
|
|
17
|
+
"glossarist/collections/localization_collection"
|
|
18
|
+
autoload :TypedCollection,
|
|
19
|
+
"glossarist/collections/typed_collection"
|
|
13
20
|
end
|
|
14
21
|
end
|
data/lib/glossarist/concept.rb
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class ConceptCollector
|
|
5
|
+
def self.collect(dir)
|
|
6
|
+
dir = File.expand_path(dir)
|
|
7
|
+
unless File.directory?(dir)
|
|
8
|
+
raise ArgumentError, "#{dir} is not a directory"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if v2_concepts?(dir)
|
|
12
|
+
collect_v2_concepts(dir)
|
|
13
|
+
elsif managed_concepts?(dir)
|
|
14
|
+
collect_managed_concepts(dir)
|
|
15
|
+
elsif v1_concepts?(dir)
|
|
16
|
+
collect_v1_concepts(dir)
|
|
17
|
+
else
|
|
18
|
+
[]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.each_concept(dir, &block)
|
|
23
|
+
dir = File.expand_path(dir)
|
|
24
|
+
unless File.directory?(dir)
|
|
25
|
+
raise ArgumentError, "#{dir} is not a directory"
|
|
26
|
+
end
|
|
27
|
+
return enum_for(:each_concept, dir) unless block
|
|
28
|
+
|
|
29
|
+
if v2_concepts?(dir)
|
|
30
|
+
each_v2_concept(dir, &block)
|
|
31
|
+
elsif managed_concepts?(dir)
|
|
32
|
+
each_managed_concept(dir, &block)
|
|
33
|
+
elsif v1_concepts?(dir)
|
|
34
|
+
each_v1_concept(dir, &block)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def v1_concepts?(dir)
|
|
42
|
+
concepts_dir = File.join(dir, "concepts")
|
|
43
|
+
File.directory?(concepts_dir) &&
|
|
44
|
+
Dir.glob(File.join(concepts_dir, "*.yaml")).any? do |f|
|
|
45
|
+
V1::Concept.from_file(f)&.termid?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def v2_concepts?(dir)
|
|
50
|
+
File.directory?(File.join(dir, "geolexica-v2"))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def managed_concepts?(dir)
|
|
54
|
+
concept_dir = File.join(dir, "concepts", "concept")
|
|
55
|
+
File.directory?(concept_dir) &&
|
|
56
|
+
Dir.glob(File.join(concept_dir, "*.yaml")).any?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def collect_v1_concepts(dir)
|
|
60
|
+
concepts = []
|
|
61
|
+
each_v1_concept(dir) { |mc| concepts << mc }
|
|
62
|
+
concepts
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def each_v1_concept(dir)
|
|
66
|
+
concepts_dir = File.join(dir, "concepts")
|
|
67
|
+
files = Dir.glob(File.join(concepts_dir, "*.yaml"))
|
|
68
|
+
files.each do |file|
|
|
69
|
+
v1 = V1::Concept.from_file(file)
|
|
70
|
+
next unless v1
|
|
71
|
+
|
|
72
|
+
yield v1.to_managed_concept
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def collect_v2_concepts(dir)
|
|
77
|
+
v2_dir = File.join(dir, "geolexica-v2")
|
|
78
|
+
if File.directory?(File.join(v2_dir, "concepts"))
|
|
79
|
+
collect_managed_concepts(v2_dir)
|
|
80
|
+
else
|
|
81
|
+
collect_grouped_v2_concepts(v2_dir)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def each_v2_concept(dir, &block)
|
|
86
|
+
v2_dir = File.join(dir, "geolexica-v2")
|
|
87
|
+
if File.directory?(File.join(v2_dir, "concepts"))
|
|
88
|
+
each_managed_concept(v2_dir, &block)
|
|
89
|
+
else
|
|
90
|
+
each_grouped_v2_concept(v2_dir, &block)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def each_grouped_v2_concept(v2_dir, &block)
|
|
95
|
+
collection = ManagedConceptCollection.new
|
|
96
|
+
manager = ConceptManager.new(path: v2_dir)
|
|
97
|
+
manager.load_from_files(collection: collection)
|
|
98
|
+
collection.each(&block)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def collect_grouped_v2_concepts(v2_dir)
|
|
102
|
+
collection = ManagedConceptCollection.new
|
|
103
|
+
manager = ConceptManager.new(path: v2_dir)
|
|
104
|
+
manager.load_from_files(collection: collection)
|
|
105
|
+
collection.to_a
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def collect_managed_concepts(dir) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
109
|
+
concepts = []
|
|
110
|
+
each_managed_concept(dir) { |mc| concepts << mc }
|
|
111
|
+
concepts
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def each_managed_concept(dir) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
115
|
+
concepts_dir = File.join(dir, "concepts")
|
|
116
|
+
concept_files = Dir.glob(File.join(concepts_dir, "concept", "*.yaml"))
|
|
117
|
+
return if concept_files.empty?
|
|
118
|
+
|
|
119
|
+
lc_dir = find_localized_concepts_dir(concepts_dir)
|
|
120
|
+
lc_index = build_lc_index(lc_dir) if lc_dir
|
|
121
|
+
|
|
122
|
+
concept_files.each do |f|
|
|
123
|
+
mc = ManagedConcept.from_yaml(File.read(f))
|
|
124
|
+
next unless mc.data&.id
|
|
125
|
+
|
|
126
|
+
lc_map = mc.data.localized_concepts || {}
|
|
127
|
+
lc_map.each_value do |uuid|
|
|
128
|
+
lc_file = lc_index ? lc_index[uuid] : nil
|
|
129
|
+
next unless lc_file
|
|
130
|
+
|
|
131
|
+
l10n = LocalizedConcept.from_yaml(File.read(lc_file))
|
|
132
|
+
mc.add_localization(l10n)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
yield mc
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_lc_index(lc_dir)
|
|
140
|
+
Dir.glob(File.join(lc_dir, "*.{yaml,yml}"))
|
|
141
|
+
.to_h { |f| [File.basename(f, ".*"), f] }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def find_localized_concepts_dir(concepts_dir)
|
|
145
|
+
%w[localized_concept localized-concept].each do |name|
|
|
146
|
+
d = File.join(concepts_dir, name)
|
|
147
|
+
return d if File.directory?(d)
|
|
148
|
+
end
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -20,6 +20,7 @@ module Glossarist
|
|
|
20
20
|
initialize_empty: true
|
|
21
21
|
attribute :terms, Designation::Base, collection: true
|
|
22
22
|
attribute :related, RelatedConcept, collection: true
|
|
23
|
+
attribute :references, ConceptReference, collection: true
|
|
23
24
|
attribute :domain, :string
|
|
24
25
|
attribute :review_date, :date_time
|
|
25
26
|
attribute :review_decision_date, :date_time
|
|
@@ -31,7 +32,7 @@ module Glossarist
|
|
|
31
32
|
attribute :language_code, :string, pattern: /^.{3}$/
|
|
32
33
|
attribute :entry_status, :string
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
key_value do
|
|
35
36
|
map :dates, to: :dates
|
|
36
37
|
map :definition, to: :definition, value_map: { to: { empty: :empty } }
|
|
37
38
|
map :examples, to: :examples, value_map: { to: { empty: :empty } }
|
|
@@ -44,6 +45,7 @@ module Glossarist
|
|
|
44
45
|
map :terms, to: :terms,
|
|
45
46
|
with: { from: :terms_from_yaml, to: :terms_to_yaml }
|
|
46
47
|
map :related, to: :related
|
|
48
|
+
map :references, to: :references
|
|
47
49
|
map :domain, to: :domain
|
|
48
50
|
map %i[language_code languageCode], to: :language_code
|
|
49
51
|
map %i[entry_status entryStatus], to: :entry_status
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class ConceptDocument < Lutaml::Model::Serializable
|
|
5
|
+
attribute :concept, ManagedConcept
|
|
6
|
+
attribute :localizations, LocalizedConcept, collection: true
|
|
7
|
+
|
|
8
|
+
yamls do
|
|
9
|
+
sequence do
|
|
10
|
+
map_document 0, to: :concept, type: ManagedConcept
|
|
11
|
+
map_document 1.., to: :localizations, type: LocalizedConcept,
|
|
12
|
+
collection: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.from_managed_concept(managed_concept)
|
|
17
|
+
new(
|
|
18
|
+
concept: managed_concept,
|
|
19
|
+
localizations: managed_concept.localizations&.values || [],
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_managed_concept
|
|
24
|
+
mc = concept
|
|
25
|
+
localizations.each { |l10n| mc.add_localization(l10n) }
|
|
26
|
+
mc
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class ConceptEnricher
|
|
5
|
+
def inject_references(concepts) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
6
|
+
extractor = ReferenceExtractor.new
|
|
7
|
+
|
|
8
|
+
concepts.each do |mc|
|
|
9
|
+
mc.localizations.each do |l10n|
|
|
10
|
+
refs = extractor.extract_from_localized_concept(l10n)
|
|
11
|
+
next if refs.empty?
|
|
12
|
+
|
|
13
|
+
existing = l10n.data.references || []
|
|
14
|
+
seen_keys = existing.to_set { |r| [r.source, r.concept_id] }
|
|
15
|
+
|
|
16
|
+
refs.each do |ref|
|
|
17
|
+
key = [ref.source, ref.concept_id]
|
|
18
|
+
next if seen_keys.include?(key)
|
|
19
|
+
|
|
20
|
+
seen_keys.add(key)
|
|
21
|
+
existing << ref
|
|
22
|
+
end
|
|
23
|
+
l10n.data.references = existing
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def apply_uri_template(concepts, template)
|
|
29
|
+
concepts.each do |mc|
|
|
30
|
+
mc.data.uri = template.sub("{id}", mc.data.id.to_s)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -3,7 +3,7 @@ module Glossarist
|
|
|
3
3
|
attribute :path, :string
|
|
4
4
|
attribute :localized_concepts_path, :string
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
key_value do
|
|
7
7
|
map :path, to: :path
|
|
8
8
|
map %i[localized_concepts_path localizedConceptsPath],
|
|
9
9
|
to: :localized_concepts_path
|
|
@@ -33,62 +33,43 @@ module Glossarist
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
def load_concept_from_file(filename) # rubocop:disable Metrics/CyclomaticComplexity
|
|
37
|
+
raw = File.read(filename, encoding: "utf-8")
|
|
38
|
+
doc = ConceptDocument.from_yamls(raw)
|
|
39
|
+
concept = doc.concept
|
|
40
|
+
unless concept
|
|
41
|
+
raise Glossarist::ParseError.new(filename: filename)
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
[concept_hashes, localized_concept_hashes]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def load_concept_from_file(filename)
|
|
51
|
-
mixed_hashes = YAML.load_stream(File.read(filename, encoding: "utf-8"))
|
|
52
|
-
concepts = []
|
|
53
|
-
|
|
54
|
-
concept_hashes, localized_concept_hashes =
|
|
55
|
-
group_concept_hashes(mixed_hashes)
|
|
56
|
-
|
|
57
|
-
concept_hashes.each do |concept_hash|
|
|
58
|
-
concept_hash["uuid"] = concept_hash["id"] ||
|
|
59
|
-
File.basename(filename, ".*")
|
|
60
|
-
concept = Config.class_for(:managed_concept).of_yaml(concept_hash)
|
|
44
|
+
concept_uuid = concept.identifier || concept.data&.id || File.basename(
|
|
45
|
+
filename, ".*"
|
|
46
|
+
)
|
|
47
|
+
concept.instance_variable_set(:@uuid, concept_uuid)
|
|
61
48
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
concept.add_l10n(localized_concept)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
concepts << concept
|
|
49
|
+
concept.data.localized_concepts.each_value do |id|
|
|
50
|
+
localized_concept = load_localized_concept(id, doc.localizations)
|
|
51
|
+
concept.add_l10n(localized_concept)
|
|
69
52
|
end
|
|
70
53
|
|
|
71
|
-
|
|
54
|
+
[concept]
|
|
72
55
|
rescue Psych::SyntaxError => e
|
|
73
56
|
raise Glossarist::ParseError.new(filename: filename, line: e.line)
|
|
74
57
|
end
|
|
75
58
|
|
|
76
|
-
def load_localized_concept(id,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
concept_hash["uuid"] = id
|
|
91
|
-
Config.class_for(:localized_concept).of_yaml(concept_hash)
|
|
59
|
+
def load_localized_concept(id, inline_localizations = nil)
|
|
60
|
+
if inline_localizations
|
|
61
|
+
l10n = inline_localizations.find { |l| l.id == id }
|
|
62
|
+
if l10n
|
|
63
|
+
l10n.instance_variable_set(:@uuid, id)
|
|
64
|
+
return l10n
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
l10n = LocalizedConcept.from_yaml(
|
|
69
|
+
File.read(localized_concept_path(id), encoding: "utf-8"),
|
|
70
|
+
)
|
|
71
|
+
l10n.instance_variable_set(:@uuid, id)
|
|
72
|
+
l10n
|
|
92
73
|
rescue Psych::SyntaxError => e
|
|
93
74
|
raise Glossarist::ParseError.new(filename: filename, line: e.line)
|
|
94
75
|
end
|
|
@@ -107,7 +88,8 @@ module Glossarist
|
|
|
107
88
|
|
|
108
89
|
concept.localized_concepts.each do |lang, uuid|
|
|
109
90
|
filename = File.join(localized_concept_dir, "#{uuid}.yaml")
|
|
110
|
-
File.write(filename, concept.localization(lang).to_yaml,
|
|
91
|
+
File.write(filename, concept.localization(lang).to_yaml,
|
|
92
|
+
encoding: "utf-8")
|
|
111
93
|
end
|
|
112
94
|
end
|
|
113
95
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
class ConceptReference < Lutaml::Model::Serializable
|
|
5
|
+
attribute :term, :string
|
|
6
|
+
attribute :concept_id, :string
|
|
7
|
+
attribute :source, :string
|
|
8
|
+
attribute :ref_type, :string
|
|
9
|
+
|
|
10
|
+
key_value do
|
|
11
|
+
map :term, to: :term
|
|
12
|
+
map :concept_id, to: :concept_id
|
|
13
|
+
map :source, to: :source
|
|
14
|
+
map :ref_type, to: :ref_type
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def local?
|
|
18
|
+
%w[local designation].include?(ref_type) ||
|
|
19
|
+
(ref_type.nil? && (source.nil? || source.empty?))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def external?
|
|
23
|
+
!local?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_urn
|
|
27
|
+
return nil unless external?
|
|
28
|
+
return nil unless source && concept_id
|
|
29
|
+
|
|
30
|
+
case source
|
|
31
|
+
when /\Aurn:iec/ then "#{source}-#{concept_id}"
|
|
32
|
+
when /\Aurn:iso/ then "#{source}:term:#{concept_id}"
|
|
33
|
+
else "#{source}/#{concept_id}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_gcr_hash
|
|
38
|
+
h = { "term" => term }
|
|
39
|
+
h["concept_id"] = concept_id if concept_id
|
|
40
|
+
h["source"] = source if source
|
|
41
|
+
h["ref_type"] = ref_type if ref_type
|
|
42
|
+
h.compact
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|