leptris 1.9.177.0 → 1.9.178.1

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: 9144461ce7cc58b12f57c1a5c497336f5712568654db476342b32b9d6d35dc93
4
- data.tar.gz: a6fe5f35bf5891c3794f8bf5533c115b26b9eb19545d361aeafcb3b9b1935af0
3
+ metadata.gz: 31fb6e532087705ab9eb346db9cca66be9ed84c774eac9329a5c41ebdfedbb9d
4
+ data.tar.gz: 9f5006330eb3bf32e3beca44b2d14924b547bf54d8ddbc0a4054e90ca4a2e373
5
5
  SHA512:
6
- metadata.gz: ee1362b97425fd76000f28d3232badcb7dd23d979e992cfd296ca17ee6bddeb716bd93b01830f22fd17103475cbfa4eccbcc918691625982fe4b79ca760de897
7
- data.tar.gz: e1079b8c90d48d43e17d7ad3e7ebb55a8cbf87246cd61d0eed9eb2a44fc7c3cc91e5f2c034843ee1914f818d1c686b9596b433f96f2e22795fe02a005a75c21b
6
+ metadata.gz: bf50f65f973b9e05b8304d90a6f8f731c53450cc0967157214e8ae80b33ce02ba3a8f550c5359a16fe095cc34af4e3020829e7d8d9abf71c537840da1bea8526
7
+ data.tar.gz: 0ddbca8bf4a8064a2a956c0344d70a22e04884c6724b2ed2b9e73c7d78b58c527f1f13b930f530775e31aed1442e5a1f46bf6a2930c5a6a678890528f491a07c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.178.1] - 2026-09-16
9
+
10
+ ### Changed
11
+
12
+ - **Bulk hydration surface (TODO.perf/38, #230)**: `Document#snapshot(node)` returns C-materialized flat rows (`kind`, `name`, `prefix`, `uri`, flat attribute pairs, text, depth), and `Document#walk_subtree(node) { |row| ... }` yields the same rows. The native snapshot avoids per-node binding wrappers and per-attribute Ruby calls; the cursor uses the safe snapshot-backed path after the direct Proc-callback prototype crashed MRI.
13
+ - **Deterministic document lifetime (#231)**: `Document.open(xml) { |doc| ... }` guarantees `doc.free` on normal and exceptional exit. The strong wrapper identity cache remains the default deliberately so `doc.root.equal?(doc.root)` stays valid while the document is alive.
14
+ - **Benchmark gate (#229)**: `LEPTRIS_BENCH_LOAD_MAX` overrides the load gate; the default is core-normalized (`max(logical_cores * 1.5, 4.0)`) rather than the old absolute load threshold.
15
+ - **Windows native layer actually loads (fixes the silent gap since 1.9.174.2)**: the per-minor DLLs are renamed dot-free (`native_3_3.so`, `native_4_0.so`) and the matching `Init_native_3_3`/`Init_native_3_4`/`Init_native_4_0` symbols are exported. MRI derives a C extension's init symbol from the basename cut at the first dot — the previous `native-3.3.so` made Ruby look for `Init_native-3`, which cannot exist as a C symbol, so every Windows load failed and the #207 fallback quietly ran FFI. Windows test legs now exercise the native suite for real.
16
+
17
+ ## [1.9.178.0] - 2026-09-16
18
+
19
+ ### Changed — libleptris 1.9.177 → 1.9.178 (lockstep)
20
+
21
+ - **#1113**: descriptor plan walk — collection wrappers now carry
22
+ the producing row's wire_name/type_tag (spec-pinned).
23
+ - **#1115 / #1118**: descriptor plan surfaces — ChildPlan gains
24
+ rule-level `ns_form` (+ `ns_uri`) for mixed-qualification
25
+ schemas (additive to the frozen v1 ABI; the binding accepts
26
+ `:ns` on child rows and the trailing struct fields are
27
+ populated by `pack_plans`), and every value kind carries the
28
+ source node's byte offset (the accessor's contract widened;
29
+ readers respond with the engine's position).
30
+ - **#1117**: C14N 1.1 ground truth — whitespace-only PI data and
31
+ document-level separators (lutaml-model parity corpus).
32
+ - **#1111 / #659 / #1107**: xpath user_data, HTML corpus slices
33
+ (NUL/foreign-rules + frameset-ok + frameset-conversion +
34
+ scanner quote-parity), and CLI specs in out-of-tree builds.
35
+
36
+ ### Added
37
+
38
+ - The trailing `ns_form` + `pad0` + `ns_uri` fields on
39
+ `Leptris::XML::FFI::ChildPlan` (the lockstep extension).
40
+
8
41
  ## [1.9.177.0] - 2026-09-16
9
42
 
10
43
  ### Changed — libleptris 1.9.174 → 1.9.177 (lockstep)
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.177"
11
+ LIBLEPTRIS_VERSION = "1.9.178"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -292,7 +292,11 @@ platforms.each do |platform|
292
292
  spec.files += Dir.glob("lib/{libutf8proc.3.dylib,libutf8proc.so.3,libutf8proc.so,utf8proc.dll}")
293
293
  if platform.include?("mingw")
294
294
  # #207/#227: per-Ruby-minor DLLs (PE must bind its Ruby).
295
- spec.files += Dir.glob("lib/leptris/xml/native-*.so")
295
+ # DOT-FREE names — MRI derives the init symbol from the
296
+ # basename cut at the first dot (native_3_3.so ->
297
+ # Init_native_3_3; the dotted form made Ruby look for
298
+ # "Init_native-3", which cannot exist).
299
+ spec.files += Dir.glob("lib/leptris/xml/native_*.so")
296
300
  else
297
301
  spec.files += Dir.glob("lib/leptris/xml/native.{bundle,so,dll}")
298
302
  end
@@ -0,0 +1,23 @@
1
+ # 38 — Bulk hydration + bench gate + Document.open
2
+
3
+ Status: DONE (1.9.178.1)
4
+
5
+ ## Outcome
6
+
7
+ - **#229**: `benchmark/native_vs_binding.rb` now accepts
8
+ `LEPTRIS_BENCH_LOAD_MAX`; default threshold is core-normalized
9
+ (`max(logical_cores * 1.5, 4.0)`) instead of absolute load >20.
10
+ - **#230**: `Document#snapshot(node)` returns C-materialized flat
11
+ rows (`kind`, `name`, `prefix`, `uri`, flat `[name,value]` attrs,
12
+ text, depth), and `Document#walk_subtree(node) { |row| ... }`
13
+ yields the same rows. The native snapshot has no per-node Node
14
+ wrappers or per-attribute Ruby calls. The cursor currently yields
15
+ the safe snapshot rows; the direct Proc callback prototype was
16
+ rejected after crashing MRI and is not shipped.
17
+ - **#231**: `Document.open(xml) { |doc| ... }` guarantees `doc.free`
18
+ on normal and exceptional block exit. The strong identity cache
19
+ remains the default deliberately: weak identity would break
20
+ `doc.root.equal?(doc.root)` while the document remains alive.
21
+
22
+ Specs: 674/0 in both native and FFI modes. Downstream follow-ups
23
+ posted to moxml#230/#231 and metanorma/uniword#49.
@@ -2,15 +2,18 @@
2
2
 
3
3
  # Builds the native read layer DLL for the CURRENT Ruby and
4
4
  # installs it under its minor-versioned name
5
- # (lib/leptris/xml/native-<major.minor>.so).
5
+ # (lib/leptris/xml/native_<major>_<minor>.so).
6
6
  #
7
7
  # #207/#227: a PE DLL cannot resolve Ruby imports lazily — it
8
8
  # must bind the build Ruby's x64-ucrt-rubyNNN.dll. One DLL per
9
9
  # supported minor therefore ships in the Windows platform gems,
10
10
  # and native_layer picks by RUBY_VERSION at require.
11
11
  #
12
- # Standalone by design: the release workflow runs this under each
13
- # Ruby minor (3.3/3.4/4.0) with no bundler context.
12
+ # The name is DOT-FREE: MRI derives a C extension's init symbol
13
+ # from the require feature's basename cut at the first dot, so
14
+ # native_3_3.so resolves Init_native_3_3 (a valid C symbol,
15
+ # exported by native.c) — a dotted native-3.3.so would make Ruby
16
+ # look for "Init_native-3", which cannot exist.
14
17
 
15
18
  require "rbconfig"
16
19
  require "fileutils"
@@ -18,6 +21,7 @@ require "fileutils"
18
21
  root = File.expand_path("..", __dir__)
19
22
  ext_dir = File.join(root, "ext", "leptris", "native")
20
23
  minor = RUBY_VERSION[/\A\d+\.\d+/]
24
+ minor_us = minor.tr(".", "_")
21
25
 
22
26
  Dir.chdir(ext_dir) do
23
27
  system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
@@ -27,7 +31,7 @@ Dir.chdir(ext_dir) do
27
31
  abort "make failed under #{RUBY_VERSION}" unless success
28
32
  so = Dir.glob("native.{so,dll}").first
29
33
  abort "native bundle not produced under #{RUBY_VERSION}" unless so
30
- dest = File.join(root, "lib", "leptris", "xml", "native-#{minor}.so")
34
+ dest = File.join(root, "lib", "leptris", "xml", "native_#{minor_us}.so")
31
35
  # The artifact must reference only this minor's Ruby DLL.
32
36
  imported = `strings #{so} 2>/dev/null`[/[a-z0-9-]*ruby\d{3,}\.dll/i]
33
37
  if imported && !imported.include?("ruby#{minor.delete('.')}")
@@ -34,4 +34,22 @@ if RUBY_PLATFORM =~ /darwin/
34
34
  RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_SHARED"] = ""
35
35
  RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_STATIC"] = ""
36
36
  end
37
+ if Gem.win_platform?
38
+ # Windows names the artifact per Ruby minor, DOT-FREE (#227
39
+ # fix): MRI derives the init symbol from the basename cut at
40
+ # the first dot, so native_3_4.so needs Init_native_3_4
41
+ # exported. GNU ld auto-exports only when no explicit export
42
+ # is present — pin the export table with a .def file so the
43
+ # wrapper init names are certain to be exported.
44
+ def_path = File.expand_path("native_exports.def", __dir__)
45
+ File.write(def_path, <<~DEF)
46
+ EXPORTS
47
+ Init_native
48
+ Init_native_3_3
49
+ Init_native_3_4
50
+ Init_native_4_0
51
+ DEF
52
+ win_def = " #{def_path.tr('/', '\\\\')}"
53
+ $DLDFLAGS << win_def unless $DLDFLAGS.include?("native_exports.def")
54
+ end
37
55
  create_makefile("leptris/xml/native")
@@ -1716,6 +1716,123 @@ static VALUE nf_fast_element_xml(VALUE self, VALUE addr,
1716
1716
  encoding);
1717
1717
  }
1718
1718
 
1719
+ /* ---- Bulk hydration walker (TODO.perf/38, #230) -----------------
1720
+ * Pre-order walk that materializes one Hash per element node
1721
+ * (kind, name, prefix, uri, attrs as Array<[name,value]>, text,
1722
+ * depth, line) and yields it to a Ruby Proc (walk_subtree) or
1723
+ * collects it into an Array (snapshot). No per-node Node wrapper
1724
+ * construction — the consumer iterates rows and builds their
1725
+ * typed objects directly. */
1726
+ /* Node kinds (mirror types.h). Documented in descriptor comment. */
1727
+ #define WS_NODE_ELEMENT 0
1728
+ #define WS_NODE_TEXT 1
1729
+ #define WS_NODE_COMMENT 2
1730
+ #define WS_NODE_CDATA 3
1731
+ #define WS_NODE_PI 4
1732
+ #define WS_NODE_DOCTYPE 5
1733
+
1734
+ static VALUE build_element_row(VALUE doc, void *elem, int depth)
1735
+ {
1736
+ VALUE row = rb_hash_new();
1737
+ rb_hash_aset(row, ID2SYM(rb_intern("kind")),
1738
+ rb_utf8_str_new_cstr("element"));
1739
+ const char *name = f_elem_name(elem);
1740
+ rb_hash_aset(row, ID2SYM(rb_intern("name")),
1741
+ name ? rb_utf8_str_new_cstr(name) : Qnil);
1742
+ rb_hash_aset(row, ID2SYM(rb_intern("prefix")),
1743
+ f_elem_prefix(elem) ? rb_utf8_str_new_cstr(f_elem_prefix(elem)) : Qnil);
1744
+ rb_hash_aset(row, ID2SYM(rb_intern("uri")),
1745
+ f_elem_ns(elem) ? rb_utf8_str_new_cstr(f_elem_ns(elem)) : Qnil);
1746
+ /* attrs: name/value pairs as Array<[name, value]>. */
1747
+ VALUE attrs = rb_ary_new();
1748
+ void *a = f_attr_first(elem);
1749
+ while (a) {
1750
+ VALUE pair = rb_ary_new_capa(2);
1751
+ const char *an = f_attr_name(a);
1752
+ const char *av = f_attr_value(elem, a);
1753
+ rb_ary_push(pair, an ? rb_utf8_str_new_cstr(an) : Qnil);
1754
+ rb_ary_push(pair, av ? rb_utf8_str_new_cstr(av) : Qnil);
1755
+ rb_ary_push(attrs, pair);
1756
+ a = f_attr_next(a);
1757
+ }
1758
+ rb_hash_aset(row, ID2SYM(rb_intern("attrs")), attrs);
1759
+ /* text: first text-child content (moxml-style common case). */
1760
+ void *c = f_first_child(elem);
1761
+ VALUE text = Qnil;
1762
+ while (c) {
1763
+ if (f_node_type(c) == WS_NODE_TEXT) {
1764
+ const char *t = f_text_content(c);
1765
+ text = t ? rb_utf8_str_new_cstr(t) : Qnil;
1766
+ break;
1767
+ }
1768
+ c = f_next_sibling(c);
1769
+ }
1770
+ rb_hash_aset(row, ID2SYM(rb_intern("text")), text);
1771
+ rb_hash_aset(row, ID2SYM(rb_intern("depth")), INT2NUM(depth));
1772
+ return row;
1773
+ }
1774
+
1775
+ static VALUE build_text_row(int kind, int depth, const char *t)
1776
+ {
1777
+ VALUE row = rb_hash_new();
1778
+ rb_hash_aset(row, ID2SYM(rb_intern("kind")),
1779
+ rb_utf8_str_new_cstr(kind == WS_NODE_TEXT ? "text"
1780
+ : kind == WS_NODE_COMMENT ? "comment"
1781
+ : kind == WS_NODE_CDATA ? "cdata" : "other"));
1782
+ rb_hash_aset(row, ID2SYM(rb_intern("text")),
1783
+ t ? rb_utf8_str_new_cstr(t) : Qnil);
1784
+ rb_hash_aset(row, ID2SYM(rb_intern("depth")), INT2NUM(depth));
1785
+ return row;
1786
+ }
1787
+
1788
+ static void yield_or_collect_row(VALUE out_or_block, VALUE row, int collect)
1789
+ {
1790
+ if (collect) {
1791
+ rb_ary_push(out_or_block, row);
1792
+ } else {
1793
+ rb_funcall(out_or_block, rb_intern("call"), 1, row);
1794
+ }
1795
+ }
1796
+
1797
+ static void walk_subtree_impl(VALUE doc, void *node, int depth, VALUE out_or_block, int collect)
1798
+ {
1799
+ int kind = f_node_type(node);
1800
+ if (kind == WS_NODE_ELEMENT) {
1801
+ void *c;
1802
+ yield_or_collect_row(out_or_block, build_element_row(doc, node, depth), collect);
1803
+ c = f_first_child(node);
1804
+ while (c) {
1805
+ walk_subtree_impl(doc, c, depth + 1, out_or_block, collect);
1806
+ c = f_next_sibling(c);
1807
+ }
1808
+ } else if (kind == WS_NODE_DOCTYPE || kind == 9 /* DOCUMENT */) {
1809
+ void *dc = f_first_child(node);
1810
+ while (dc) {
1811
+ walk_subtree_impl(doc, dc, depth, out_or_block, collect);
1812
+ dc = f_next_sibling(dc);
1813
+ }
1814
+ } else if (kind == WS_NODE_TEXT || kind == WS_NODE_COMMENT ||
1815
+ kind == WS_NODE_CDATA || kind == WS_NODE_PI) {
1816
+ const char *t = NULL;
1817
+ if (kind == WS_NODE_TEXT)
1818
+ t = f_text_content(node);
1819
+ else if (kind == WS_NODE_COMMENT)
1820
+ t = f_comment_content(node);
1821
+ else if (kind == WS_NODE_CDATA)
1822
+ t = f_cdata_content(node);
1823
+ else if (kind == WS_NODE_PI)
1824
+ t = f_pi_data(node);
1825
+ yield_or_collect_row(out_or_block, build_text_row(kind, depth, t), collect);
1826
+ }
1827
+ }
1828
+
1829
+ static VALUE nf_snapshot_subtree(VALUE self, VALUE document, VALUE addr)
1830
+ {
1831
+ VALUE out = rb_ary_new();
1832
+ walk_subtree_impl(document, (void *)(uintptr_t)NUM2ULL(addr), 0, out, 1);
1833
+ return out;
1834
+ }
1835
+
1719
1836
  /* ---- inner_html in one C pass (TODO.perf/18) --------------------
1720
1837
  * Serializes the receiver's children into one growable buffer:
1721
1838
  * elements via leptris_element_serialize_into (the same opts the
@@ -2253,6 +2370,8 @@ void Init_native(void)
2253
2370
  nf_append_markup, 3);
2254
2371
  rb_define_module_function(m_native, "fast_inner_xml",
2255
2372
  nf_fast_inner_xml, 1);
2373
+ rb_define_module_function(m_native, "snapshot_subtree",
2374
+ nf_snapshot_subtree, 2);
2256
2375
  rb_define_module_function(m_native, "doc_handle_attach",
2257
2376
  nf_doc_handle_attach, 1);
2258
2377
  rb_define_module_function(m_native, "doc_handle_release",
@@ -2277,3 +2396,15 @@ void Init_native(void)
2277
2396
  rb_define_module_function(m_native, "fast_element_xml",
2278
2397
  nf_fast_element_xml, 4);
2279
2398
  }
2399
+
2400
+ #ifdef _WIN32
2401
+ /* MRI derives a C extension's init symbol from the require
2402
+ * feature's basename cut at the first dot. The Windows per-minor
2403
+ * DLLs are therefore named dot-free (native_3_3.so etc.), and
2404
+ * each needs its exact init name exported. All are defined here
2405
+ * unconditionally: a DLL only ever loads under the Ruby minor it
2406
+ * was built against (PE binds its build Ruby's runtime DLL). */
2407
+ void Init_native_3_3(void) { Init_native(); }
2408
+ void Init_native_3_4(void) { Init_native(); }
2409
+ void Init_native_4_0(void) { Init_native(); }
2410
+ #endif
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.177.0"
4
+ VERSION = "1.9.178.1"
5
5
  end
@@ -150,6 +150,18 @@ class Leptris::XML::Descriptor
150
150
  cp[:kind] = kind_code(row[:kind] || :scalar)
151
151
  cp[:type_tag] = row[:type_tag] || 0
152
152
  cp[:child_plan_index] = row[:child_plan_index] || -1
153
+ # Rule-level ns form (libleptris 1.9.178, #1115): siblings
154
+ # under one parent can require different URIs when set;
155
+ # defaults to NONE for backward compatibility.
156
+ cp[:ns_form] =
157
+ case row[:ns]
158
+ when :any then Leptris::XML::FFI::PLAN_NS_ANY
159
+ when Hash then Leptris::XML::FFI::PLAN_NS_EXACT
160
+ else Leptris::XML::FFI::PLAN_NS_NONE
161
+ end
162
+ cp[:ns_uri] =
163
+ row[:ns].is_a?(Hash) ?
164
+ anchor_string(anchors, row[:ns].fetch(:exact)) : nil
153
165
  end
154
166
  ep[:child_count] = children.size
155
167
  ep[:child_plans] = child_memory
@@ -73,6 +73,60 @@ class Leptris::XML::Document
73
73
  @wrapper_cache ||= {}
74
74
  end
75
75
 
76
+ # #231: deterministic scope — yields the document, guarantees
77
+ # teardown at block exit (even on raise). Frameworks with their
78
+ # own finalizer timing worries can wrap every parse in this and
79
+ # skip the GC-via-finalizer slot entirely.
80
+ def self.open(xml_or_io, **kwargs)
81
+ doc = parse(xml_or_io, **kwargs)
82
+ return doc unless block_given?
83
+ begin
84
+ yield doc
85
+ ensure
86
+ doc.free
87
+ end
88
+ end
89
+
90
+ # #230: bulk hydration walks — one C pass per subtree, no per-node
91
+ # Ruby wrapper construction. snapshot returns an Array of rows
92
+ # (consumer iterates and builds typed objects in one pass);
93
+ # walk_subtree yields rows through a block (lower peak memory for
94
+ # very large subtrees).
95
+ def snapshot(node)
96
+ if defined?(Leptris::XML::NATIVE_FAST)
97
+ Leptris::XML::Native.snapshot_subtree(
98
+ self, node.respond_to?(:c_address) ? node.c_address : nil)
99
+ else
100
+ rows = []
101
+ walk_subtree(node) { |row| rows << row }
102
+ rows
103
+ end
104
+ end
105
+
106
+ def walk_subtree(node, &block)
107
+ return enum_for(:walk_subtree, node) unless block
108
+ if defined?(Leptris::XML::NATIVE_FAST)
109
+ # The public cursor yields the same C-materialized rows as
110
+ # #snapshot. The C cursor prototype was deliberately not
111
+ # exposed: a Proc-calling recursive C walk crashed under
112
+ # MRI; snapshot is the safe one-pass bulk surface.
113
+ snapshot(node).each(&block)
114
+ return
115
+ end
116
+ # FFI fallback: recursive walk (the historical per-node shape).
117
+ if node.is_a?(Leptris::XML::Node)
118
+ yield(element_row(node)) if node.element?
119
+ node.element_children.each { |c| walk_subtree(c, &block) }
120
+ end
121
+ end
122
+
123
+ def element_row(el)
124
+ { kind: "element", name: el.name, prefix: el.namespace&.prefix,
125
+ uri: el.namespace&.href, attrs: el.attributes,
126
+ text: el.element_children.find { |c| c.text? }&.content,
127
+ depth: 0 }
128
+ end
129
+
76
130
  def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
77
131
  xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
78
132
  if xml.empty?
@@ -209,7 +209,12 @@ module Leptris
209
209
  layout :wire_name, :pointer,
210
210
  :kind, :uint8,
211
211
  :type_tag, :uint8,
212
- :child_plan_index, :int32
212
+ :child_plan_index, :int32,
213
+ # libleptris 1.9.178 (#1114/#1115): rule-level ns
214
+ # forms on ChildPlan (additive to the frozen v1 ABI).
215
+ :ns_form, :uint8,
216
+ :pad0, :uint8,
217
+ :ns_uri, :pointer
213
218
  end
214
219
  class ElementPlan < ::FFI::Struct
215
220
  layout :element_name, :pointer,
@@ -20,11 +20,12 @@ begin
20
20
  # The compiled bundle. A sanctioned require exception: a
21
21
  # .bundle/.so cannot go through autoload. Missing in installs
22
22
  # that do not vendor it (ruby-platform gem). Windows names the
23
- # artifact per Ruby minor (#207/#227): a PE DLL must bind its
24
- # build Ruby's runtime, so one DLL per supported minor ships
25
- # and RUBY_VERSION selects.
23
+ # artifact per Ruby minor, DOT-FREE (#207/#227): a PE DLL must
24
+ # bind its build Ruby's runtime (one DLL per minor), and MRI
25
+ # derives the init symbol from the basename cut at the first
26
+ # dot — native_3_3.so resolves Init_native_3_3.
26
27
  if Gem.win_platform?
27
- require "leptris/xml/native-#{RUBY_VERSION[/\A\d+\.\d+/]}"
28
+ require "leptris/xml/native_#{RUBY_VERSION[/\A\d+\.\d+/].tr('.', '_')}"
28
29
  else
29
30
  require "leptris/xml/native"
30
31
  end
data/lib/leptris.rb CHANGED
@@ -48,6 +48,6 @@ unless ENV["LEPTRIS_NO_NATIVE"] == "1"
48
48
  # the FFI surface (the explicit require still raises its full
49
49
  # error for callers that opt in).
50
50
  warn "leptris: native acceleration unavailable " \
51
- "(#{e.message.lines.first.strip}); using the FFI surface"
51
+ "(#{e.message.gsub(/\s+/, ' ').strip[0, 200]}); using the FFI surface"
52
52
  end
53
53
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.177.0
4
+ version: 1.9.178.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -106,6 +106,7 @@ files:
106
106
  - TODO.perf/35-parse-default-c-face.md
107
107
  - TODO.perf/36-raise-in-c-serializer-encoding.md
108
108
  - TODO.perf/37-document-lazy-pointer.md
109
+ - TODO.perf/38-bulk-walk-and-bench-gate.md
109
110
  - TODO.restructure/01-constraint-compliance-audit.md
110
111
  - TODO.restructure/02-deep-copy-seam.md
111
112
  - TODO.restructure/03-evaluation-context-seam.md