leptris 1.9.199.0-arm-linux

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3890 -0
  3. data/CLAUDE.md +115 -0
  4. data/CONTEXT.md +39 -0
  5. data/LICENSE.md +33 -0
  6. data/README.adoc +755 -0
  7. data/Rakefile +407 -0
  8. data/TODO.perf/01-ext-accelerated-default-reads.md +28 -0
  9. data/TODO.perf/02-auto-enable-native-layer.md +19 -0
  10. data/TODO.perf/03-bulk-xpath-result-materialization.md +18 -0
  11. data/TODO.perf/04-ext-bound-serialization.md +15 -0
  12. data/TODO.perf/05-native-surface-completion.md +19 -0
  13. data/TODO.perf/06-perf-battery-native-legs.md +16 -0
  14. data/TODO.perf/07-native-read-floor.md +31 -0
  15. data/TODO.perf/08-version-coherent-native-mutations.md +24 -0
  16. data/TODO.perf/09-adoption-lift-fast-path.md +38 -0
  17. data/TODO.perf/10-bulk-attribute-materialization.md +20 -0
  18. data/TODO.perf/11-moxml-gap-battery.md +18 -0
  19. data/TODO.perf/12-document-lifetime-in-c.md +47 -0
  20. data/TODO.perf/13-structural-memos.md +49 -0
  21. data/TODO.perf/14-insert-family-c-bound.md +31 -0
  22. data/TODO.perf/15-compiled-expression-cache.md +35 -0
  23. data/TODO.perf/16-at-xpath-single-result-seam.md +38 -0
  24. data/TODO.perf/17-precomputed-fast-path-flags.md +35 -0
  25. data/TODO.perf/18-inner-html-one-c-pass.md +32 -0
  26. data/TODO.perf/19-lazy-node-pointer.md +39 -0
  27. data/TODO.perf/20-css-translation-cache.md +24 -0
  28. data/TODO.perf/21-key-memo-consult.md +20 -0
  29. data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
  30. data/TODO.perf/23-cbound-value-mutations.md +27 -0
  31. data/TODO.perf/24-immutable-read-lanes.md +22 -0
  32. data/TODO.perf/25-scope-owned-bulk-path.md +39 -0
  33. data/TODO.perf/26-post-mutation-memo-seeding.md +26 -0
  34. data/TODO.perf/27-c-yield-traversal.md +38 -0
  35. data/TODO.perf/28-address-fills.md +25 -0
  36. data/TODO.perf/29-ns-xpath-compiled.md +23 -0
  37. data/TODO.perf/30-copy-and-element-child-faces.md +31 -0
  38. data/TODO.perf/31-battery-rounds-4-8.md +23 -0
  39. data/TODO.perf/32-clean-host-floor-table.md +29 -0
  40. data/TODO.perf/33-cbound-root-set.md +29 -0
  41. data/TODO.perf/34-fragment-fast-lane.md +41 -0
  42. data/TODO.perf/35-parse-default-c-face.md +24 -0
  43. data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
  44. data/TODO.perf/37-document-lazy-pointer.md +28 -0
  45. data/TODO.perf/38-bulk-walk-and-bench-gate.md +23 -0
  46. data/TODO.restructure/01-constraint-compliance-audit.md +40 -0
  47. data/TODO.restructure/02-deep-copy-seam.md +21 -0
  48. data/TODO.restructure/03-evaluation-context-seam.md +18 -0
  49. data/TODO.restructure/04-spec-mece-restructure.md +20 -0
  50. data/TODO.restructure/05-memory-documentation.md +13 -0
  51. data/TODO.restructure/06-architecture-map-refresh.md +12 -0
  52. data/TODO.restructure/07-external-gates.md +16 -0
  53. data/TODO.restructure/08-new-code-audit-and-ext-memo.md +11 -0
  54. data/TODO.restructure/09-iteration-scope.md +29 -0
  55. data/TODO.restructure/10-cold-attr-read-diet.md +19 -0
  56. data/TODO.restructure/11-construction-factory-diet.md +16 -0
  57. data/TODO.restructure/12-resultattr-merge.md +12 -0
  58. data/TODO.restructure/13-spec-mece-followup.md +9 -0
  59. data/TODO.restructure/14-constraint-audit-refresh.md +15 -0
  60. data/TODO.restructure/15-readme-feature-documentation.md +10 -0
  61. data/TODO.restructure/16-claudemd-map-refresh.md +5 -0
  62. data/TODO.restructure/17-perf-drift-record.md +12 -0
  63. data/TODO.restructure/18-audit-spec-doubles-and-gem-contents.md +12 -0
  64. data/TODO.restructure/19-gitignore-hygiene.md +10 -0
  65. data/TODO.restructure/20-utf8proc-enablement.md +34 -0
  66. data/TODO.restructure/21-typeddata-variant-prototype.md +42 -0
  67. data/TODO.restructure/22-sax-drain-bulk-strip.md +28 -0
  68. data/docs/adr/0001-lockstep-mirror.md +17 -0
  69. data/docs/adr/0002-utf8-at-the-seam.md +19 -0
  70. data/docs/adr/0003-readonly-memoization-pattern.md +34 -0
  71. data/docs/adr/0004-lifetime-guard.md +21 -0
  72. data/docs/adr/0005-autoload-manifest-ordering.md +19 -0
  73. data/docs/adr/0006-ruby-variant-policy.md +25 -0
  74. data/ext/build_windows_native.rb +42 -0
  75. data/ext/leptris/native/extconf.rb +55 -0
  76. data/ext/leptris/native/native.c +2528 -0
  77. data/leptris.gemspec +43 -0
  78. data/lib/leptris/version.rb +5 -0
  79. data/lib/leptris/xml/attr.rb +76 -0
  80. data/lib/leptris/xml/c_string_array.rb +37 -0
  81. data/lib/leptris/xml/cdata.rb +27 -0
  82. data/lib/leptris/xml/comment.rb +27 -0
  83. data/lib/leptris/xml/css_to_xpath.rb +192 -0
  84. data/lib/leptris/xml/descriptor.rb +214 -0
  85. data/lib/leptris/xml/diff.rb +74 -0
  86. data/lib/leptris/xml/doc_type.rb +54 -0
  87. data/lib/leptris/xml/document.rb +716 -0
  88. data/lib/leptris/xml/document_fragment.rb +63 -0
  89. data/lib/leptris/xml/element.rb +771 -0
  90. data/lib/leptris/xml/entity_reference.rb +13 -0
  91. data/lib/leptris/xml/evaluation_context.rb +32 -0
  92. data/lib/leptris/xml/ffi.rb +1527 -0
  93. data/lib/leptris/xml/iteration_scope.rb +56 -0
  94. data/lib/leptris/xml/iterparse.rb +144 -0
  95. data/lib/leptris/xml/namespace.rb +43 -0
  96. data/lib/leptris/xml/native.so +0 -0
  97. data/lib/leptris/xml/native_layer.rb +92 -0
  98. data/lib/leptris/xml/node.rb +686 -0
  99. data/lib/leptris/xml/node_set.rb +212 -0
  100. data/lib/leptris/xml/parse_options.rb +97 -0
  101. data/lib/leptris/xml/plan_value.rb +121 -0
  102. data/lib/leptris/xml/processing_instruction.rb +90 -0
  103. data/lib/leptris/xml/pull.rb +212 -0
  104. data/lib/leptris/xml/relaxng.rb +126 -0
  105. data/lib/leptris/xml/result_attr.rb +39 -0
  106. data/lib/leptris/xml/result_text.rb +36 -0
  107. data/lib/leptris/xml/sax/document.rb +45 -0
  108. data/lib/leptris/xml/sax/dom_dispatch.rb +144 -0
  109. data/lib/leptris/xml/sax/parser.rb +326 -0
  110. data/lib/leptris/xml/sax/recorder.rb +374 -0
  111. data/lib/leptris/xml/sax.rb +14 -0
  112. data/lib/leptris/xml/schematron.rb +88 -0
  113. data/lib/leptris/xml/searchable.rb +361 -0
  114. data/lib/leptris/xml/serialization.rb +180 -0
  115. data/lib/leptris/xml/text.rb +32 -0
  116. data/lib/leptris/xml/xpath.rb +89 -0
  117. data/lib/leptris/xml/xquery.rb +77 -0
  118. data/lib/leptris/xml/xslt.rb +85 -0
  119. data/lib/leptris/xml.rb +108 -0
  120. data/lib/leptris.rb +53 -0
  121. data/lib/libleptris.so +0 -0
  122. data/lib/libutf8proc.so.3 +0 -0
  123. data/scripts/gem_smoke.rb +36 -0
  124. metadata +214 -0
@@ -0,0 +1,17 @@
1
+ # ADR 0001: ffi.rb mirrors the public header
2
+
3
+ ## Context
4
+ ffi.rb attaches ~30 functions the binding never calls (typed
5
+ attribute accessors, copy-inserts, allocator hooks, xinclude
6
+ getters). Reviews keep proposing to strip them.
7
+
8
+ ## Decision
9
+ Keep attaching every public-header symbol. The mirror makes the
10
+ attached-vs-exported audit (`nm -gU` on a fresh build) a drift
11
+ detector: when libleptris changes its surface, the audit fails
12
+ loudly instead of the binding silently missing ABI.
13
+
14
+ ## Consequences
15
+ Attachment is declarative and cheap; unused functions cost nothing.
16
+ When a call site starts using a string-returning mirror function,
17
+ add it to the UTF8_RETURNS wrap-list (ADR 0002).
@@ -0,0 +1,19 @@
1
+ # ADR 0002: UTF-8 at the seam, both faces
2
+
3
+ ## Context
4
+ The headers contract every C string as UTF-8, but FFI's implicit
5
+ string conversion returns ASCII-8BIT — on returns (2026-08-25,
6
+ release 1.9.3) and on FFI::Function callback params (2026-08-25,
7
+ 1.9.4; only `characters` was corrected before).
8
+
9
+ ## Decision
10
+ The encoding policy lives entirely in ffi.rb: the UTF8_RETURNS
11
+ wrap-list for called string-returning attaches, plus
12
+ read_owned_string, CStringArray.to_ruby, and the SAX adapter's
13
+ nil-safe utf8 for callback params. Mirror-only attachments stay
14
+ raw; they join the list when a call site appears.
15
+
16
+ ## Consequences
17
+ Memoized readonly results can safely cache strings (they freeze as
18
+ UTF-8, the correct encoding, not BINARY). Non-ASCII content can no
19
+ longer leak BINARY into consumer code.
@@ -0,0 +1,34 @@
1
+ # ADR 0003: memoization stays hand-rolled; writable docs memoize via version stamps
2
+
3
+ ## 2026-08-27 extension (leptris-ruby 1.9.14)
4
+
5
+ Writable documents now memoize too: Document carries a mutation
6
+ version advanced at every mutation gate (Node#ensure_writable!,
7
+ root=, add_pi), and each memoized field stores its OWN version
8
+ stamp (`return @x if memo_hit?(@x_version)`). A shared node-level
9
+ stamp was tried and rejected — one field's recompute resurrects
10
+ another field's stale memo. Readonly semantics are unchanged
11
+ (their version never advances). Round X's "version-stamp rejected:
12
+ the compare costs the read" was corrected: the compare is cheap
13
+ against multi-FFI reads.
14
+
15
+ The hand-rolled per-site pattern (below) still stands; only the
16
+ guard gained a version argument.
17
+
18
+
19
+ ## Context
20
+ Every readonly-mode read repeats the same three lines:
21
+ `return @x if readonly_cached?(:@x); v = compute; @x = v if
22
+ @document&.readonly?`. ~25 sites. Reviews keep proposing a
23
+ `readonly_memo(ivar) { }` concentrator.
24
+
25
+ ## Decision
26
+ Keep the pattern hand-rolled. Concentrating it requires
27
+ instance_variable_get/set — banned project-wide (encapsulation:
28
+ no reaching into another object's ivars, and metaprogrammed ivar
29
+ access hides the memo identity from readers).
30
+
31
+ ## Consequences
32
+ The repetition IS the invariant — greppable, one file at a time.
33
+ New readonly reads copy the three lines verbatim; a site that
34
+ deviates is visible in review.
@@ -0,0 +1,21 @@
1
+ # ADR 0004: borrowed handles validate their lender
2
+
3
+ ## Context
4
+ Before 1.9.5, only Document's own methods checked freed state:
5
+ `doc.free; node.name` read freed memory silently (verified
6
+ returning ""), stale mutations risked segfaults.
7
+
8
+ ## Decision
9
+ `Node#ensure_alive!` guards every c_ptr-derefercing entry point —
10
+ after memo checks (memoized readonly results stay check-free) and
11
+ folded into `ensure_writable!` for mutations. The check is the
12
+ cheapest sufficient one: `Document#c_ptr.nil?` (#free nils it; the
13
+ GC finalizer cannot fire while any handle exists). `Document#freed?`
14
+ is the accurate public predicate.
15
+
16
+ ## Consequences
17
+ Measured cost (harness, 1.9.4→1.9.6): visible only on the
18
+ single-cheapest call shape (Element#[] hot loops, ~13-28%); every
19
+ other loop at parity. Accepted as the price of the contract;
20
+ readonly hot loops have the memoized attributes/keys alternatives.
21
+ Do not re-litigate without a C-side lifetime mechanism.
@@ -0,0 +1,19 @@
1
+ # ADR 0005: the eager FFI require runs after autoload registration
2
+
3
+ ## Context
4
+ Issue #53: eager-loading ffi.rb from inside `module Leptris`
5
+ BEFORE `autoload :XML` was registered let ffi.rb's module opening
6
+ create Leptris::XML first; the registration was shadowed, xml.rb
7
+ never loaded, and the whole API was unreachable
8
+ (constants == [:FFI]).
9
+
10
+ ## Decision
11
+ leptris.rb: register `autoload :XML` first, then the eager
12
+ `require "leptris/xml/ffi"` at top level — ffi.rb's module opening
13
+ triggers the autoload, landing FFI inside the real manifest module;
14
+ a downstream `require "leptris/xml"` is then a no-op. A subprocess
15
+ spec guards the cold path (the in-process suite cannot see it).
16
+
17
+ ## Consequences
18
+ Any new eager require in leptris.rb must come after the autoload
19
+ block. Eager resolution at require time (issue #49) is unchanged.
@@ -0,0 +1,25 @@
1
+ # ADR 0006: the ruby-platform variant ships as the fallback, unvendored
2
+
3
+ ## Context
4
+ The 1.9.0-era CHANGELOG said "never publishes ruby-platform releases
5
+ without the vendored library — platform variants only", and an early
6
+ fix removed the `gem:native:any` task. The 1.9.0 lockstep restored
7
+ the `ruby` matrix entry deliberately, and every release since has
8
+ published the variant — practice contradicting the recorded policy.
9
+
10
+ ## Decision
11
+ The `ruby`-platform gem ships as the fallback/source variant WITHOUT
12
+ a vendored library, by design. It is safe because library resolution
13
+ is eager (issue #49): on a machine without a usable libleptris the
14
+ gem fails at `require` with the actionable remedy (install the
15
+ platform-specific variant or set LEPTRIS_LIB_PATH) instead of deep
16
+ inside Document.parse. Bundler prefers platform-specific gems
17
+ wherever they exist (9 of 10 published variants are precompiled);
18
+ the ruby variant serves source installs and exotic platforms that
19
+ accept providing their own library.
20
+
21
+ ## Consequences
22
+ Do not re-propose removing the `platform: any` release leg without
23
+ new evidence that bundler misresolves it in practice. A future
24
+ change to the eager-load behavior (ADR 0005 ordering aside) must
25
+ keep the ruby variant failing loudly at require time.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Builds the native read layer DLL for the CURRENT Ruby and
4
+ # installs it under its minor-versioned name
5
+ # (lib/leptris/xml/native_<major>_<minor>.so).
6
+ #
7
+ # #207/#227: a PE DLL cannot resolve Ruby imports lazily — it
8
+ # must bind the build Ruby's x64-ucrt-rubyNNN.dll. One DLL per
9
+ # supported minor therefore ships in the Windows platform gems,
10
+ # and native_layer picks by RUBY_VERSION at require.
11
+ #
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.
17
+
18
+ require "rbconfig"
19
+ require "fileutils"
20
+
21
+ root = File.expand_path("..", __dir__)
22
+ ext_dir = File.join(root, "ext", "leptris", "native")
23
+ minor = RUBY_VERSION[/\A\d+\.\d+/]
24
+ minor_us = minor.tr(".", "_")
25
+
26
+ Dir.chdir(ext_dir) do
27
+ system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
28
+ # mkmf's link binds the CURRENT Ruby's runtime DLL — exactly
29
+ # what the versioned naming is for.
30
+ success = system("make")
31
+ abort "make failed under #{RUBY_VERSION}" unless success
32
+ so = Dir.glob("native.{so,dll}").first
33
+ abort "native bundle not produced under #{RUBY_VERSION}" unless so
34
+ dest = File.join(root, "lib", "leptris", "xml", "native_#{minor_us}.so")
35
+ # The artifact must reference only this minor's Ruby DLL.
36
+ imported = `strings #{so} 2>/dev/null`[/[a-z0-9-]*ruby\d{3,}\.dll/i]
37
+ if imported && !imported.include?("ruby#{minor.delete('.')}")
38
+ abort "#{so} imports #{imported} but was built under #{RUBY_VERSION} — refuse to mis-name it"
39
+ end
40
+ FileUtils.cp(so, dest)
41
+ puts "Installed native layer for Ruby #{minor} -> #{dest} (#{imported || 'no ruby dll string found'})"
42
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The bundle must load into ANY Ruby (not the building runner's):
4
+ # macOS links with -undefined dynamic_lookup (Ruby symbols resolve
5
+ # from the loading interpreter — the standard C-ext convention)
6
+ # and a conservative deployment target; libruby is never linked.
7
+ ENV["MACOSX_DEPLOYMENT_TARGET"] ||= "11.0"
8
+ require "mkmf"
9
+ if RUBY_PLATFORM =~ /darwin/
10
+ # Robust across mkmf versions: older rubies drop $(ldflags)
11
+ # from the bundle link line, so pin BOTH the compile and link
12
+ # variables. -undefined dynamic_lookup is the standard C-ext
13
+ # convention (Ruby symbols resolve from the loading
14
+ # interpreter); -mmacosx-version-min keeps the bundle loadable
15
+ # on older user OSes than the building runner.
16
+ min = " -mmacosx-version-min=#{ENV['MACOSX_DEPLOYMENT_TARGET']}"
17
+ lookup = " -undefined dynamic_lookup"
18
+ [$LDFLAGS, $DLDFLAGS, $CFLAGS, $CXXFLAGS].each do |var|
19
+ var << min unless var.include?(min.strip)
20
+ end
21
+ [$LDFLAGS, $DLDFLAGS].each do |var|
22
+ var << lookup unless var.include?("dynamic_lookup")
23
+ end
24
+ # setup-ruby builds carry the RUNNER's absolute libruby path in
25
+ # LIBRUBYARG_SHARED — recorded as an LC_LOAD_DYLIB in the
26
+ # bundle, unresolvable on user machines. A Ruby extension
27
+ # resolves Ruby symbols from the loading interpreter; never
28
+ # link libruby.
29
+ # BOTH maps: mkmf's Makefile interpolates from MAKEFILE_CONFIG,
30
+ # not CONFIG — clearing only CONFIG is a silent no-op (caught via
31
+ # the build-log otool on the runner).
32
+ RbConfig::CONFIG["LIBRUBYARG_SHARED"] = ""
33
+ RbConfig::CONFIG["LIBRUBYARG_STATIC"] = ""
34
+ RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_SHARED"] = ""
35
+ RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_STATIC"] = ""
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
55
+ create_makefile("leptris/xml/native")