serialbench 0.1.3 → 0.2.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +71 -51
  3. data/Gemfile +2 -0
  4. data/README.adoc +5 -0
  5. data/lib/serialbench/cli/resultset_cli.rb +40 -0
  6. data/lib/serialbench/models/platform.rb +5 -2
  7. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +0 -4
  8. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +6 -1
  9. data/lib/serialbench/serializers/xml/leptris_serializer.rb +167 -0
  10. data/lib/serialbench/serializers/xml/libxml_serializer.rb +9 -0
  11. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +9 -0
  12. data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -0
  13. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +1 -3
  14. data/lib/serialbench/serializers/yaml/psych_serializer.rb +0 -4
  15. data/lib/serialbench/serializers.rb +3 -1
  16. data/lib/serialbench/site_generator.rb +56 -2
  17. data/lib/serialbench/version.rb +1 -1
  18. data/site/.gitignore +5 -0
  19. data/site/README.md +32 -0
  20. data/site/astro.config.mjs +12 -0
  21. data/site/package-lock.json +6160 -0
  22. data/site/package.json +22 -0
  23. data/site/public/favicon.svg +4 -0
  24. data/site/scripts/gen-sample-data.mjs +154 -0
  25. data/site/src/components/AvailabilityMatrix.astro +61 -0
  26. data/site/src/components/CalibratedLeaderboard.vue +134 -0
  27. data/site/src/components/CitationBox.vue +50 -0
  28. data/site/src/components/DashboardConsole.vue +193 -0
  29. data/site/src/components/FeaturesTable.astro +97 -0
  30. data/site/src/components/MemoryPanel.vue +44 -0
  31. data/site/src/components/OpsChart.vue +135 -0
  32. data/site/src/config.ts +18 -0
  33. data/site/src/layouts/Layout.astro +85 -0
  34. data/site/src/lib/channels.ts +24 -0
  35. data/site/src/lib/dashboard.ts +269 -0
  36. data/site/src/pages/index.astro +44 -0
  37. data/site/src/pages/library/[slug].astro +128 -0
  38. data/site/src/pages/methodology.astro +55 -0
  39. data/site/src/styles/global.css +103 -0
  40. data/site/tsconfig.json +5 -0
  41. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c24dcb07a68058a2ff622be5f16e6c7225cd266bf29c3fcd06a0ecd597b9b8d3
4
- data.tar.gz: 95833392ca0fe8cab20d7d0300cd86f11948a2e0d766885e6dff0de2b75189b1
3
+ metadata.gz: ff36b734da92061c2bbf271fd1e94f79f80d844f0b492c7f06a51aca5ed794aa
4
+ data.tar.gz: 28aeb4041d3c1c04cdcfd4b5f5ad21757ed0616277bcf34b2db15c9d56a06b5a
5
5
  SHA512:
6
- metadata.gz: 8e1b7c78643b32cf34251220cd9dac61db43fa16cf104669b2882b139faad9bb1051a3a552d5caf37fc8018014c0dda6bcbe35870688e3ea229c64c5c8064d5e
7
- data.tar.gz: d5c0727a5d5d24d82107837b996f8a31013bcf75a66a737ce6087767858c7b54b59ad9ed9014cf84d0ef3f4c07d5954bcfa3aa0698d3f745bb984477f18b880f
6
+ metadata.gz: 9c497b089e84bc674bd444df875cc161ecf97b0d077d2ba9cb3c26cee91c27d8694cabc149b61f78c4fc0f9890b808a7604e6106e1d91aa04595c40a3cb6fa77
7
+ data.tar.gz: 5a8eebcfe19699b5d9e1aed3caf1c65bec4937fe51199b8da0ef152e6145deab815621c354495279236f9b7c2e0eaa8e1c70eebdc1353ffb8ff6c89c900afeaa
@@ -20,7 +20,10 @@ permissions:
20
20
  id-token: write
21
21
 
22
22
  concurrency:
23
- group: "pages"
23
+ # Renamed off "pages": that group's server-side state is wedged (a queued
24
+ # run there returns 500 on cancel and blocks the single in-progress slot),
25
+ # which starved every benchmark-weekly run after 2026-08-23 10:22Z.
26
+ group: "site-deploy"
24
27
  cancel-in-progress: false
25
28
 
26
29
  jobs:
@@ -33,8 +36,8 @@ jobs:
33
36
  - name: Set matrix configurations
34
37
  id: set-matrix
35
38
  run: |
36
- echo 'ruby-versions=["3.1", "3.2", "3.3", "3.4"]' >> $GITHUB_OUTPUT
37
- echo 'platforms=["ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-13", "macos-15-intel", "macos-14", "macos-15", "macos-26", "windows-2022", "windows-2025", "windows-11-arm"]' >> $GITHUB_OUTPUT
39
+ echo 'ruby-versions=["3.2", "3.3", "3.4", "4.0"]' >> $GITHUB_OUTPUT
40
+ echo 'platforms=["ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-14", "macos-15", "macos-15-intel", "macos-26", "macos-26-intel", "windows-2022", "windows-2025", "windows-11-arm"]' >> $GITHUB_OUTPUT
38
41
 
39
42
  # Cross-platform native benchmarks
40
43
  benchmark:
@@ -46,9 +49,8 @@ jobs:
46
49
  platform: ${{ fromJson(needs.setup.outputs.platforms) }}
47
50
  ruby-version: ${{ fromJson(needs.setup.outputs.ruby-versions) }}
48
51
  exclude:
49
- # windows-11-arm only supports Ruby 3.4.x
50
- - platform: windows-11-arm
51
- ruby-version: "3.1"
52
+ # RubyInstaller ships no arm64 builds for Ruby 3.2/3.3;
53
+ # windows-11-arm runs 3.4.x and 4.x only
52
54
  - platform: windows-11-arm
53
55
  ruby-version: "3.2"
54
56
  - platform: windows-11-arm
@@ -72,8 +74,11 @@ jobs:
72
74
  - name: Install vcpkg dependencies (Windows)
73
75
  if: startsWith(matrix.platform, 'windows-')
74
76
  run: |
75
- vcpkg install libxml2:${{ steps.windows-arch.outputs.arch }} 2>&1 || true
77
+ vcpkg install libxml2:${{ steps.windows-arch.outputs.arch }} libxslt:${{ steps.windows-arch.outputs.arch }} 2>&1 || true
76
78
  vcpkg integrate install 2>&1 || true
79
+ # vcpkg dynamic triplets put the DLLs in bin/; nokogiri's compiled
80
+ # extension needs them on PATH at require time.
81
+ echo "C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/bin" >> $GITHUB_PATH
77
82
  shell: bash
78
83
 
79
84
  - name: Configure bundler for libxml2 (Windows)
@@ -82,6 +87,11 @@ jobs:
82
87
  mkdir -p .bundle 2>&1 || true
83
88
  echo "---" > .bundle/config 2>&1 || true
84
89
  echo 'BUNDLE_BUILD__LIBXML___RUBY: "--with-xml2-dir=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }} --with-xml2-include=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/include/libxml2 --with-xml2-lib=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/lib"' >> .bundle/config 2>&1 || true
90
+ # windows-11-arm has no precompiled nokogiri (pulled transitively via
91
+ # moxml); build it against the vcpkg libxml2 instead of the vendored
92
+ # source build that fails on ucrt/ARM64. Harmless on x64 Windows,
93
+ # where the precompiled gem makes this config moot.
94
+ echo 'BUNDLE_BUILD__NOKOGIRI: "--use-system-libraries --with-xml2-include=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/include/libxml2 --with-xml2-lib=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/lib --with-xslt-include=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/include/libxslt --with-xslt-lib=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/lib --with-zlib-dir=C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}"' >> .bundle/config 2>&1 || true
85
95
  shell: bash
86
96
 
87
97
  - name: Set up Ruby ${{ matrix.ruby-version }}
@@ -90,6 +100,18 @@ jobs:
90
100
  ruby-version: ${{ matrix.ruby-version }}
91
101
  bundler-cache: true
92
102
 
103
+ # The directory of ruby.exe is always in the DLL search path (unlike
104
+ # PATH manipulation across step boundaries), so stage the vcpkg DLLs
105
+ # there for the system-built nokogiri on Windows ARM.
106
+ - name: Stage vcpkg DLLs beside ruby.exe (Windows)
107
+ if: startsWith(matrix.platform, 'windows-')
108
+ run: |
109
+ RUBY_BIN="$(dirname "$(command -v ruby)")"
110
+ echo "Staging vcpkg DLLs into $RUBY_BIN"
111
+ ls "C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/bin/" || true
112
+ cp -f "C:/vcpkg/installed/${{ steps.windows-arch.outputs.arch }}/bin/"*.dll "$RUBY_BIN/" 2>&1 || true
113
+ shell: bash
114
+
93
115
  - name: Install system dependencies (Ubuntu)
94
116
  if: startsWith(matrix.platform, 'ubuntu-')
95
117
  run: |
@@ -116,6 +138,28 @@ jobs:
116
138
  - name: Install gems
117
139
  run: bundle install
118
140
 
141
+ # ubuntu-22.04's glibc (2.35) predates the precompiled leptris gem's
142
+ # build host, so the gem's vendored libleptris.so fails to dlopen there
143
+ # and the adapter reports leptris unavailable (empty availability dots
144
+ # on the site). Leptris is pure C99 — build the lib from the gem's
145
+ # tagged source and point the FFI loader at it; LEPTRIS_LIB_PATH is the
146
+ # first candidate in the gem's ffi_lib list.
147
+ - name: Build libleptris from source (Ubuntu 22.04)
148
+ if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm'
149
+ run: |
150
+ LEPTRIS_VERSION=$(bundle exec ruby -e "require 'leptris'; print Leptris::VERSION")
151
+ echo "Building libleptris ${LEPTRIS_VERSION}"
152
+ curl -sL "https://github.com/leptris/leptris/archive/refs/tags/v${LEPTRIS_VERSION}.tar.gz" | tar xz
153
+ cmake -S "leptris-${LEPTRIS_VERSION}" -B /tmp/leptris-build \
154
+ -DLEPTRIS_BUILD_SHARED=ON -DLEPTRIS_BUILD_STATIC=OFF -DBUILD_TESTING=OFF \
155
+ -DLEPTRIS_BUILD_CLI=OFF -DLEPTRIS_BUILD_BENCHMARKS=OFF -DLEPTRIS_BUILD_MAN_PAGES=OFF \
156
+ -DLEPTRIS_ENABLE_UTF8PROC=OFF -DLEPTRIS_ENABLE_ICONV=OFF
157
+ cmake --build /tmp/leptris-build -j"$(nproc)"
158
+ LIBLEPTRIS=$(find /tmp/leptris-build -name 'libleptris.so*' -type f | head -1)
159
+ echo "Built ${LIBLEPTRIS}"
160
+ echo "LEPTRIS_LIB_PATH=${LIBLEPTRIS}" >> "$GITHUB_ENV"
161
+ shell: bash
162
+
119
163
  - name: Update Ruby-Build cache
120
164
  run: bundle exec serialbench ruby-build update
121
165
  env:
@@ -138,7 +182,7 @@ jobs:
138
182
  env:
139
183
  GITHUB_RUNNER_PLATFORM: ${{ matrix.platform }}
140
184
  run: |
141
- bundle exec serialbench environment execute config/environments/ci-ruby-${{ matrix.ruby-version }}.yml config/benchmarks/short.yml results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}
185
+ bundle exec serialbench environment execute config/environments/ci-ruby-${{ matrix.ruby-version }}.yml config/benchmarks/full.yml results/runs/ci-ruby-${{ matrix.ruby-version }}-${{ matrix.platform }}
142
186
 
143
187
  - name: Verify benchmark results before upload
144
188
  run: |
@@ -230,47 +274,22 @@ jobs:
230
274
  run: |
231
275
  bundle exec serialbench resultset add-result results/sets/weekly-benchmark artifacts/benchmark-results-*/
232
276
 
233
- - name: Generate GitHub Pages site
234
- run: |
235
- bundle exec serialbench resultset build-site results/sets/weekly-benchmark _site
236
-
237
- - name: Create summary page
277
+ - name: Export dashboard payload and raw data
238
278
  run: |
239
- mkdir -p _site
240
- cat > _site/README.md << 'EOF'
241
- # Serialbench - Weekly Benchmark Results
242
-
243
- This site contains automated benchmark results for Ruby serialization libraries.
244
-
245
- ## Benchmarked Libraries
246
-
247
- - **XML**: REXML, Ox, Nokogiri, Oga, LibXML
248
- - **JSON**: JSON, Oj, RapidJSON, YAJL
249
- - **YAML**: Psych
250
- - **TOML**: TOML-RB, Tomlib, tomlrb
279
+ bundle exec serialbench resultset export-data results/sets/weekly-benchmark site/src/data/sample.json --raw-data-dir site/public/data
251
280
 
252
- ## Platforms Tested
253
-
254
- - Ubuntu Latest (Linux x86_64)
255
- - Ubuntu 24.04 (Linux ARM64)
256
- - Ubuntu 22.04 (Linux ARM64)
257
- - macOS 13 (Intel x86_64)
258
- - macOS 15 Intel (Intel x86_64)
259
- - macOS 14 (Apple Silicon ARM64)
260
- - macOS 15 (Apple Silicon ARM64)
261
-
262
- ## Ruby Versions
263
-
264
- - Ruby 3.1, 3.2, 3.3, 3.4
265
-
266
- ## View Results
267
-
268
- Open [index.html](./index.html) to view the interactive dashboard.
269
-
270
- ---
281
+ - name: Set up Node
282
+ uses: actions/setup-node@v4
283
+ with:
284
+ node-version: '24'
285
+ cache: npm
286
+ cache-dependency-path: site/package-lock.json
271
287
 
272
- *Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
273
- EOF
288
+ - name: Build site
289
+ run: |
290
+ cd site
291
+ npm ci
292
+ npm run build
274
293
 
275
294
  - name: Setup Pages
276
295
  uses: actions/configure-pages@v4
@@ -278,7 +297,7 @@ jobs:
278
297
  - name: Upload to GitHub Pages
279
298
  uses: actions/upload-pages-artifact@v3
280
299
  with:
281
- path: _site
300
+ path: site/dist
282
301
 
283
302
  - name: Deploy to GitHub Pages
284
303
  id: deployment
@@ -301,16 +320,17 @@ jobs:
301
320
  echo "- Ubuntu Latest (Linux x86_64)" >> $GITHUB_STEP_SUMMARY
302
321
  echo "- Ubuntu 24.04 (Linux ARM64)" >> $GITHUB_STEP_SUMMARY
303
322
  echo "- Ubuntu 22.04 (Linux ARM64)" >> $GITHUB_STEP_SUMMARY
304
- echo "- macOS 13 (Intel x86_64)" >> $GITHUB_STEP_SUMMARY
305
- echo "- macOS 15 Intel (Intel x86_64)" >> $GITHUB_STEP_SUMMARY
306
323
  echo "- macOS 14 (Apple Silicon ARM64)" >> $GITHUB_STEP_SUMMARY
307
324
  echo "- macOS 15 (Apple Silicon ARM64)" >> $GITHUB_STEP_SUMMARY
325
+ echo "- macOS 15 Intel (Intel x86_64)" >> $GITHUB_STEP_SUMMARY
326
+ echo "- macOS 26 (Apple Silicon ARM64)" >> $GITHUB_STEP_SUMMARY
327
+ echo "- macOS 26 Intel (Intel x86_64)" >> $GITHUB_STEP_SUMMARY
308
328
  echo "" >> $GITHUB_STEP_SUMMARY
309
329
  echo "### Ruby Versions" >> $GITHUB_STEP_SUMMARY
310
- echo "- Ruby 3.1, 3.2, 3.3, 3.4" >> $GITHUB_STEP_SUMMARY
330
+ echo "- Ruby 3.2, 3.3, 3.4, 4.0" >> $GITHUB_STEP_SUMMARY
311
331
  echo "" >> $GITHUB_STEP_SUMMARY
312
332
  echo "### Serialization Libraries" >> $GITHUB_STEP_SUMMARY
313
- echo "- **XML**: REXML, Ox, Nokogiri, Oga, LibXML" >> $GITHUB_STEP_SUMMARY
333
+ echo "- **XML**: REXML, Ox, Nokogiri, Oga, LibXML, Leptris" >> $GITHUB_STEP_SUMMARY
314
334
  echo "- **JSON**: JSON, Oj, RapidJSON, YAJL" >> $GITHUB_STEP_SUMMARY
315
335
  echo "- **YAML**: Psych" >> $GITHUB_STEP_SUMMARY
316
336
  echo "- **TOML**: TOML-RB, Tomlib, tomlrb" >> $GITHUB_STEP_SUMMARY
data/Gemfile CHANGED
@@ -12,6 +12,8 @@ unless Gem.win_platform? && RUBY_PLATFORM.include?('aarch64')
12
12
  gem 'libxml-ruby'
13
13
  end
14
14
 
15
+ gem 'leptris' # Needs the libleptris shared library (LEPTRIS_LIB_PATH or system install)
16
+ gem 'benchmark' # Removed from stdlib in Ruby 4.0
15
17
  gem 'base64' # Required for Ruby 3.4+
16
18
  gem 'lutaml-model', '~> 0.7'
17
19
  gem 'octokit'
data/README.adoc CHANGED
@@ -41,6 +41,11 @@ Serialbench is a comprehensive benchmarking suite that evaluates the performance
41
41
  | v3.4
42
42
  | Pure Ruby XML parser with XPath support
43
43
 
44
+ | XML
45
+ | https://github.com/leptris/leptris-ruby[Leptris]
46
+ | v1.1.0
47
+ | FFI binding to libleptris (pure-C XML 1.0, XPath 1.0, SAX)
48
+
44
49
  | XML
45
50
  | https://github.com/ruby/rexml[REXML]
46
51
  | v3.4.1
@@ -213,6 +213,46 @@ module Serialbench
213
213
  exit 1
214
214
  end
215
215
 
216
+ desc 'export-data RESULTSET_PATH OUTPUT_JSON', 'Export a resultset as the site dashboard JSON payload'
217
+ long_desc <<~DESC
218
+ Write the aggregated dashboard payload (combined_results, environments,
219
+ metadata) for a resultset as JSON. This is the data contract consumed
220
+ by the Astro site in site/.
221
+
222
+ Optionally also export the raw per-environment YAML files and the
223
+ complete resultset YAML for download links.
224
+
225
+ Examples:
226
+ serialbench resultset export-data results/sets/weekly site/src/data/sample.json --raw-data-dir site/public/data
227
+ DESC
228
+ method_option 'raw-data-dir', type: :string,
229
+ desc: 'Also write per-environment YAML and resultset.yaml to this directory'
230
+ def export_data(resultset_path, output_json)
231
+ resultset = Serialbench::Models::ResultSet.load(resultset_path)
232
+
233
+ if resultset.results.empty?
234
+ say "ResultSet '#{resultset_path}' contains no runs", :yellow
235
+ say "Use 'serialbench resultset add-result' to add runs first", :white
236
+ exit 1
237
+ end
238
+
239
+ payload = Serialbench::SiteGenerator.resultset_payload(resultset)
240
+ output_dir = File.dirname(File.expand_path(output_json))
241
+ FileUtils.mkdir_p(output_dir)
242
+ File.write(output_json, JSON.pretty_generate(payload))
243
+
244
+ say "✅ Dashboard payload exported: #{output_json}", :green
245
+ say "Environments: #{payload['environments'].size}", :cyan
246
+
247
+ if options['raw-data-dir']
248
+ Serialbench::SiteGenerator.export_raw_data(resultset, options['raw-data-dir'])
249
+ say "📦 Raw YAML exported to: #{options['raw-data-dir']}", :cyan
250
+ end
251
+ rescue StandardError => e
252
+ say "Error exporting data: #{e.message}", :red
253
+ exit 1
254
+ end
255
+
216
256
  desc 'list', 'List all available resultsets'
217
257
  long_desc <<~DESC
218
258
  List all resultsets in the results/sets/ directory.
@@ -15,8 +15,11 @@ module Serialbench
15
15
  class Platform < Lutaml::Model::Serializable
16
16
  attribute :platform_string, :string
17
17
  attribute :kind, :string, default: -> { 'local' }
18
- attribute :os, :string, default: -> { detect_os }
19
- attribute :arch, :string, default: -> { detect_arch }
18
+ # Fully qualified: lutaml-model 0.8+ instance-evals default
19
+ # lambdas, so bare detect_os/detect_arch (class methods) no
20
+ # longer resolve there.
21
+ attribute :os, :string, default: -> { Serialbench::Models::Platform.detect_os }
22
+ attribute :arch, :string, default: -> { Serialbench::Models::Platform.detect_arch }
20
23
  attribute :ruby_version, :string, default: -> { RUBY_VERSION }
21
24
  attribute :ruby_platform, :string, default: -> { RUBY_PLATFORM }
22
25
  attribute :ruby_build_tag, :string
@@ -32,10 +32,6 @@ module Serialbench
32
32
  RapidJSON.dump(object)
33
33
  end
34
34
 
35
- def features
36
- %w[parsing generation high-performance c-extension]
37
- end
38
-
39
35
  private
40
36
 
41
37
  def require_library(library_name)
@@ -29,10 +29,15 @@ module Serialbench
29
29
  xpath: supports_xpath?,
30
30
  namespaces: supports_namespaces?,
31
31
  validation: supports_validation?,
32
- streaming: supports_streaming?
32
+ streaming: supports_streaming?,
33
+ stax: supports_stax?
33
34
  }
34
35
  end
35
36
 
37
+ def supports_stax?
38
+ false
39
+ end
40
+
36
41
  def supports_generation?
37
42
  true
38
43
  end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_xml_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Xml
8
+ class LeptrisSerializer < BaseXmlSerializer
9
+ def name
10
+ 'leptris'
11
+ end
12
+
13
+ def parse(xml_string)
14
+ require 'leptris'
15
+ Leptris::XML.parse(xml_string)
16
+ end
17
+
18
+ def generate(data)
19
+ require 'leptris'
20
+ if data.is_a?(Leptris::XML::Document) || data.is_a?(Leptris::XML::Node)
21
+ data.to_xml(indent: 2)
22
+ else
23
+ build_document_from_data(data).to_xml(indent: 2)
24
+ end
25
+ end
26
+
27
+ def parse_streaming(xml_string, &block)
28
+ require 'leptris'
29
+
30
+ handler = StreamingHandler.new(&block)
31
+ parser = Leptris::XML::SAX::Parser.new(handler)
32
+ parser.parse_memory(xml_string)
33
+ handler.elements_processed
34
+ end
35
+
36
+ def supports_streaming?
37
+ true
38
+ end
39
+
40
+ def supports_xpath?
41
+ true
42
+ end
43
+
44
+ # leptris-ruby >= 1.6 exposes Leptris::XML::Pull, a StAX-style
45
+ # cursor (one event per #next). Declared by gem version so the
46
+ # check never triggers the FFI library load.
47
+ def supports_stax?
48
+ spec = Gem.loaded_specs['leptris'] || Gem::Specification.find_by_name('leptris')
49
+ !spec.nil? && spec.version >= Gem::Version.new('1.6.0')
50
+ rescue Gem::LoadError
51
+ false
52
+ end
53
+
54
+ def version
55
+ return 'unknown' unless available?
56
+
57
+ require 'leptris'
58
+ Leptris::VERSION
59
+ end
60
+
61
+ def library_require_name
62
+ 'leptris'
63
+ end
64
+
65
+ # The gem's require succeeds even when the libtaurus shared library is
66
+ # missing -- the FFI load happens lazily at first use. Probe with a
67
+ # real parse so "available" means "actually usable".
68
+ def available?
69
+ return @available if defined?(@available)
70
+
71
+ @available = begin
72
+ require 'leptris'
73
+ Leptris::XML.parse('<probe/>')
74
+ true
75
+ rescue StandardError, LoadError
76
+ false
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def build_document_from_data(data, root_name = 'root')
83
+ document = Leptris::XML.parse('<root/>')
84
+ document.root.name = sanitize_element_name(root_name)
85
+ append_data(document.root, data)
86
+ document
87
+ end
88
+
89
+ def append_data(element, data)
90
+ case data
91
+ when Hash
92
+ data.each do |key, value|
93
+ child = element.document.create_element(sanitize_element_name(key.to_s))
94
+ element.add_child(child)
95
+ append_data(child, value)
96
+ end
97
+ when Array
98
+ data.each do |item|
99
+ child = element.document.create_element('item')
100
+ element.add_child(child)
101
+ append_data(child, item)
102
+ end
103
+ else
104
+ element.content = data.to_s
105
+ end
106
+ end
107
+
108
+ def sanitize_element_name(name)
109
+ sanitized = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
110
+ sanitized = "element_#{sanitized}" if sanitized.empty? || sanitized =~ /\A\d/
111
+ sanitized
112
+ end
113
+
114
+ # SAX handler counting elements, mirroring the Nokogiri adapter's
115
+ # StreamingHandler shape (Leptris SAX delivers attrs as [name, value]
116
+ # pairs, same as Nokogiri::XML::SAX). Plain duck-typed class --
117
+ # Leptris::XML::SAX::Parser needs no base class, and subclassing it
118
+ # would trigger the FFI library load at file-load time.
119
+ class StreamingHandler
120
+ attr_reader :elements_processed
121
+
122
+ def initialize(&block)
123
+ @block = block
124
+ @elements_processed = 0
125
+ @element_stack = []
126
+ end
127
+
128
+ # No-op callbacks the parser invokes unconditionally (the base
129
+ # Leptris::XML::SAX::Document normally provides these).
130
+ def start_document; end
131
+ def end_document; end
132
+ def xmldecl(_version, _encoding, _standalone); end
133
+ def comment(_string); end
134
+ def processing_instruction(_name, _content); end
135
+ def start_prefix_mapping(_prefix, _uri); end
136
+ def end_prefix_mapping(_prefix); end
137
+ def warning(_string); end
138
+ def error(_message, _line = 0, _column = 0); end
139
+
140
+ def start_element(name, attrs = [])
141
+ @elements_processed += 1
142
+ @element_stack.push({ name: name, attributes: Hash[attrs], children: [], text: '' })
143
+ end
144
+
145
+ def end_element(_name)
146
+ element = @element_stack.pop
147
+ if @element_stack.empty?
148
+ @block&.call(element)
149
+ else
150
+ @element_stack.last[:children] << element
151
+ end
152
+ end
153
+
154
+ def characters(string)
155
+ return if string.strip.empty?
156
+
157
+ @element_stack.last[:text] += string if @element_stack.any?
158
+ end
159
+
160
+ def cdata_block(string)
161
+ @element_stack.last[:text] += string if @element_stack.any?
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -38,6 +38,15 @@ module Serialbench
38
38
  handler.elements_processed
39
39
  end
40
40
 
41
+ def supports_xpath?
42
+ true
43
+ end
44
+
45
+ # The binding exposes a pull-parser cursor (XML::Reader)
46
+ def supports_stax?
47
+ true
48
+ end
49
+
41
50
  def supports_streaming?
42
51
  true
43
52
  end
@@ -38,6 +38,15 @@ module Serialbench
38
38
  handler.elements_processed
39
39
  end
40
40
 
41
+ def supports_xpath?
42
+ true
43
+ end
44
+
45
+ # The binding exposes a pull-parser cursor (XML::Reader)
46
+ def supports_stax?
47
+ true
48
+ end
49
+
41
50
  def supports_streaming?
42
51
  true
43
52
  end
@@ -38,6 +38,10 @@ module Serialbench
38
38
  handler.elements_processed
39
39
  end
40
40
 
41
+ def supports_xpath?
42
+ true
43
+ end
44
+
41
45
  def supports_streaming?
42
46
  true
43
47
  end
@@ -16,9 +16,7 @@ module Serialbench
16
16
  end
17
17
 
18
18
  def features
19
- features = %w[parsing generation]
20
- features << 'streaming' if supports_streaming?
21
- features
19
+ { streaming: supports_streaming? }
22
20
  end
23
21
 
24
22
  # Default YAML generation options
@@ -36,10 +36,6 @@ module Serialbench
36
36
  Psych.dump(object)
37
37
  end
38
38
 
39
- def features
40
- %w[parsing generation built-in]
41
- end
42
-
43
39
  private
44
40
 
45
41
  def require_library(library_name)
@@ -10,6 +10,7 @@ require_relative 'serializers/xml/ox_serializer'
10
10
  require_relative 'serializers/xml/nokogiri_serializer'
11
11
  require_relative 'serializers/xml/oga_serializer'
12
12
  require_relative 'serializers/xml/libxml_serializer'
13
+ require_relative 'serializers/xml/leptris_serializer'
13
14
 
14
15
  # JSON Serializers
15
16
  require_relative 'serializers/json/base_json_serializer'
@@ -38,7 +39,8 @@ module Serialbench
38
39
  Xml::OxSerializer,
39
40
  Xml::NokogiriSerializer,
40
41
  Xml::OgaSerializer,
41
- Xml::LibxmlSerializer
42
+ Xml::LibxmlSerializer,
43
+ Xml::LeptrisSerializer
42
44
  ],
43
45
  json: [
44
46
  Json::JsonSerializer,