leptris 1.2.0 → 1.3.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: 28a6d0b01b14207e6a928eead2f38f84bf2dda03b9c7ae4a3c6bcc3163f2760d
4
- data.tar.gz: e23962a817090c8707dc691fe58276f25da7075e1b0b64bb1fa39774cd113d4e
3
+ metadata.gz: e181133c2d381d3ed81fbab6ad41b99c0a72ed89cc651a7882c069eee06535fa
4
+ data.tar.gz: 768c1463a6746f331251d10f5499293b4c00c0914037fee33a08725b7855ac7c
5
5
  SHA512:
6
- metadata.gz: 83e9f30fd09ded2f909d651423db46be8a6baca9f39f310e9b8c3632f5fd7cd5627d193cd57c80d6191a2c00145858b87d9454a72df12e96a28fbd928864a663
7
- data.tar.gz: 5991c97a7ea5b8a3462c62492fd70808f4094d2758525ce2ec6ac47a9ffe61bb318c71d1999c95081e5e48ce10cc98d9e671ea8c52400c3a90b01e4f9a5b3ce1
6
+ metadata.gz: f0c45bea9042ffec94231c4c799aa43145592d1dff5268ee75e75212b015be689ad875dac84b76ee8a7cf0624bb1b983c39b3dba8ef3966bc8591275648c7738
7
+ data.tar.gz: eb2803ee6a55c4dcd543678bcb9899c5651269d9737d92bffa9f8f156ddb87b22838f6c12b610644ee585ed6854041f4815f5b4a6f1de4cceb8dd8ae5aceca50
data/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0] - 2026-08-23
9
+
10
+ Lockstep with libleptris 1.3.0 — takes full advantage of the
11
+ concurrency-release surface.
12
+
13
+ ### Added
14
+
15
+ - **EXSLT extension pack**: `Document#exslt` enables the first-party
16
+ str:/set:/math: function pack (replace, tokenize, split, concat,
17
+ padding; distinct, intersection, difference, leading, trailing;
18
+ max, min, abs, sqrt, power) as native C handlers on that document.
19
+ - `Document#last_error` — per-document error retrieval (thread-safe
20
+ successor to the library-global string).
21
+ - `leptris_thread_cleanup` bound — optional release of per-thread
22
+ registry entries when worker threads exit.
23
+
24
+ ### Changed
25
+
26
+ - Namespace-bound XPath builds its binding set in **one FFI call**
27
+ via `leptris_xpath_ns_set_new_from_pairs` (flat alternating
28
+ prefix/URI array through the CStringArray adapter) instead of
29
+ N+1 calls.
30
+ - `NodeSet#each` fetches mixed-kind results through
31
+ `leptris_xpath_result_get_nodes_ex`, which copies ALL node kinds —
32
+ the per-index fallback for the under-copying legacy accessor
33
+ (leptris#477 residual) is gone.
34
+ - `leptris_version_components` is now officially declared upstream
35
+ (it was exported-but-undeclared; the export gate caught it).
36
+
37
+ ## [1.2.1] - 2026-08-23
38
+
39
+ Completes the libleptris 1.2.0 public surface in the binding.
40
+
41
+ ### Added
42
+
43
+ - **Namespace-bound XPath**: `doc.xpath("//p:title", "p" => "urn:p")`
44
+ (Nokogiri parity) — expression prefixes now resolve to caller-supplied
45
+ URIs regardless of the prefixes the document declared, via
46
+ `leptris_xpath_ns_set_new/_add/_free` + `leptris_xpath_eval_ns`. The
47
+ binding set is built per call and freed under `ensure`. Previously
48
+ the trailing namespace hash was silently ignored.
49
+ - `leptris_error_message` / `leptris_last_error` bound; the status seam
50
+ and parse errors now append the library's last-error detail when
51
+ present (best-effort: it is a library-global string).
52
+
8
53
  ## [1.2.0] - 2026-08-23
9
54
 
10
55
  Lockstep with libleptris 1.2.0.
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.2.0"
11
+ LIBLEPTRIS_VERSION = "1.3.0"
12
12
 
13
13
  CMAKE_FLAGS = %w[
14
14
  -DCMAKE_BUILD_TYPE=Release
@@ -34,8 +34,10 @@ task :compile do
34
34
  sh "cmake --build #{build}/build --config Release -j 4"
35
35
  # Windows names the shared library leptris.dll (no "lib" prefix);
36
36
  # vendoring under the uniform libleptris.* name keeps the FFI
37
- # search order simple.
38
- lib = Dir.glob("#{build}/build/**/libleptris.{dylib,so}").first ||
37
+ # search order simple. libleptris >= 1.3.0 renames the DLL to
38
+ # libleptris.dll on Windows too (leptris/leptris#507, issue
39
+ # TODO.concurrency/05) — accept both names.
40
+ lib = Dir.glob("#{build}/build/**/libleptris.{dylib,so,dll}").first ||
39
41
  Dir.glob("#{build}/build/**/leptris.dll").first
40
42
  raise "libleptris shared library not found after build" unless lib
41
43
  ext = File.extname(lib)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
@@ -50,7 +50,8 @@ class Leptris::XML::Document
50
50
  if raw.null?
51
51
  status = status_ptr.read_int
52
52
  raise Leptris::XML::ParseError,
53
- "leptris_parse_string failed (status=#{status})"
53
+ "leptris_parse_string failed (status=#{status}): " +
54
+ Leptris::XML::FFI.status_message(status)
54
55
  end
55
56
  wrap(raw)
56
57
  end
@@ -222,6 +223,23 @@ class Leptris::XML::Document
222
223
  @wrapper_cache.clear
223
224
  end
224
225
 
226
+ # Enable the first-party EXSLT-style extension pack on this
227
+ # document: str:/set:/math: prefixed functions (replace, tokenize,
228
+ # split, concat, padding; distinct, intersection, difference,
229
+ # leading, trailing; max, min, abs, sqrt, power) as native C
230
+ # handlers. Returns self for chaining.
231
+ def exslt
232
+ Leptris::XML::FFI.check_status(
233
+ Leptris::XML::FFI.leptris_exslt_enable(@c_ptr))
234
+ self
235
+ end
236
+
237
+ # The most recent error recorded against this document, or nil.
238
+ def last_error
239
+ msg = Leptris::XML::FFI.leptris_document_last_error(@c_ptr)
240
+ msg.nil? || msg.empty? ? nil : msg
241
+ end
242
+
225
243
  def name; "document"; end
226
244
  def document; self; end
227
245
  def encoding
@@ -23,6 +23,7 @@ module Leptris
23
23
  typedef :pointer, :leptris_attribute
24
24
  typedef :pointer, :leptris_doctype
25
25
  typedef :pointer, :leptris_xpath_result
26
+ typedef :pointer, :leptris_xpath_ns_set
26
27
  typedef :pointer, :leptris_xpath_var_set
27
28
  typedef :pointer, :leptris_sax_parser
28
29
  typedef :int, :leptris_status
@@ -343,6 +344,10 @@ module Leptris
343
344
  [:leptris_document, :string, :pointer, :pointer], :leptris_status
344
345
  attach_function :leptris_xpath_result_get_nodes,
345
346
  [:leptris_xpath_result, :pointer, :size_t], :size_t
347
+ # v1.3.0: copies ALL node kinds (the legacy accessor drops
348
+ # non-element entries) and can also report per-entry kinds.
349
+ attach_function :leptris_xpath_result_get_nodes_ex,
350
+ [:leptris_xpath_result, :pointer, :pointer, :size_t], :size_t
346
351
  attach_function :leptris_xpath_result_boolean,
347
352
  [:leptris_xpath_result], :int
348
353
  attach_function :leptris_xpath_result_number,
@@ -365,6 +370,22 @@ module Leptris
365
370
  attach_function :leptris_xpath_variable_set_string,
366
371
  [:leptris_xpath_var_set, :string, :string], :leptris_status
367
372
 
373
+ # Namespace-bound XPath (v1.2.0): expression prefixes resolve to
374
+ # caller-supplied URIs regardless of the document's declarations.
375
+ attach_function :leptris_xpath_ns_set_new,
376
+ [], :leptris_xpath_ns_set
377
+ attach_function :leptris_xpath_ns_set_free,
378
+ [:leptris_xpath_ns_set], :void
379
+ attach_function :leptris_xpath_ns_set_add,
380
+ [:leptris_xpath_ns_set, :string, :string], :leptris_status
381
+ # One-call constructor: flat array of 2*pair_count alternating
382
+ # prefix/URI strings (v1.3.0).
383
+ attach_function :leptris_xpath_ns_set_new_from_pairs,
384
+ [:pointer, :size_t], :leptris_xpath_ns_set
385
+ attach_function :leptris_xpath_eval_ns,
386
+ [:leptris_document, :leptris_element, :string, :leptris_xpath_ns_set],
387
+ :leptris_xpath_result
388
+
368
389
  attach_function :leptris_sax_parse,
369
390
  [:string, :size_t, :pointer, :pointer], :int
370
391
  attach_function :leptris_sax_parser_create,
@@ -419,6 +440,19 @@ module Leptris
419
440
  attach_function :leptris_document_set_allocators,
420
441
  [:leptris_document, :pointer, :pointer], :leptris_status
421
442
  attach_function :leptris_status_string, [:leptris_status], :string
443
+ attach_function :leptris_error_message, [:leptris_status], :string
444
+ # Thread-local since v1.3.0; reliable under the
445
+ # one-document-per-thread contract.
446
+ attach_function :leptris_last_error, [], :string
447
+ attach_function :leptris_document_last_error,
448
+ [:leptris_document], :string
449
+ # Optional: release per-thread registry entries when a worker
450
+ # thread exits (long-lived threads never need it).
451
+ attach_function :leptris_thread_cleanup, [], :void
452
+ # First-party EXSLT-style extension pack (str:/set:/math:),
453
+ # registered natively on one document.
454
+ attach_function :leptris_exslt_enable,
455
+ [:leptris_document], :leptris_status
422
456
 
423
457
  LEPTRIS_OK = 0
424
458
  LEPTRIS_ERROR_MEMORY = -1
@@ -467,13 +501,22 @@ module Leptris
467
501
  ptr.read_string.tap { leptris_free_string(ptr) }
468
502
  end
469
503
 
470
- # Single status seam: turns a C status code into a Ruby error.
504
+ # Single status seam: turns a C status code into a Ruby error,
505
+ # appending the library's last-error detail when present. The
506
+ # detail is a library-global string, so it can only be treated
507
+ # as best-effort context.
471
508
  def self.check_status(status)
472
509
  unless status == LEPTRIS_OK
473
- raise Leptris::XML::Error, leptris_status_string(status)
510
+ raise Leptris::XML::Error, status_message(status)
474
511
  end
475
512
  status
476
513
  end
514
+
515
+ def self.status_message(status)
516
+ base = leptris_status_string(status).to_s
517
+ detail = leptris_last_error.to_s
518
+ detail.empty? ? base : "#{base} (#{detail})"
519
+ end
477
520
  end
478
521
  end
479
522
  end
@@ -2,14 +2,19 @@
2
2
 
3
3
  module Leptris::XML::Searchable
4
4
  def xpath(*paths)
5
- handler, _ns, _vars = parse_search_args(paths)
5
+ handler, ns, _vars = parse_search_args(paths)
6
6
  raise ArgumentError, "custom XPath handlers not supported" if handler
7
7
  expr = paths.join(" | ")
8
8
 
9
9
  doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
10
10
  context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
11
11
 
12
- result_ptr = Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
12
+ result_ptr =
13
+ if ns && !ns.empty?
14
+ xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
15
+ else
16
+ Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
17
+ end
13
18
  if result_ptr.null?
14
19
  raise Leptris::XML::XPathError,
15
20
  Leptris::XML::FFI.leptris_status_string(Leptris::XML::FFI::LEPTRIS_ERROR_XPATH)
@@ -50,6 +55,23 @@ module Leptris::XML::Searchable
50
55
 
51
56
  protected
52
57
 
58
+ # Builds a caller-owned namespace binding set in one FFI call
59
+ # (ns_set_new_from_pairs takes a flat alternating prefix/URI array,
60
+ # the same wire format CStringArray owns), evaluates via
61
+ # leptris_xpath_eval_ns, and frees the set regardless of outcome.
62
+ def xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
63
+ flat = ns.flat_map { |prefix, uri| [prefix.to_s, uri.to_s] }
64
+ buffer, _anchors = Leptris::XML::CStringArray.to_c(flat)
65
+ set = Leptris::XML::FFI.leptris_xpath_ns_set_new_from_pairs(
66
+ buffer, flat.length / 2)
67
+ raise Leptris::XML::Error, "leptris_xpath_ns_set_new_from_pairs failed" if set.null?
68
+ begin
69
+ Leptris::XML::FFI.leptris_xpath_eval_ns(doc_ptr, context_ptr, expr, set)
70
+ ensure
71
+ Leptris::XML::FFI.leptris_xpath_ns_set_free(set)
72
+ end
73
+ end
74
+
53
75
  def parse_search_args(args)
54
76
  handler = args.find { |a| !a.is_a?(String) && !a.is_a?(Hash) && !a.is_a?(Symbol) }
55
77
  args = args - [handler] if handler
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.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.