leptris 1.9.197.2-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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3890 -0
- data/CLAUDE.md +115 -0
- data/CONTEXT.md +39 -0
- data/LICENSE.md +33 -0
- data/README.adoc +755 -0
- data/Rakefile +407 -0
- data/TODO.perf/01-ext-accelerated-default-reads.md +28 -0
- data/TODO.perf/02-auto-enable-native-layer.md +19 -0
- data/TODO.perf/03-bulk-xpath-result-materialization.md +18 -0
- data/TODO.perf/04-ext-bound-serialization.md +15 -0
- data/TODO.perf/05-native-surface-completion.md +19 -0
- data/TODO.perf/06-perf-battery-native-legs.md +16 -0
- data/TODO.perf/07-native-read-floor.md +31 -0
- data/TODO.perf/08-version-coherent-native-mutations.md +24 -0
- data/TODO.perf/09-adoption-lift-fast-path.md +38 -0
- data/TODO.perf/10-bulk-attribute-materialization.md +20 -0
- data/TODO.perf/11-moxml-gap-battery.md +18 -0
- data/TODO.perf/12-document-lifetime-in-c.md +47 -0
- data/TODO.perf/13-structural-memos.md +49 -0
- data/TODO.perf/14-insert-family-c-bound.md +31 -0
- data/TODO.perf/15-compiled-expression-cache.md +35 -0
- data/TODO.perf/16-at-xpath-single-result-seam.md +38 -0
- data/TODO.perf/17-precomputed-fast-path-flags.md +35 -0
- data/TODO.perf/18-inner-html-one-c-pass.md +32 -0
- data/TODO.perf/19-lazy-node-pointer.md +39 -0
- data/TODO.perf/20-css-translation-cache.md +24 -0
- data/TODO.perf/21-key-memo-consult.md +20 -0
- data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
- data/TODO.perf/23-cbound-value-mutations.md +27 -0
- data/TODO.perf/24-immutable-read-lanes.md +22 -0
- data/TODO.perf/25-scope-owned-bulk-path.md +39 -0
- data/TODO.perf/26-post-mutation-memo-seeding.md +26 -0
- data/TODO.perf/27-c-yield-traversal.md +38 -0
- data/TODO.perf/28-address-fills.md +25 -0
- data/TODO.perf/29-ns-xpath-compiled.md +23 -0
- data/TODO.perf/30-copy-and-element-child-faces.md +31 -0
- data/TODO.perf/31-battery-rounds-4-8.md +23 -0
- data/TODO.perf/32-clean-host-floor-table.md +29 -0
- data/TODO.perf/33-cbound-root-set.md +29 -0
- data/TODO.perf/34-fragment-fast-lane.md +41 -0
- data/TODO.perf/35-parse-default-c-face.md +24 -0
- data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
- data/TODO.perf/37-document-lazy-pointer.md +28 -0
- data/TODO.perf/38-bulk-walk-and-bench-gate.md +23 -0
- data/TODO.restructure/01-constraint-compliance-audit.md +40 -0
- data/TODO.restructure/02-deep-copy-seam.md +21 -0
- data/TODO.restructure/03-evaluation-context-seam.md +18 -0
- data/TODO.restructure/04-spec-mece-restructure.md +20 -0
- data/TODO.restructure/05-memory-documentation.md +13 -0
- data/TODO.restructure/06-architecture-map-refresh.md +12 -0
- data/TODO.restructure/07-external-gates.md +16 -0
- data/TODO.restructure/08-new-code-audit-and-ext-memo.md +11 -0
- data/TODO.restructure/09-iteration-scope.md +29 -0
- data/TODO.restructure/10-cold-attr-read-diet.md +19 -0
- data/TODO.restructure/11-construction-factory-diet.md +16 -0
- data/TODO.restructure/12-resultattr-merge.md +12 -0
- data/TODO.restructure/13-spec-mece-followup.md +9 -0
- data/TODO.restructure/14-constraint-audit-refresh.md +15 -0
- data/TODO.restructure/15-readme-feature-documentation.md +10 -0
- data/TODO.restructure/16-claudemd-map-refresh.md +5 -0
- data/TODO.restructure/17-perf-drift-record.md +12 -0
- data/TODO.restructure/18-audit-spec-doubles-and-gem-contents.md +12 -0
- data/TODO.restructure/19-gitignore-hygiene.md +10 -0
- data/TODO.restructure/20-utf8proc-enablement.md +34 -0
- data/TODO.restructure/21-typeddata-variant-prototype.md +42 -0
- data/TODO.restructure/22-sax-drain-bulk-strip.md +28 -0
- data/docs/adr/0001-lockstep-mirror.md +17 -0
- data/docs/adr/0002-utf8-at-the-seam.md +19 -0
- data/docs/adr/0003-readonly-memoization-pattern.md +34 -0
- data/docs/adr/0004-lifetime-guard.md +21 -0
- data/docs/adr/0005-autoload-manifest-ordering.md +19 -0
- data/docs/adr/0006-ruby-variant-policy.md +25 -0
- data/ext/build_windows_native.rb +42 -0
- data/ext/leptris/native/extconf.rb +55 -0
- data/ext/leptris/native/native.c +2646 -0
- data/leptris.gemspec +43 -0
- data/lib/leptris/version.rb +5 -0
- data/lib/leptris/xml/attr.rb +76 -0
- data/lib/leptris/xml/c_string_array.rb +37 -0
- data/lib/leptris/xml/cdata.rb +27 -0
- data/lib/leptris/xml/comment.rb +27 -0
- data/lib/leptris/xml/css_to_xpath.rb +192 -0
- data/lib/leptris/xml/descriptor.rb +214 -0
- data/lib/leptris/xml/diff.rb +74 -0
- data/lib/leptris/xml/doc_type.rb +54 -0
- data/lib/leptris/xml/document.rb +716 -0
- data/lib/leptris/xml/document_fragment.rb +63 -0
- data/lib/leptris/xml/element.rb +771 -0
- data/lib/leptris/xml/entity_reference.rb +13 -0
- data/lib/leptris/xml/evaluation_context.rb +32 -0
- data/lib/leptris/xml/ffi.rb +1527 -0
- data/lib/leptris/xml/iteration_scope.rb +56 -0
- data/lib/leptris/xml/iterparse.rb +144 -0
- data/lib/leptris/xml/namespace.rb +43 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_layer.rb +92 -0
- data/lib/leptris/xml/node.rb +686 -0
- data/lib/leptris/xml/node_set.rb +212 -0
- data/lib/leptris/xml/parse_options.rb +97 -0
- data/lib/leptris/xml/plan_value.rb +121 -0
- data/lib/leptris/xml/processing_instruction.rb +90 -0
- data/lib/leptris/xml/pull.rb +212 -0
- data/lib/leptris/xml/relaxng.rb +126 -0
- data/lib/leptris/xml/result_attr.rb +39 -0
- data/lib/leptris/xml/result_text.rb +36 -0
- data/lib/leptris/xml/sax/document.rb +45 -0
- data/lib/leptris/xml/sax/dom_dispatch.rb +144 -0
- data/lib/leptris/xml/sax/parser.rb +326 -0
- data/lib/leptris/xml/sax/recorder.rb +374 -0
- data/lib/leptris/xml/sax.rb +14 -0
- data/lib/leptris/xml/schematron.rb +88 -0
- data/lib/leptris/xml/searchable.rb +361 -0
- data/lib/leptris/xml/serialization.rb +180 -0
- data/lib/leptris/xml/text.rb +32 -0
- data/lib/leptris/xml/xpath.rb +89 -0
- data/lib/leptris/xml/xquery.rb +77 -0
- data/lib/leptris/xml/xslt.rb +85 -0
- data/lib/leptris/xml.rb +108 -0
- data/lib/leptris.rb +53 -0
- data/lib/libleptris.so +0 -0
- data/lib/libutf8proc.so.3 +0 -0
- data/scripts/gem_smoke.rb +36 -0
- 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")
|