metanorma-utils 1.11.7 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aad2ada58dc0997b429f6bf703c4e1495b6a2e036477ceb757048ec38995b32c
4
- data.tar.gz: 32c86b68d932fa0dba31c0fc41931632b2a3b8cc0e964c5f6b95e7b8d3e3cd27
3
+ metadata.gz: 3356c5d36c4605ce33d895949bb01b4bc59c823ef73fa6974844746578347087
4
+ data.tar.gz: 13f3f9e75e204cb1c152186e9c0feae34b02542dde612626535d598022a8cd50
5
5
  SHA512:
6
- metadata.gz: be9ad9ecf0e96cb3999c53fbf205bfa624c4f1ce6bd2ad0ae1e90b0c8541d5f81a24db8b702471a778464dac86fdd6f299a739c04fd8de56aa6ff478717507cc
7
- data.tar.gz: 056d8756d3fe2788550e795693a621dfca4a716280f62aa2d74530628a40cd3c3071b24a7105528285411afdfbd9ed9444e3cf00c190c02352e6595fa8bd554f
6
+ metadata.gz: f90ebfb556dadc68dde7ed04ed93f44819e69a60fe0daab2775d9cb61726b12222cfe187456770f92399a250dd6d52b079a56f6ad6a2da0a253b72ca7a7a7c03
7
+ data.tar.gz: 25a38f448ca9883380623391f12f8ee6c83b1ed8f4556cab5f6eca39baea3fd79cbbdd8553cc42b265a2b64dbd2e1078a1e7c392a28f9f98a27479f0d0019b86
data/README.adoc CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  image:https://img.shields.io/gem/v/metanorma-utils.svg["Gem Version", link="https://rubygems.org/gems/metanorma-utils"]
4
4
  image:https://github.com/metanorma/metanorma-utils/workflows/rake/badge.svg["Build Status", link="https://github.com/metanorma/metanorma-utils/actions?workflow=rake"]
5
- image:https://codeclimate.com/github/metanorma/metanorma-utils/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/metanorma/metanorma-utils"]
5
+ // image:https://codeclimate.com/github/metanorma/metanorma-utils/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/metanorma/metanorma-utils"]
6
6
  image:https://img.shields.io/github/issues-pr-raw/metanorma/metanorma-utils.svg["Pull Requests", link="https://github.com/metanorma/metanorma-utils/pulls"]
7
7
  image:https://img.shields.io/github/commits-since/metanorma/metanorma-utils/latest.svg["Commits since latest",link="https://github.com/metanorma/metanorma-utils/releases"]
8
8
 
data/lib/utils/cjk.rb CHANGED
@@ -106,5 +106,9 @@ module Metanorma
106
106
  BOPOMOFO_EXTENSIONS
107
107
  ].join("|").freeze
108
108
  end
109
+
110
+ # Export CJK constant for external access while maintaining backward compatibility
111
+ # This allows external code to access the pattern as Metanorma::Utils::CJK
112
+ CJK = (class << self; CJK; end)
109
113
  end
110
114
  end
data/lib/utils/log.rb CHANGED
@@ -1,19 +1,26 @@
1
1
  require "htmlentities"
2
+ require_relative "log_html"
2
3
 
3
4
  module Metanorma
4
5
  module Utils
5
6
  class Log
6
7
  attr_writer :xml, :suppress_log
7
8
 
8
- def initialize
9
+ # messages: hash of message IDs to {error, severity, category}
10
+ # severity: 0: abort; 1: serious; 2: not serious; 3: info only
11
+ def initialize(messages = {})
9
12
  @log = {}
10
13
  @c = HTMLEntities.new
11
14
  @mapid = {}
12
15
  @suppress_log = { severity: 4, category: [] }
16
+ @msg = messages.each_value do |v|
17
+ v[:error] = v[:error]
18
+ .encode("UTF-8", invalid: :replace, undef: :replace)
19
+ end
13
20
  end
14
21
 
15
- def to_ncname(tag)
16
- ::Metanorma::Utils.to_ncname(tag)
22
+ def add_msg(messages)
23
+ @msg.merge!(messages)
17
24
  end
18
25
 
19
26
  def save_to(filename, dir = nil)
@@ -24,21 +31,28 @@ module Metanorma
24
31
  @htmlfilename = "#{b}.html"
25
32
  end
26
33
 
27
- # severity: 0: abort; 1: serious; 2: not serious; 3: info only
28
- def add(category, loc, msg, severity: 2, display: true)
29
- @novalid || suppress_log?(category, severity, msg) and return
30
- @log[category] ||= []
31
- item = create_entry(loc, msg, severity)
32
- @log[category] << item
34
+ def add_prep(id)
35
+ id = id.to_sym
36
+ @msg[id] or raise "Logging: Error #{id} is not defined!"
37
+ @novalid || suppress_log?(id) and return nil
38
+ @log[@msg[id][:category]] ||= []
39
+ @msg[id]
40
+ end
41
+
42
+ def add(id, loc, display: true, params: [])
43
+ m = add_prep(id) or return
44
+ msg = create_entry(loc, m[:error],
45
+ m[:severity], params)
46
+ @log[m[:category]] << msg
33
47
  loc = loc.nil? ? "" : "(#{current_location(loc)}): "
34
- suppress_display?(category, loc, msg, display) or
35
- warn "#{category}: #{loc}#{msg}"
48
+ suppress_display?(m[:category], loc, msg, display) or
49
+ warn "#{m[:category]}: #{loc}#{msg[:error]}"
36
50
  end
37
51
 
38
52
  def abort_messages
39
53
  @log.values.each_with_object([]) do |v, m|
40
54
  v.each do |e|
41
- e[:severity].zero? and m << e[:message]
55
+ e[:severity].zero? and m << e[:error]
42
56
  end
43
57
  end
44
58
  end
@@ -51,9 +65,10 @@ module Metanorma
51
65
  end
52
66
  end
53
67
 
54
- def suppress_log?(category, severity, msg)
55
- category == "Relaton" && /^Fetching /.match?(msg) ||
56
- @suppress_log[:severity] <= severity ||
68
+ def suppress_log?(id)
69
+ category = @msg[id][:category]
70
+ category && /^Fetching /.match?(@msg[id][:error]) ||
71
+ @suppress_log[:severity] <= @msg[id][:severity] ||
57
72
  @suppress_log[:category].include?(category)
58
73
  end
59
74
 
@@ -62,18 +77,30 @@ module Metanorma
62
77
  !display
63
78
  end
64
79
 
65
- def create_entry(loc, msg, severity)
66
- msg = msg.encode("UTF-8", invalid: :replace, undef: :replace)
80
+ def create_entry(loc, msg, severity, params)
81
+ interpolated = interpolate_msg(msg, params)
67
82
  item = { location: current_location(loc), severity: severity,
68
- message: msg, context: context(loc), line: line(loc, msg) }
69
- if item[:message].include?(" :: ")
70
- a = item[:message].split(" :: ", 2)
83
+ error: interpolated, context: context(loc),
84
+ line: line(loc, msg) }
85
+ if item[:error].include?(" :: ")
86
+ a = item[:error].split(" :: ", 2)
71
87
  item[:context] = a[1]
72
- item[:message] = a[0]
88
+ item[:error] = a[0]
73
89
  end
74
90
  item
75
91
  end
76
92
 
93
+ def interpolate_msg(msg, params)
94
+ # Count %s placeholders in the message
95
+ placeholder_count = msg.scan(/%s/).length
96
+ interpolation_params = if params.empty?
97
+ ::Array.new(placeholder_count, "")
98
+ else
99
+ params
100
+ end
101
+ placeholder_count.zero? ? msg : (msg % interpolation_params)
102
+ end
103
+
77
104
  def current_location(node)
78
105
  if node.nil? then ""
79
106
  elsif node.respond_to?(:id) && !node.id.nil? then "ID #{node.id}"
@@ -133,113 +160,6 @@ module Metanorma
133
160
  end
134
161
  ret.to_xml
135
162
  end
136
-
137
- def log_hdr(file)
138
- <<~HTML
139
- <html><head><title>#{file} errors</title>
140
- <meta charset="UTF-8"/>
141
- <style> pre { white-space: pre-wrap; }
142
- thead th { font-weight: bold; background-color: aqua; }
143
- .severity0 { font-weight: bold; background-color: lightpink }
144
- .severity1 { font-weight: bold; }
145
- .severity2 { }
146
- .severity3 { font-style: italic; color: grey; }
147
- </style>
148
- </head><body><h1>#{file} errors</h1>
149
- <ul>#{log_index}</ul>
150
- HTML
151
- end
152
-
153
- def log_index
154
- @log.each_with_object([]) do |(k, v), m|
155
- m << <<~HTML
156
- <li><p><b><a href="##{to_ncname(k)}">#{k}</a></b>: #{index_severities(v)}</p></li>
157
- HTML
158
- end.join("\n")
159
- end
160
-
161
- def index_severities(entries)
162
- s = entries.each_with_object({}) do |e, m|
163
- m[e[:severity]] ||= 0
164
- m[e[:severity]] += 1
165
- end.compact
166
- s.keys.sort.map do |k|
167
- "Severity #{k}: <b>#{s[k]}</b> errors"
168
- end.join("; ")
169
- end
170
-
171
- def write(file = nil)
172
- (!file && @filename) or save_to(file || "metanorma", nil)
173
- File.open(@filename, "w:UTF-8") do |f|
174
- f.puts log_hdr(@filename)
175
- @log.each_key { |key| write_key(f, key) }
176
- f.puts "</body></html>\n"
177
- end
178
- end
179
-
180
- def write_key(file, key)
181
- file.puts <<~HTML
182
- <h2 id="#{to_ncname(key)}">#{key}</h2>\n<table border="1">
183
- <thead><th width="5%">Line</th><th width="20%">ID</th>
184
- <th width="30%">Message</th><th width="40%">Context</th><th width="5%">Severity</th></thead>
185
- <tbody>
186
- HTML
187
- @log[key].sort_by { |a| [a[:line], a[:location], a[:message]] }
188
- .each do |n|
189
- write_entry(file, render_preproc_entry(n))
190
- end
191
- file.puts "</tbody></table>\n"
192
- end
193
-
194
- def render_preproc_entry(entry)
195
- ret = entry.dup
196
- ret[:line] = nil if ret[:line] == "000000"
197
- ret[:location] = loc_link(entry)
198
- ret[:message] = break_up_long_str(entry[:message], 10, 2)
199
- .gsub(/`([^`]+)`/, "<code>\\1</code>")
200
- ret[:context] = context_render(entry)
201
- ret.compact
202
- end
203
-
204
- def context_render(entry)
205
- entry[:context] or return nil
206
- entry[:context].split("\n").first(5)
207
- .join("\n").gsub("><", "> <")
208
- end
209
-
210
- def mapid(old, new)
211
- @mapid[old] = new
212
- end
213
-
214
- def loc_link(entry)
215
- loc = entry[:location]
216
- loc.nil? || loc.empty? and loc = "--"
217
- loc, url = loc_to_url(loc)
218
- loc &&= break_up_long_str(loc, 10, 2)
219
- url and loc = "<a href='#{url}'>#{loc}</a>"
220
- loc
221
- end
222
-
223
- def loc_to_url(loc)
224
- /^ID /.match?(loc) or return [loc, nil]
225
- loc.sub!(/^ID /, "")
226
- loc = @mapid[loc] while @mapid[loc]
227
- url = "#{@htmlfilename}##{to_ncname loc}"
228
- [loc, url]
229
- end
230
-
231
- def break_up_long_str(str, threshold, punct)
232
- Metanorma::Utils.break_up_long_str(str, threshold, punct)
233
- end
234
-
235
- def write_entry(file, entry)
236
- entry[:context] &&= @c.encode(break_up_long_str(entry[:context], 40, 2))
237
- file.print <<~HTML
238
- <tr class="severity#{entry[:severity]}">
239
- <td>#{entry[:line]}</td><th><code>#{entry[:location]}</code></th>
240
- <td>#{entry[:message]}</td><td><pre>#{entry[:context]}</pre></td><td>#{entry[:severity]}</td></tr>
241
- HTML
242
- end
243
163
  end
244
164
  end
245
165
  end
@@ -0,0 +1,163 @@
1
+ module Metanorma
2
+ module Utils
3
+ class Log
4
+ def to_ncname(tag)
5
+ ::Metanorma::Utils.to_ncname(tag)
6
+ end
7
+
8
+ def log_hdr(file)
9
+ <<~HTML
10
+ <html><head><title>#{file} errors</title>
11
+ <meta charset="UTF-8"/>
12
+ <style> pre { white-space: pre-wrap; }
13
+ thead th { font-weight: bold; background-color: aqua; }
14
+ .severity0 { font-weight: bold; background-color: lightpink }
15
+ .severity1 { font-weight: bold; }
16
+ .severity2 { }
17
+ .severity3 { font-style: italic; color: grey; }
18
+ </style>
19
+ </head><body><h1>#{file} errors</h1>
20
+ <ul>#{log_index}</ul>
21
+ HTML
22
+ end
23
+
24
+ def log_index
25
+ @log.each_with_object([]) do |(k, v), m|
26
+ m << <<~HTML
27
+ <li><p><b><a href="##{to_ncname(k)}">#{k}</a></b>: #{index_severities(v)}</p></li>
28
+ HTML
29
+ end.join("\n")
30
+ end
31
+
32
+ def index_severities(entries)
33
+ s = entries.each_with_object({}) do |e, m|
34
+ m[e[:severity]] ||= 0
35
+ m[e[:severity]] += 1
36
+ end.compact
37
+ s.keys.sort.map do |k|
38
+ "Severity #{k}: <b>#{s[k]}</b> errors"
39
+ end.join("; ")
40
+ end
41
+
42
+ def write(file = nil)
43
+ (!file && @filename) or save_to(file || "metanorma", nil)
44
+ File.open(@filename, "w:UTF-8") do |f|
45
+ f.puts log_hdr(@filename)
46
+ @log.each_key { |key| write_key(f, key) }
47
+ f.puts "</body></html>\n"
48
+ end
49
+ end
50
+
51
+ def write_key(file, key)
52
+ file.puts <<~HTML
53
+ <h2 id="#{to_ncname(key)}">#{key}</h2>\n<table border="1">
54
+ <thead><th width="5%">Line</th><th width="20%">ID</th>
55
+ <th width="30%">Message</th><th width="40%">Context</th><th width="5%">Severity</th></thead>
56
+ <tbody>
57
+ HTML
58
+ @log[key].sort_by { |a| [a[:line], a[:location], a[:error]] }
59
+ .each do |n|
60
+ write_entry(file, render_preproc_entry(n))
61
+ end
62
+ file.puts "</tbody></table>\n"
63
+ end
64
+
65
+ def render_preproc_entry(entry)
66
+ ret = entry.dup
67
+ ret[:line] = nil if ret[:line] == "000000"
68
+ ret[:location] = loc_link(entry)
69
+ ret[:error] = break_up_long_str(entry[:error], 10, 2)
70
+ .gsub(/`([^`]+)`/, "<code>\\1</code>")
71
+ ret[:context] = context_render(entry)
72
+ ret.compact
73
+ end
74
+
75
+ def context_render(entry)
76
+ entry[:context] or return nil
77
+ entry[:context].split("\n").first(5)
78
+ .join("\n").gsub("><", "> <")
79
+ end
80
+
81
+ def mapid(old, new)
82
+ @mapid[old] = new
83
+ end
84
+
85
+ def loc_link(entry)
86
+ loc = entry[:location]
87
+ loc.nil? || loc.empty? and loc = "--"
88
+ loc, url = loc_to_url(loc)
89
+ loc &&= break_up_long_str(loc, 10, 2)
90
+ url and loc = "<a href='#{url}'>#{loc}</a>"
91
+ loc
92
+ end
93
+
94
+ def loc_to_url(loc)
95
+ /^ID /.match?(loc) or return [loc, nil]
96
+ loc.sub!(/^ID /, "")
97
+ loc = @mapid[loc] while @mapid[loc]
98
+ url = "#{@htmlfilename}##{to_ncname loc}"
99
+ [loc, url]
100
+ end
101
+
102
+ def break_up_long_str(str, threshold, punct)
103
+ Metanorma::Utils.break_up_long_str(str, threshold, punct)
104
+ end
105
+
106
+ def write_entry(file, entry)
107
+ entry[:context] &&= @c.encode(break_up_long_str(entry[:context], 40, 2))
108
+ file.print <<~HTML
109
+ <tr class="severity#{entry[:severity]}">
110
+ <td>#{entry[:line]}</td><th><code>#{entry[:location]}</code></th>
111
+ <td>#{entry[:error]}</td><td><pre>#{entry[:context]}</pre></td><td>#{entry[:severity]}</td></tr>
112
+ HTML
113
+ end
114
+
115
+ def display_messages
116
+ grouped = group_messages_by_category
117
+ grouped.map { |cat, keys| format_category_section(cat, keys) }
118
+ .join("\n")
119
+ end
120
+
121
+ def group_messages_by_category
122
+ sort_messages_by_category_and_key
123
+ .group_by { |k| @msg[k][:category] }
124
+ .sort_by { |cat, _| cat }
125
+ end
126
+
127
+ def format_category_section(category, keys)
128
+ lines = keys.map { |k| format_error_line(k) }
129
+ "#{category}:\n#{lines.join("\n")}"
130
+ end
131
+
132
+ def format_error_line(key)
133
+ padded_key = key.to_s.ljust(12)
134
+ "\t#{padded_key}: #{@msg[key][:error]}"
135
+ end
136
+
137
+ def sort_messages_by_category_and_key
138
+ @msg.keys.sort do |a, b|
139
+ cat_cmp = @msg[a][:category] <=> @msg[b][:category]
140
+ a_parts = parse_message_key(a)
141
+ b_parts = parse_message_key(b)
142
+ cat_cmp.zero? ? compare_key_parts(a_parts, b_parts) : cat_cmp
143
+ end
144
+ end
145
+
146
+ def parse_message_key(key)
147
+ match = key.to_s.match(/^(.+?)_(\d+)$/)
148
+ match ? [match[1], match[2].to_i] : [key.to_s, nil]
149
+ end
150
+
151
+ def compare_key_parts(a_parts, b_parts)
152
+ a_str, a_num = a_parts
153
+ b_str, b_num = b_parts
154
+ if a_num.nil? || b_num.nil?
155
+ a_str <=> b_str
156
+ else
157
+ str_cmp = a_str <=> b_str
158
+ str_cmp.zero? ? a_num <=> b_num : str_cmp
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
data/lib/utils/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Metanorma
2
2
  module Utils
3
- VERSION = "1.11.7".freeze
3
+ VERSION = "2.0.0".freeze
4
4
  end
5
5
  end
@@ -46,6 +46,6 @@ Gem::Specification.new do |spec|
46
46
  spec.add_development_dependency "simplecov", "~> 0.15"
47
47
  spec.add_development_dependency "timecop", "~> 0.9"
48
48
  spec.add_development_dependency "webmock"
49
- spec.add_development_dependency "xml-c14n"
49
+ spec.add_development_dependency "canon", "= 0.1.3"
50
50
  # spec.metadata["rubygems_mfa_required"] = "true"
51
51
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.7
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-23 00:00:00.000000000 Z
11
+ date: 2025-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -277,19 +277,19 @@ dependencies:
277
277
  - !ruby/object:Gem::Version
278
278
  version: '0'
279
279
  - !ruby/object:Gem::Dependency
280
- name: xml-c14n
280
+ name: canon
281
281
  requirement: !ruby/object:Gem::Requirement
282
282
  requirements:
283
- - - ">="
283
+ - - '='
284
284
  - !ruby/object:Gem::Version
285
- version: '0'
285
+ version: 0.1.3
286
286
  type: :development
287
287
  prerelease: false
288
288
  version_requirements: !ruby/object:Gem::Requirement
289
289
  requirements:
290
- - - ">="
290
+ - - '='
291
291
  - !ruby/object:Gem::Version
292
- version: '0'
292
+ version: 0.1.3
293
293
  description: 'metanorma-utils provides utilities for the Metanorma stack
294
294
 
295
295
  '
@@ -313,6 +313,7 @@ files:
313
313
  - lib/utils/image.rb
314
314
  - lib/utils/linestatus.rb
315
315
  - lib/utils/log.rb
316
+ - lib/utils/log_html.rb
316
317
  - lib/utils/main.rb
317
318
  - lib/utils/namespace.rb
318
319
  - lib/utils/version.rb