rbxl 1.0.0 → 1.0.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: 7dfc04eae51753bfa17b28f87476e1da9904efd4748a6a503ef999b58316d419
4
- data.tar.gz: dcad9a70d574b225be56d5c942995c5634ef06ac2a882ff030a69d9d1fde1ecb
3
+ metadata.gz: e9bedc3242085871b368d031e7791aeb925d8d2a53329aebaf1776a0a0d273eb
4
+ data.tar.gz: e4d6594b3c7d19b63f429b5cb5680df1d4e6e762dd86c2ab33499c97a5389918
5
5
  SHA512:
6
- metadata.gz: c0c65e0501a613c690795274aee90ee71b26332e916f416e62f7df398d610a67992e230651ffec87a9017243adfb56c517da6498d076e54ed11fe61a8f6dc74d
7
- data.tar.gz: fd952f51da370eb1a9a433d661f0c6018a7460e43560b72eb9682cf938a833edda949774ebfcd942add2eabea1d87e37199d993f0b5172153710b0f264d890a9
6
+ metadata.gz: fac56fdc22b72ff9bf75c3273e8e9a61fbab953c3ddb280618522c121a71a8d530f09792d4c80682b94309b7042604af42b5bfeeab41420e67611cb57d0a57de
7
+ data.tar.gz: 72f58522b5d7d9a0e1ca16e153a578871e8a2355e12a9066c7f1b1ec1026c72124bdbd7ec72c9e293caf74dfcff0e21386c09db1155c7d2c7549e04ef93abdec
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1
4
+
5
+ - Fix ZIP64 handling.
6
+ - Add Go and Rust benchmark comparisons.
7
+ - Align `rbxl/native` with Nokogiri's libxml2 to avoid mixed-library warnings at runtime.
8
+
9
+ ## 1.0.0
10
+
11
+ - Initial 1.0 release.
data/README.md CHANGED
@@ -98,6 +98,7 @@ Requirements for the C extension:
98
98
 
99
99
  ```bash
100
100
  bundle install
101
+ cd benchmark && npm install && cd ..
101
102
 
102
103
  # Run tests (pure Ruby)
103
104
  ruby -Ilib -Itest test/rbxl_test.rb
@@ -115,28 +116,42 @@ RBXL_BENCH_WARMUP=1 RBXL_BENCH_ITERATIONS=5 ruby -Ilib benchmark/read_modes.rb
115
116
 
116
117
  ## Benchmarks
117
118
 
118
- 5000 rows x 10 columns, Ruby 3.4 / Python 3.13:
119
+ The performance story is primarily about `rbxl/native`.
120
+
121
+ `require "rbxl"` remains the portability-first default: no native extension is
122
+ required, the API stays the same, and the fallback path is still useful for
123
+ environments where native builds are inconvenient. But the numbers below are
124
+ best read as:
125
+
126
+ - `rbxl` = portable baseline
127
+ - `rbxl/native` = performance mode
128
+
129
+ 5000 rows x 10 columns, Ruby 3.4 / Python 3.13 / Node 24:
119
130
 
120
131
  ![Benchmark chart](benchmark/chart.png)
121
132
 
122
- ### Pure Ruby (Nokogiri Reader)
133
+ ### Portable Baseline (`require "rbxl"`)
123
134
 
124
135
  | benchmark | real (s) |
125
136
  |---|---|
126
- | rbxl write | 0.09 |
127
- | rbxl read | 0.30 |
128
- | rbxl read values | 0.22 |
129
- | openpyxl write | 0.36 |
130
- | openpyxl read | 0.28 |
131
- | openpyxl read values | 0.26 |
132
-
133
- ### With `rbxl/native`
134
-
135
- | benchmark | real (s) | vs openpyxl |
137
+ | rbxl write | 0.08 |
138
+ | rbxl read | 0.33 |
139
+ | rbxl read values | 0.23 |
140
+ | exceljs write | 0.08 |
141
+ | exceljs read | 0.17 |
142
+ | sheetjs write | 0.13 |
143
+ | sheetjs read | 0.19 |
144
+ | openpyxl write | 0.35 |
145
+ | openpyxl read | 0.22 |
146
+ | openpyxl read values | 0.18 |
147
+
148
+ ### Performance Mode (`require "rbxl/native"`)
149
+
150
+ | benchmark | real (s) | vs exceljs/openpyxl |
136
151
  |---|---|---|
137
- | rbxl write | **0.04** | 9x faster |
138
- | rbxl read | **0.08** | 3.5x faster |
139
- | rbxl read values | **0.03** | 9x faster |
152
+ | rbxl write | **0.04** | about 2x / 9x faster |
153
+ | rbxl read | **0.07** | about 2.6x / 3.2x faster |
154
+ | rbxl read values | **0.03** | about 6.8x faster than openpyxl values |
140
155
 
141
156
  The comparison script uses these libraries when available:
142
157
 
@@ -144,11 +159,16 @@ Benchmark notes:
144
159
 
145
160
  - `RBXL_BENCH_WARMUP` and `RBXL_BENCH_ITERATIONS` control warmup and repeated runs.
146
161
  - Read comparisons use the same `rbxl.xlsx` fixture for `rbxl`, `roo`, `rubyXL`, and `openpyxl`.
162
+ - JS comparisons use the same `rbxl.xlsx` fixture for `exceljs` and `sheetjs`.
147
163
  - Write comparisons still measure each library producing its own workbook.
148
164
  - `rss_delta_kb` is best-effort process RSS on Linux and should be treated as directional.
165
+ - Install JS benchmark dependencies with `cd benchmark && npm install`.
149
166
 
150
167
  - `rbxl` for write/read
151
- - `caxlsx` for write
152
- - `roo` for read streaming
168
+ - `exceljs` for write/read
169
+ - `sheetjs` for write/read
170
+ - `excelize` (Go) for write/read
171
+ - `rust_xlsxwriter` (Rust) for write
172
+ - `calamine` (Rust) for read
153
173
  - `rubyXL` for full workbook read
154
174
  - `openpyxl` as a Python reference point when `openpyxl` or `uv` is available
@@ -1,48 +1,37 @@
1
1
  require "mkmf"
2
2
 
3
- # Try to find libxml2 headers and library.
4
- # Priority:
5
- # 1. Nokogiri's bundled libxml2 (avoids version mismatch warnings)
6
- # 2. System pkg-config
7
- # 3. Common system paths
8
- #
9
- # If libxml2 is not available at all, skip compilation gracefully so
10
- # that `gem install rbxl` never fails — the C extension is optional.
11
-
12
- found = false
13
-
14
- # 1. Try Nokogiri's bundled libxml2
3
+ # The extension is intentionally built against Nokogiri's vendored libxml2.
4
+ # We only borrow Nokogiri's headers at build time and rely on Nokogiri's
5
+ # extension to export the libxml2 symbols at runtime. Linking against the
6
+ # system libxml2 here would reintroduce mixed-version warnings and can lead
7
+ # to process instability.
8
+
15
9
  begin
16
- nokogiri_spec = Gem::Specification.find_by_name("nokogiri")
17
- nokogiri_include = File.join(nokogiri_spec.full_gem_path, "ext", "nokogiri", "include", "libxml2")
18
- nokogiri_lib = File.join(nokogiri_spec.full_gem_path, "ext", "nokogiri")
19
-
20
- if File.directory?(nokogiri_include) && find_header("libxml/parser.h", nokogiri_include)
21
- # Link against Nokogiri's bundled libxml2
22
- nokogiri_so = Dir.glob(File.join(nokogiri_lib, "**", "nokogiri.{so,bundle}")).first
23
- if nokogiri_so
24
- so_dir = File.dirname(nokogiri_so)
25
- $LDFLAGS << " -L#{so_dir} -Wl,-rpath,#{so_dir}"
26
- end
27
- found = have_library("xml2") || true # headers found via Nokogiri, may link at runtime
28
- end
29
- rescue Gem::MissingSpecError
30
- # Nokogiri not installed — fall through
10
+ require "nokogiri"
11
+ rescue LoadError
12
+ warn "rbxl_native: nokogiri is required to build the C extension"
13
+ File.write("Makefile", "all install clean:\n\t@:\n")
14
+ exit 0
31
15
  end
32
16
 
33
- # 2. System pkg-config
34
- found ||= pkg_config("libxml-2.0")
17
+ nokogiri_cppflags = Array(Nokogiri::VERSION_INFO.dig("nokogiri", "cppflags"))
18
+ nokogiri_ldflags = Array(Nokogiri::VERSION_INFO.dig("nokogiri", "ldflags"))
35
19
 
36
- # 3. Common system paths
37
- found ||= (have_header("libxml/parser.h") && have_library("xml2"))
38
- found ||= (find_header("libxml/parser.h", "/usr/include/libxml2") && have_library("xml2"))
20
+ $CPPFLAGS = [*nokogiri_cppflags, $CPPFLAGS].reject(&:empty?).join(" ")
21
+ $LDFLAGS = [*nokogiri_ldflags, $LDFLAGS].reject(&:empty?).join(" ")
39
22
 
40
- unless found
41
- warn "rbxl_native: libxml2 not found skipping C extension build"
23
+ unless have_header("libxml/parser.h")
24
+ warn "rbxl_native: failed to find Nokogiri libxml2 headers"
42
25
  File.write("Makefile", "all install clean:\n\t@:\n")
43
26
  exit 0
44
27
  end
45
28
 
29
+ # macOS refuses unresolved references in shared objects unless explicitly told
30
+ # to leave them for runtime lookup in already-loaded extensions like Nokogiri.
31
+ if RUBY_PLATFORM.include?("darwin")
32
+ append_ldflags("-Wl,-undefined,dynamic_lookup")
33
+ end
34
+
46
35
  # Hardening flags
47
36
  $CFLAGS << " -Wall -Wextra -Werror=format-security"
48
37
  $CFLAGS << " -D_FORTIFY_SOURCE=2" unless $CFLAGS.include?("_FORTIFY_SOURCE")
data/lib/rbxl/native.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require "nokogiri"
2
+
1
3
  begin
2
4
  require "rbxl_native/rbxl_native"
3
5
  rescue LoadError
data/lib/rbxl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rbxl
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -20,16 +20,23 @@ module Rbxl
20
20
  ensure_writable!
21
21
  raise Error, "at least one worksheet is required" if worksheets.empty?
22
22
 
23
- Zip::OutputStream.open(path) do |zip|
24
- write_entry(zip, "[Content_Types].xml", content_types_xml)
25
- write_entry(zip, "_rels/.rels", root_rels_xml)
26
- write_entry(zip, "xl/workbook.xml", workbook_xml)
27
- write_entry(zip, "xl/_rels/workbook.xml.rels", workbook_rels_xml)
28
- write_entry(zip, "xl/styles.xml", styles_xml)
29
-
30
- worksheets.each_with_index do |sheet, index|
31
- write_entry(zip, "xl/worksheets/sheet#{index + 1}.xml", sheet.to_xml)
23
+ previous_zip64 = Zip.write_zip64_support
24
+ begin
25
+ Zip.write_zip64_support = false
26
+
27
+ Zip::OutputStream.open(path) do |zip|
28
+ write_entry(zip, "[Content_Types].xml", content_types_xml)
29
+ write_entry(zip, "_rels/.rels", root_rels_xml)
30
+ write_entry(zip, "xl/workbook.xml", workbook_xml)
31
+ write_entry(zip, "xl/_rels/workbook.xml.rels", workbook_rels_xml)
32
+ write_entry(zip, "xl/styles.xml", styles_xml)
33
+
34
+ worksheets.each_with_index do |sheet, index|
35
+ write_entry(zip, "xl/worksheets/sheet#{index + 1}.xml", sheet.to_xml)
36
+ end
32
37
  end
38
+ ensure
39
+ Zip.write_zip64_support = previous_zip64
33
40
  end
34
41
 
35
42
  @saved = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbxl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taro KOBAYASHI
@@ -51,6 +51,7 @@ extensions:
51
51
  - ext/rbxl_native/extconf.rb
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - CHANGELOG.md
54
55
  - LICENSE.txt
55
56
  - README.md
56
57
  - Rakefile