metanorma-plugin-glossarist 0.3.0 → 0.3.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/.github/workflows/rake.yml +3 -0
- data/.github/workflows/release.yml +7 -1
- data/.gitignore +2 -1
- data/.rubocop.yml +11 -2
- data/.rubocop_todo.yml +326 -0
- data/Gemfile +12 -15
- data/README.adoc +30 -9
- data/lib/metanorma/plugin/glossarist/bibliography_renderer.rb +66 -0
- data/lib/metanorma/plugin/glossarist/concept_filter.rb +132 -0
- data/lib/metanorma/plugin/glossarist/concept_renderer.rb +91 -0
- data/lib/metanorma/plugin/glossarist/concept_serializer.rb +27 -0
- data/lib/metanorma/plugin/glossarist/dataset_preprocessor.rb +116 -298
- data/lib/metanorma/plugin/glossarist/document.rb +8 -23
- data/lib/metanorma/plugin/glossarist/liquid/custom_blocks/with_glossarist_context.rb +58 -136
- data/lib/metanorma/plugin/glossarist/liquid/custom_filters/filters.rb +5 -34
- data/lib/metanorma/plugin/glossarist/liquid/multiply_local_file_system.rb +6 -3
- data/lib/metanorma/plugin/glossarist/sanitize.rb +26 -0
- data/lib/metanorma/plugin/glossarist/version.rb +3 -1
- data/lib/metanorma-plugin-glossarist.rb +8 -10
- data/metanorma-plugin-glossarist.gemspec +6 -6
- metadata +16 -9
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
class ConceptFilter
|
|
7
|
+
COLLECTION_FILTERS = %w[lang domain group sort_by].freeze
|
|
8
|
+
SORT_LAST = [""].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(filters)
|
|
11
|
+
@filters = filters || {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def apply(collection)
|
|
15
|
+
result = collection
|
|
16
|
+
result = filter_by_lang(result) if @filters.key?("lang")
|
|
17
|
+
if @filters.key?("domain") || @filters.key?("group")
|
|
18
|
+
result = filter_by_domain(result)
|
|
19
|
+
end
|
|
20
|
+
result = filter_by_field(result) if field_filter?
|
|
21
|
+
result = sort(result) if @filters.key?("sort_by")
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def field_filter?
|
|
28
|
+
(@filters.keys - COLLECTION_FILTERS).any?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def field_filter_key
|
|
32
|
+
(@filters.keys - COLLECTION_FILTERS).first
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def filter_by_lang(collection)
|
|
36
|
+
lang = @filters["lang"]
|
|
37
|
+
collection.reject { |c| c.localization(lang).nil? }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def filter_by_domain(collection)
|
|
41
|
+
domain = @filters["domain"] || @filters["group"]
|
|
42
|
+
collection.select do |c|
|
|
43
|
+
c.data.domains&.any? { |d| d.concept_id == domain }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def sort(collection)
|
|
48
|
+
field = @filters["sort_by"]
|
|
49
|
+
return collection unless field
|
|
50
|
+
|
|
51
|
+
case field
|
|
52
|
+
when "term", "default_designation"
|
|
53
|
+
collection.sort_by { |c| c.default_designation.to_s.downcase }
|
|
54
|
+
else
|
|
55
|
+
parts = parse_path(field)
|
|
56
|
+
collection.sort_by { |c| sort_key(c, parts) }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sort_key(concept, parts)
|
|
61
|
+
hash = ConceptSerializer.new(concept).to_h
|
|
62
|
+
value = dig_path(hash, parts)
|
|
63
|
+
value.nil? ? SORT_LAST : natural_sort_key(value.to_s)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def natural_sort_key(str)
|
|
67
|
+
str.scan(/(\d+)|(\D+)/)
|
|
68
|
+
.map { |num, txt| num ? num.to_i : txt.downcase }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def filter_by_field(collection)
|
|
72
|
+
path = field_filter_key
|
|
73
|
+
value = @filters[path]
|
|
74
|
+
|
|
75
|
+
start_with = path.include?(".start_with(")
|
|
76
|
+
path, match_value = extract_start_with(path, value, start_with)
|
|
77
|
+
|
|
78
|
+
parts = parse_path(path)
|
|
79
|
+
collection.select do |concept|
|
|
80
|
+
hash = ConceptSerializer.new(concept).to_h
|
|
81
|
+
actual = dig_path(hash, parts)
|
|
82
|
+
if start_with
|
|
83
|
+
actual&.start_with?(match_value)
|
|
84
|
+
else
|
|
85
|
+
actual == value
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def extract_start_with(path, value, start_with)
|
|
91
|
+
return [path, value] unless start_with
|
|
92
|
+
|
|
93
|
+
match = path.match(/^([^.]+(?:\.[^.]+)*)\.start_with\(([^)]+)\)$/)
|
|
94
|
+
return [path, value] unless match
|
|
95
|
+
|
|
96
|
+
[match[1], match[2]]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def parse_path(path)
|
|
100
|
+
path.split(".").flat_map do |segment|
|
|
101
|
+
if segment.include?("[")
|
|
102
|
+
parse_indexed_segment(segment)
|
|
103
|
+
else
|
|
104
|
+
[segment]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_indexed_segment(segment)
|
|
110
|
+
field, index_part = segment.split("[", 2)
|
|
111
|
+
index = index_part&.delete("]'\"")
|
|
112
|
+
if index.match?(/\A\d+\z/)
|
|
113
|
+
[field, index.to_i]
|
|
114
|
+
else
|
|
115
|
+
[field, index]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def dig_path(hash, parts)
|
|
120
|
+
parts.reduce(hash) do |current, key|
|
|
121
|
+
case current
|
|
122
|
+
when Hash
|
|
123
|
+
current[key] || current[key.to_s]
|
|
124
|
+
when Array
|
|
125
|
+
key.is_a?(Integer) ? current[key] : nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
class ConceptRenderer
|
|
7
|
+
TERM_TYPES = %w[preferred admitted deprecated].freeze
|
|
8
|
+
|
|
9
|
+
def initialize(concept, depth:, anchor_prefix: nil)
|
|
10
|
+
@concept = concept
|
|
11
|
+
@depth = depth
|
|
12
|
+
@anchor_prefix = anchor_prefix
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render
|
|
16
|
+
sections = [concept_header]
|
|
17
|
+
sections << alt_terms_section
|
|
18
|
+
sections << definition_section
|
|
19
|
+
sections << examples_section
|
|
20
|
+
sections << notes_section
|
|
21
|
+
sections << sources_section
|
|
22
|
+
sections.compact.join("\n\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def eng_l10n
|
|
28
|
+
@eng_l10n ||= @concept.localization("eng")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def concept_header
|
|
32
|
+
"[[#{anchor_id}]]\n#{heading_line}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def anchor_id
|
|
36
|
+
id = "#{@anchor_prefix}#{@concept.data.id}"
|
|
37
|
+
id.match?(/\A\d/) ? id : Metanorma::Utils.to_ncname(id.gsub(":", "_"))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def heading_line
|
|
41
|
+
"#{'=' * (@depth + 1)} #{term_designation}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def term_designation
|
|
45
|
+
eng_l10n.terms.first&.designation.to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def alt_terms_section
|
|
49
|
+
terms = eng_l10n.terms[1..].map do |term|
|
|
50
|
+
type = TERM_TYPES.include?(term.normative_status) ? term.normative_status : "alt"
|
|
51
|
+
"#{type}:[#{term.designation}]"
|
|
52
|
+
end
|
|
53
|
+
terms.empty? ? nil : terms.join("\n")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def definition_section
|
|
57
|
+
content = eng_l10n.definition.first&.content
|
|
58
|
+
return nil unless content
|
|
59
|
+
|
|
60
|
+
Sanitize.references(content.to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def examples_section
|
|
64
|
+
examples = eng_l10n.examples.map do |example|
|
|
65
|
+
"[example]\n#{Sanitize.references(example.content.to_s)}"
|
|
66
|
+
end
|
|
67
|
+
examples.empty? ? nil : examples.join("\n")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def notes_section
|
|
71
|
+
notes = eng_l10n.notes.map do |note|
|
|
72
|
+
"[NOTE]\n====\n#{Sanitize.references(note.content.to_s)}\n===="
|
|
73
|
+
end
|
|
74
|
+
notes.empty? ? nil : notes.join("\n")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def sources_section
|
|
78
|
+
sources = eng_l10n.sources.filter_map do |source|
|
|
79
|
+
next if source.origin&.text.nil? || source.origin.text.empty?
|
|
80
|
+
next unless source.origin.locality&.type == "clause"
|
|
81
|
+
|
|
82
|
+
ref = source.origin.text.gsub(%r{[ /:]}, "_")
|
|
83
|
+
clause = source.origin.locality.reference_from
|
|
84
|
+
"[.source]\n<<#{ref},#{clause}>>"
|
|
85
|
+
end
|
|
86
|
+
sources.empty? ? nil : sources.join("\n")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
class ConceptSerializer
|
|
7
|
+
def initialize(concept)
|
|
8
|
+
@concept = concept
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_h
|
|
12
|
+
data = @concept.data.to_hash
|
|
13
|
+
data["localizations"] = localizations_hash unless @concept.localizations.empty?
|
|
14
|
+
{ "data" => data.compact }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def localizations_hash
|
|
20
|
+
@concept.localizations.to_h do |l10n|
|
|
21
|
+
[l10n.language_code, l10n.to_hash]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|