xlsxrb 0.1.12 → 0.1.13

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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -42
  3. data/README.md +26 -22
  4. data/lib/xlsxrb/dsl_helpers.rb +59 -0
  5. data/lib/xlsxrb/elements/workbook.rb +11 -0
  6. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +3 -5
  7. data/lib/xlsxrb/ooxml/workbook_writer.rb +5 -7
  8. data/lib/xlsxrb/ooxml/worksheet_parser.rb +172 -9
  9. data/lib/xlsxrb/ooxml/writer.rb +2 -1
  10. data/lib/xlsxrb/ooxml/xml_builder.rb +20 -0
  11. data/lib/xlsxrb/ooxml/zip_reader.rb +364 -78
  12. data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
  13. data/lib/xlsxrb/stream_sheet.rb +94 -6
  14. data/lib/xlsxrb/stream_writer.rb +33 -66
  15. data/lib/xlsxrb/version.rb +1 -1
  16. data/lib/xlsxrb/workbook_builder.rb +1 -1
  17. data/lib/xlsxrb/worksheet_builder.rb +3 -31
  18. data/lib/xlsxrb.rb +448 -171
  19. data/sig/generated/xlsxrb/dsl_helpers.rbs +32 -0
  20. data/sig/generated/xlsxrb/elements/workbook.rbs +7 -0
  21. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +0 -2
  22. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +5 -4
  23. data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +11 -0
  24. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +96 -8
  25. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
  26. data/sig/generated/xlsxrb/stream_sheet.rbs +43 -3
  27. data/sig/generated/xlsxrb.rbs +18 -11
  28. metadata +6 -146
  29. data/.devcontainer/Dockerfile +0 -65
  30. data/.devcontainer/devcontainer.json +0 -17
  31. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
  32. data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
  33. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +0 -9
  34. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +0 -4
  35. data/.gem_rbs_collection/concurrent-ruby/1.1/atomic_reference.rbs +0 -16
  36. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +0 -96
  37. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +0 -4
  38. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +0 -68
  39. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +0 -249
  40. data/.gem_rbs_collection/concurrent-ruby/1.1/set.rbs +0 -4
  41. data/.gem_rbs_collection/concurrent-ruby/1.1/timer_task.rbs +0 -47
  42. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +0 -5
  43. data/.gem_rbs_collection/csv/3.3/.rbs_meta.yaml +0 -9
  44. data/.gem_rbs_collection/csv/3.3/csv.rbs +0 -3871
  45. data/.gem_rbs_collection/csv/3.3/manifest.yaml +0 -3
  46. data/.gem_rbs_collection/lint_roller/1.1/.rbs_meta.yaml +0 -9
  47. data/.gem_rbs_collection/lint_roller/1.1/lint_roller.rbs +0 -48
  48. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +0 -9
  49. data/.gem_rbs_collection/listen/3.9/listen.rbs +0 -25
  50. data/.gem_rbs_collection/listen/3.9/listener.rbs +0 -24
  51. data/.gem_rbs_collection/logger/1.7/.rbs_meta.yaml +0 -9
  52. data/.gem_rbs_collection/logger/1.7/formatter.rbs +0 -45
  53. data/.gem_rbs_collection/logger/1.7/log_device.rbs +0 -100
  54. data/.gem_rbs_collection/logger/1.7/logger.rbs +0 -796
  55. data/.gem_rbs_collection/logger/1.7/manifest.yaml +0 -2
  56. data/.gem_rbs_collection/logger/1.7/period.rbs +0 -17
  57. data/.gem_rbs_collection/logger/1.7/severity.rbs +0 -34
  58. data/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +0 -9
  59. data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +0 -2332
  60. data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +0 -4
  61. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
  62. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
  63. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
  64. data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
  65. data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -194
  66. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
  67. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
  68. data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
  69. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
  70. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
  71. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
  72. data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
  73. data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
  74. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
  75. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
  76. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
  77. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -208
  78. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +0 -9
  79. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +0 -903
  80. data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +0 -9
  81. data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +0 -8
  82. data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +0 -42
  83. data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +0 -5
  84. data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +0 -47
  85. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +0 -30
  86. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +0 -9
  87. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +0 -7
  88. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +0 -19
  89. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +0 -31
  90. data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +0 -18
  91. data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +0 -12
  92. data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +0 -11
  93. data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +0 -13
  94. data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +0 -95
  95. data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +0 -31
  96. data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +0 -58
  97. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +0 -24
  98. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +0 -17
  99. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +0 -23
  100. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +0 -22
  101. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +0 -30
  102. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +0 -20
  103. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +0 -15
  104. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +0 -26
  105. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +0 -21
  106. data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +0 -131
  107. data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +0 -14
  108. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +0 -33
  109. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +0 -21
  110. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +0 -63
  111. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +0 -55
  112. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +0 -35
  113. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +0 -7
  114. data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +0 -10
  115. data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +0 -22
  116. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +0 -29
  117. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +0 -17
  118. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +0 -13
  119. data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +0 -10
  120. data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +0 -8
  121. data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +0 -6
  122. data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +0 -30
  123. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +0 -10
  124. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +0 -10
  125. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +0 -5
  126. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +0 -15
  127. data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +0 -3
  128. data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +0 -40
  129. data/.mutant.yml +0 -8
  130. data/Rakefile +0 -633
  131. data/Steepfile +0 -18
  132. data/benchmark.rb +0 -406
  133. data/docs/ARCHITECTURE.md +0 -514
  134. data/docs/DEVELOPMENT.md +0 -137
  135. data/docs/MUTATION_TESTING.md +0 -52
  136. data/docs/PEER_LIBRARIES.md +0 -121
  137. data/docs/QUALITY_ASSURANCE.md +0 -26
  138. data/docs/SPEC_SOURCES.md +0 -48
  139. data/docs/assets/benchmark_results.svg +0 -144
  140. data/docs/assets/lsp_autocompletion.png +0 -0
  141. data/docs/assets/playground_preview.png +0 -0
  142. data/docs/coi-serviceworker.js +0 -82
  143. data/docs/office_thread.js +0 -77
  144. data/docs/preview.html +0 -719
  145. data/docs/visual/VisualGallery.md +0 -4683
  146. data/docs/wasm/ruby.wasm +0 -0
  147. data/docs/wasm/wasm_doc_helper.css +0 -351
  148. data/docs/wasm/wasm_doc_helper.js +0 -439
  149. data/docs/zeta.js +0 -1107
  150. data/rbs_collection.lock.yaml +0 -252
  151. data/rbs_collection.yaml +0 -19
  152. data/vendor/sdk_runner/Program.cs +0 -93
  153. data/vendor/sdk_runner/sdk_runner.csproj +0 -13
data/Rakefile DELETED
@@ -1,633 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
5
- require "etc"
6
- require "fileutils"
7
- require "open3"
8
- require "tmpdir"
9
-
10
- def dotnet_available?
11
- system("which dotnet > /dev/null 2>&1")
12
- end
13
-
14
- desc "Build the Open XML SDK runner"
15
- task :build_sdk_runner do
16
- if dotnet_available?
17
- sh "dotnet build vendor/sdk_runner/sdk_runner.csproj -c Release"
18
- elsif File.exist?(sdk_runner_dll)
19
- puts "dotnet not found in PATH, but pre-built sdk_runner.dll exists. Skipping build."
20
- else
21
- warn "dotnet command not found and sdk_runner.dll is missing. Cannot build SDK runner."
22
- end
23
- end
24
-
25
- def reader_fixture_dir
26
- File.expand_path("test/fixtures/reader_generated", __dir__)
27
- end
28
-
29
- def sdk_scenario_dir
30
- File.expand_path("test/fixtures/sdk_scenarios", __dir__)
31
- end
32
-
33
- def sdk_runner_dll
34
- File.expand_path("vendor/sdk_runner/bin/Release/net8.0/sdk_runner.dll", __dir__)
35
- end
36
-
37
- def reader_fixture_specs
38
- Dir.glob(File.join(sdk_scenario_dir, "reader_*_generated_by_sdk.cs")).map do |scenario_path|
39
- scenario_name = File.basename(scenario_path, ".cs")
40
- [scenario_name, File.join(reader_fixture_dir, "#{scenario_name}.xlsx")]
41
- end
42
- end
43
-
44
- def reader_fixture_workers
45
- Integer(ENV.fetch("READER_FIXTURE_WORKERS", Etc.nprocessors))
46
- rescue ArgumentError
47
- Etc.nprocessors
48
- end
49
-
50
- desc "Ensure SDK-generated reader fixtures exist"
51
- task :ensure_reader_fixtures do
52
- missing_specs = reader_fixture_specs.reject { |_scenario_name, fixture_path| File.exist?(fixture_path) }
53
- next if missing_specs.empty?
54
-
55
- Rake::Task[:build_sdk_runner].invoke
56
- raise "Cannot generate #{missing_specs.size} missing reader fixture(s) because dotnet is not installed." unless dotnet_available?
57
-
58
- FileUtils.mkdir_p(reader_fixture_dir)
59
-
60
- queue = Queue.new
61
- missing_specs.each { |spec| queue << spec }
62
- failures = Queue.new
63
-
64
- worker_count = [reader_fixture_workers, missing_specs.size].min
65
- threads = Array.new(worker_count) do
66
- Thread.new do
67
- loop do
68
- scenario_name, fixture_path = queue.pop(true)
69
- scenario_path = File.join(sdk_scenario_dir, "#{scenario_name}.cs")
70
- FileUtils.touch(fixture_path)
71
-
72
- stdout, stderr, status = Open3.capture3(
73
- "dotnet", sdk_runner_dll, scenario_path, fixture_path
74
- )
75
-
76
- next if status.success?
77
-
78
- FileUtils.rm_f(fixture_path)
79
- message = stderr.to_s.strip.empty? ? stdout : stderr
80
- failures << "Failed to generate reader fixture #{scenario_name}: #{message}"
81
- rescue ThreadError
82
- break
83
- end
84
- end
85
- end
86
-
87
- threads.each(&:join)
88
- next if failures.empty?
89
-
90
- raise failures.pop
91
- end
92
-
93
- Rake::TestTask.new(test: :ensure_reader_fixtures) do |t|
94
- t.libs << "test"
95
- t.libs << "lib"
96
- t.test_files = FileList["test/xlsxrb/**/*_test.rb", "test/*_test.rb", "test/contract/**/*_test.rb"]
97
- workers = ENV.fetch("TEST_WORKERS", [Etc.nprocessors, 4].min)
98
- t.options = "--parallel --n-workers=#{workers}"
99
- end
100
-
101
- namespace :test do
102
- desc "Run tests with runtime type checking enabled (RBS_TEST=1)"
103
- task :rbs do
104
- ENV["RBS_TEST"] = "1"
105
- Rake::Task["test:unit"].invoke
106
- Rake::Task["test:contract"].invoke
107
- end
108
- Rake::TestTask.new(:unit) do |t|
109
- t.libs << "test"
110
- t.libs << "lib"
111
- t.test_files = FileList["test/xlsxrb/**/*_test.rb", "test/*_test.rb"]
112
- workers = ENV.fetch("TEST_WORKERS", Etc.nprocessors)
113
- t.options = "--parallel --n-workers=#{workers}"
114
- end
115
-
116
- Rake::TestTask.new(:contract) do |t|
117
- t.libs << "test"
118
- t.libs << "lib"
119
- t.test_files = FileList["test/contract/**/*_test.rb"]
120
- workers = ENV.fetch("TEST_WORKERS", Etc.nprocessors)
121
- t.options = "--parallel --n-workers=#{workers}"
122
- end
123
-
124
- desc "Run property-based tests (PBT)"
125
- Rake::TestTask.new(:pbt) do |t|
126
- t.libs << "test"
127
- t.libs << "lib"
128
- t.test_files = FileList["test/pbt/**/*_test.rb"]
129
- workers = ENV.fetch("TEST_WORKERS", Etc.nprocessors)
130
- t.options = "--parallel --n-workers=#{workers}"
131
- end
132
-
133
- desc "Run memory and performance tests"
134
- Rake::TestTask.new(:performance) do |t|
135
- t.libs << "test"
136
- t.libs << "lib"
137
- t.test_files = FileList["test/performance/**/*_test.rb"]
138
- end
139
- desc "Alias for test:performance"
140
- task perf: :performance
141
-
142
- Rake::TestTask.new(:e2e) do |t|
143
- t.libs << "test"
144
- t.libs << "lib"
145
- t.test_files = FileList["test/e2e/**/*_test.rb"]
146
- workers = ENV.fetch("TEST_WORKERS", Etc.nprocessors)
147
- t.options = "--parallel --n-workers=#{workers}"
148
- end
149
-
150
- Rake::TestTask.new(:visual) do |t|
151
- t.libs << "test"
152
- t.libs << "lib"
153
- t.test_files = FileList["test/visual/**/*_test.rb"]
154
- workers = ENV.fetch("TEST_WORKERS", Etc.nprocessors)
155
- t.options = "--parallel --n-workers=#{workers}"
156
- end
157
-
158
- namespace :fixtures do
159
- namespace :reader do
160
- desc "Generate reader fixture XLSX files from SDK scenarios"
161
- task generate: :ensure_reader_fixtures
162
-
163
- desc "Remove generated reader fixture XLSX files"
164
- task :clean do
165
- FileUtils.rm_rf(reader_fixture_dir)
166
- end
167
- end
168
- end
169
- end
170
-
171
- task "test:e2e" => %i[build_sdk_runner ensure_reader_fixtures]
172
-
173
- require "rubocop/rake_task"
174
-
175
- RuboCop::RakeTask.new
176
-
177
- require "bundler/audit/task"
178
- Bundler::Audit::Task.new
179
-
180
- task default: %i[bundle:audit rubocop typecheck test]
181
-
182
- namespace :visual do
183
- desc "Generate docs/visual/ README.md explanation gallery"
184
- task :gallery do
185
- require_relative "test/visual/support/gallery_generator"
186
- Xlsxrb::Visual::GalleryGenerator.generate
187
- end
188
-
189
- desc "Generate / update VRT baselines in test/visual/baselines/"
190
- task :baseline do
191
- require_relative "test/visual/support/renderer"
192
- require "securerandom"
193
- examples = Dir.glob(File.expand_path("examples/visual/*.rb", __dir__))
194
- baselines_dir = File.expand_path("test/visual/baselines", __dir__)
195
-
196
- FileUtils.rm_rf(baselines_dir)
197
- FileUtils.mkdir_p(baselines_dir)
198
-
199
- examples.each do |example_path|
200
- name = File.basename(example_path, ".rb")
201
- puts "Generating baseline for #{name}..."
202
-
203
- example_tmp_dir = File.join(Dir.tmpdir, "xlsxrb_baselines_build_#{name}_#{SecureRandom.hex(4)}")
204
- FileUtils.mkdir_p(example_tmp_dir)
205
-
206
- xlsx_path = File.join(example_tmp_dir, "#{name}.xlsx")
207
- system("ruby", "-Ilib", example_path, xlsx_path)
208
-
209
- dest_dir = File.join(baselines_dir, name)
210
- begin
211
- Xlsxrb::Visual::Renderer.render(xlsx_path, dest_dir)
212
- ensure
213
- FileUtils.rm_rf(example_tmp_dir)
214
- end
215
- end
216
- puts "Baselines generated successfully."
217
- end
218
- end
219
-
220
- desc "Build custom packed ruby.wasm from staging bundle"
221
- task :wasm do
222
- original_wasm_cache = File.expand_path("tmp/test_env/ruby.wasm", __dir__)
223
- packed_wasm_path = File.expand_path("docs/wasm/ruby.wasm", __dir__)
224
- wasm_bundle_dir = File.expand_path("tmp/wasm_bundle", __dir__)
225
- bundle_assets_dir = File.expand_path("docs/wasm/bundle_assets", __dir__)
226
-
227
- # 1. Download original base Wasm if not cached locally
228
- unless File.exist?(original_wasm_cache)
229
- puts "Downloading original ruby.wasm for caching..."
230
- require "open-uri"
231
- wasm_url = "https://cdn.jsdelivr.net/npm/@ruby/4.0-wasm-wasi@2.9.3-2.9.4/dist/ruby.wasm"
232
- FileUtils.mkdir_p(File.dirname(original_wasm_cache))
233
- # rubocop:disable Security/Open
234
- URI.open(wasm_url) do |stream|
235
- File.open(original_wasm_cache, "wb") do |file|
236
- IO.copy_stream(stream, file)
237
- end
238
- end
239
- # rubocop:enable Security/Open
240
- puts "Original ruby.wasm cached successfully."
241
- end
242
-
243
- # 2. Recreate and populate the staging bundle directories
244
- FileUtils.rm_rf(wasm_bundle_dir)
245
- FileUtils.rm_rf(bundle_assets_dir)
246
- FileUtils.mkdir_p(wasm_bundle_dir)
247
- FileUtils.mkdir_p(bundle_assets_dir)
248
-
249
- # A. Write custom static stubs and gateways dynamically
250
- File.write(File.join(bundle_assets_dir, "openssl.rb"), "# frozen_string_literal: true\n")
251
- time_rb_path = $LOAD_PATH.lazy.map { |p| File.join(p, "time.rb") }.find { |f| File.exist?(f) }
252
- FileUtils.cp(time_rb_path, bundle_assets_dir) if time_rb_path
253
-
254
- File.write(File.join(bundle_assets_dir, "opentelemetry.rb"), <<~RUBY)
255
- # frozen_string_literal: true
256
- module OpenTelemetry
257
- def self.tracer_provider
258
- @tracer_provider ||= Class.new {
259
- def tracer(*args)
260
- Class.new {
261
- def in_span(*args)
262
- yield Class.new { def record_exception(*args); end; def status=(*args); end }.new
263
- end
264
- }.new
265
- end
266
- }.new
267
- end
268
- end
269
- RUBY
270
-
271
- # Gateway for REXML
272
- File.write(File.join(bundle_assets_dir, "rexml.rb"), "# frozen_string_literal: true\nrequire \"rexml/rexml\"\n")
273
-
274
- # Resolve and copy host's rexml files
275
- rexml_spec_path = $LOAD_PATH.find { |p| File.exist?(File.join(p, "rexml/rexml.rb")) }
276
- FileUtils.cp_r(File.join(rexml_spec_path, "rexml"), bundle_assets_dir) if rexml_spec_path
277
-
278
- # Gateway for strscan
279
- File.write(File.join(bundle_assets_dir, "strscan.rb"), <<~RUBY)
280
- # frozen_string_literal: true
281
- begin
282
- require "strscan.so"
283
- rescue LoadError
284
- end
285
- require "strscan/strscan"
286
- RUBY
287
-
288
- # Resolve and copy host's strscan files
289
- strscan_spec_path = $LOAD_PATH.find { |p| File.exist?(File.join(p, "strscan/strscan.rb")) }
290
- if strscan_spec_path
291
- FileUtils.mkdir_p(File.join(bundle_assets_dir, "strscan"))
292
- FileUtils.cp(File.join(strscan_spec_path, "strscan/strscan.rb"), File.join(bundle_assets_dir, "strscan/strscan.rb"))
293
- end
294
-
295
- # B. Copy necessary standard libraries dynamically from host's $LOAD_PATH
296
- stdlib_files = [
297
- "date.rb", "delegate.rb", "forwardable.rb", "securerandom.rb",
298
- "random/formatter.rb", "set.rb", "tempfile.rb", "tmpdir.rb", "fileutils.rb",
299
- "pp.rb", "prettyprint.rb"
300
- ]
301
- stdlib_files.each do |name|
302
- path = $LOAD_PATH.find { |p| File.exist?(File.join(p, name)) }
303
- next unless path
304
-
305
- dest_path = File.join(bundle_assets_dir, name)
306
- FileUtils.mkdir_p(File.dirname(dest_path))
307
- FileUtils.cp(File.join(path, name), dest_path)
308
- end
309
-
310
- # C. Patch tmpdir.rb to automatically create /tmp in Wasm virtual filesystem (since Wasm has no writable /tmp by default)
311
- tmpdir_path = File.join(bundle_assets_dir, "tmpdir.rb")
312
- if File.exist?(tmpdir_path)
313
- File.open(tmpdir_path, "a") do |f|
314
- f.puts "\nclass Dir\n def self.tmpdir\n Dir.mkdir(\"/tmp\") rescue nil unless File.directory?(\"/tmp\")\n \"/tmp\"\n end\nend\n"
315
- end
316
- end
317
-
318
- # D. Copy everything from bundle_assets_dir to wasm_bundle_dir
319
- FileUtils.cp_r(File.join(bundle_assets_dir, "."), wasm_bundle_dir)
320
-
321
- # E. Copy latest xlsxrb implementation files from lib/
322
- FileUtils.cp(File.expand_path("lib/xlsxrb.rb", __dir__), File.join(wasm_bundle_dir, "xlsxrb.rb"))
323
- FileUtils.cp_r(File.expand_path("lib/xlsxrb", __dir__), wasm_bundle_dir)
324
-
325
- # 3. Compile custom ruby.wasm packaging staging libs
326
- FileUtils.mkdir_p(File.dirname(packed_wasm_path))
327
- puts "Building packed ruby.wasm from staging bundle..."
328
- cmd = "bundle exec rbwasm pack #{original_wasm_cache} --dir #{wasm_bundle_dir}::/usr/local/lib/ruby/site_ruby -o #{packed_wasm_path}"
329
- puts "Executing: #{cmd}"
330
- raise "Failed to build packed ruby.wasm using rbwasm pack!" unless system(cmd)
331
- end
332
-
333
- def download_file(url, dest)
334
- # Check if the file exists and is not a tiny placeholder/error document
335
- return if File.exist?(dest) && File.size(dest) > 1024
336
-
337
- puts "Downloading #{url} to #{dest}..."
338
- FileUtils.mkdir_p(File.dirname(dest))
339
-
340
- require "net/http"
341
- uri = URI.parse(url)
342
- temp_dest = "#{dest}.tmp"
343
-
344
- begin
345
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 15, read_timeout: 90) do |http|
346
- request = Net::HTTP::Get.new(uri)
347
- # Specify User-Agent to bypass scraping prevention on CDNs and act as a normal browser
348
- request["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
349
- # Force raw (identity) encoding to prevent receiving Brotli-compressed (.br) data,
350
- # which Ruby's Net::HTTP cannot decode automatically, leading to corrupted Wasm files.
351
- request["Accept-Encoding"] = "identity"
352
-
353
- http.request(request) do |response|
354
- raise "HTTP error #{response.code}: #{response.message}" if response.code.to_i != 200
355
-
356
- File.open(temp_dest, "wb") do |output|
357
- response.read_body do |chunk|
358
- output.write(chunk)
359
- end
360
- end
361
- end
362
- end
363
- # Atomic rename to prevent leaving incomplete files on failure
364
- File.rename(temp_dest, dest)
365
- puts "Downloaded successfully."
366
-
367
- # Dynamic Brotli Decompression if the server ignored identity encoding and sent Brotli (.br) data
368
- if File.exist?(dest) && File.binread(dest, 4)&.bytes == [0xCF, 0xFF, 0xFF, 0x7F]
369
- puts "Detected Brotli compression on #{dest}. Decompressing..."
370
-
371
- unpacked = "#{dest}.unpacked"
372
- if system("brotli -d -f -o #{unpacked} #{dest}")
373
- File.rename(unpacked, dest)
374
- puts "Decompressed #{dest} successfully."
375
- else
376
- FileUtils.rm_f(unpacked)
377
- raise "Failed to decompress Brotli file: #{dest}"
378
- end
379
- end
380
- rescue StandardError => e
381
- puts "Failed to download #{url}: #{e.message}"
382
- FileUtils.rm_f(temp_dest)
383
- FileUtils.rm_f(dest)
384
- raise "Required asset download failed. Build aborted."
385
- end
386
- end
387
-
388
- def fetch_google_fonts
389
- css_dest = "docs/fonts/fonts.css"
390
- return if File.exist?(css_dest)
391
-
392
- puts "Fetching and localizing Google Fonts..."
393
- font_url = "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
394
-
395
- css_content = nil
396
- begin
397
- # Specify Chrome User-Agent to ensure Google Fonts returns modern and lightweight .woff2 formats
398
- # instead of legacy formats (like .ttf or .eot) designed for older browsers
399
- # rubocop:disable Security/Open
400
- URI.open(font_url, "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") do |f|
401
- css_content = f.read
402
- end
403
- # rubocop:enable Security/Open
404
- rescue StandardError => e
405
- puts "Failed to fetch Google Fonts CSS: #{e.message}"
406
- return
407
- end
408
-
409
- urls = css_content.scan(%r{url\((https://fonts\.gstatic\.com/[^)]+)\)}).flatten
410
-
411
- urls.uniq.each do |url|
412
- filename = url.split("/").last
413
- local_path = "docs/fonts/#{filename}"
414
- download_file(url, local_path)
415
- css_content.gsub!(url, filename)
416
- end
417
-
418
- FileUtils.mkdir_p("docs/fonts")
419
- File.write(css_dest, css_content)
420
- puts "Google Fonts localized successfully."
421
- end
422
-
423
- desc "Fetch required external assets for offline usage"
424
- task :fetch_assets do
425
- require "open-uri"
426
- require "fileutils"
427
-
428
- # Fetch Ruby WASI JS
429
- download_file(
430
- "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.9.3-2.9.4/dist/browser.umd.js",
431
- "docs/wasm/browser.umd.js"
432
- )
433
-
434
- # Fetch ZetaOffice Wasm
435
- base_zeta_url = "https://cdn.zetaoffice.net/zetaoffice_latest/"
436
- %w[soffice.js soffice.wasm soffice.data soffice.data.js.metadata qtloader.js].each do |file|
437
- download_file(base_zeta_url + file, "docs/zetaoffice/#{file}")
438
- end
439
-
440
- # Fetch and localize Google Fonts
441
- fetch_google_fonts
442
- end
443
-
444
- desc "Generate RDoc documentation including Visual Gallery"
445
- task doc: %i[wasm fetch_assets] do
446
- FileUtils.rm_rf("doc")
447
-
448
- # RDoc コマンドを実行 (--exclude を指定して docs 配下のプレビュー用アセットの誤パースを回避)
449
- sh "bundle exec rdoc --op doc " \
450
- "--exclude 'docs/coi-serviceworker\\.js' " \
451
- "--exclude 'docs/zeta\\.js' " \
452
- "--exclude 'docs/office_thread\\.js' " \
453
- "--exclude 'docs/preview\\.html' " \
454
- "--title 'xlsxrb Documentation' --main README.md README.md CHANGELOG.md CODE_OF_CONDUCT.md docs/*.md \"docs/visual/VisualGallery.md\" lib/"
455
-
456
- # Copy visual gallery images and files so they are available in RDoc output
457
- FileUtils.mkdir_p("doc/test/visual/baselines")
458
- FileUtils.mkdir_p("doc/test/visual/support/illustrations")
459
- FileUtils.cp_r(Dir.glob("test/visual/baselines/*"), "doc/test/visual/baselines")
460
- FileUtils.cp_r(Dir.glob("test/visual/support/illustrations/*.png"), "doc/test/visual/support/illustrations")
461
-
462
- FileUtils.mkdir_p("doc/docs/visual/files")
463
- FileUtils.cp_r(Dir.glob("docs/visual/files/*"), "doc/docs/visual/files")
464
-
465
- FileUtils.mkdir_p("doc/docs/assets")
466
- FileUtils.cp_r(Dir.glob("docs/assets/*"), "doc/docs/assets")
467
-
468
- # Alias index.html as README_md.html for links referencing README.md
469
- FileUtils.cp("doc/index.html", "doc/README_md.html") if File.exist?("doc/index.html")
470
-
471
- # --- WebAssembly Playground Integration ---
472
- puts "Integrating WebAssembly Playground..."
473
-
474
- # Copy playground helper assets and custom Wasm binary to doc directory
475
- FileUtils.mkdir_p("doc/css")
476
- FileUtils.mkdir_p("doc/js")
477
- FileUtils.mkdir_p("doc/wasm")
478
- FileUtils.mkdir_p("doc/zetaoffice")
479
- FileUtils.mkdir_p("doc/fonts")
480
-
481
- FileUtils.cp("docs/wasm/wasm_doc_helper.js", "doc/js/wasm_doc_helper.js")
482
- FileUtils.cp("docs/wasm/wasm_doc_helper.css", "doc/css/wasm_doc_helper.css")
483
- FileUtils.cp("docs/wasm/ruby.wasm", "doc/wasm/ruby.wasm")
484
- FileUtils.cp("docs/wasm/browser.umd.js", "doc/wasm/browser.umd.js")
485
- FileUtils.cp_r(Dir.glob("docs/zetaoffice/*"), "doc/zetaoffice")
486
- FileUtils.cp_r(Dir.glob("docs/fonts/*"), "doc/fonts")
487
-
488
- # Copy LibreOffice Wasm Preview assets to doc directory
489
- FileUtils.cp("docs/preview.html", "doc/preview.html")
490
- FileUtils.cp("docs/coi-serviceworker.js", "doc/coi-serviceworker.js")
491
- FileUtils.cp("docs/zeta.js", "doc/zeta.js")
492
- FileUtils.cp("docs/office_thread.js", "doc/office_thread.js")
493
-
494
- # Inject stylesheet and javascript loading tags to all generated HTML docs
495
- Dir.glob("doc/**/*.html").each do |html_path|
496
- next if File.basename(html_path) == "preview.html"
497
-
498
- html_content = File.read(html_path)
499
- depth = html_path.sub(%r{\Adoc/}, "").count("/")
500
- rel_prefix = "../" * depth
501
-
502
- coi_tag = %(<script src="#{rel_prefix}coi-serviceworker.js"></script>)
503
- js_tag = %(<script src="#{rel_prefix}js/wasm_doc_helper.js" defer></script>)
504
- css_tag = %(<link href="#{rel_prefix}css/wasm_doc_helper.css" rel="stylesheet">)
505
-
506
- if html_content.include?("<body")
507
- modified = html_content.sub("<body", "#{coi_tag}\n#{js_tag}\n#{css_tag}\n<body")
508
- File.write(html_path, modified)
509
- end
510
- end
511
- puts "WebAssembly Playground integrated successfully!"
512
- puts "RDoc documentation generated successfully at doc/"
513
- end
514
-
515
- namespace :doc do
516
- desc "Build and preview RDoc documentation locally"
517
- task preview: :doc do
518
- require "webrick"
519
-
520
- port = ENV.fetch("PORT", "8000").to_i
521
-
522
- # Configure WEBrick to serve 'doc' directory
523
- server = WEBrick::HTTPServer.new(
524
- Port: port,
525
- DocumentRoot: File.expand_path("doc", __dir__),
526
- Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
527
- AccessLog: [],
528
- # If the file is a Brotli-compressed Wasm/Data asset (checked via magic bytes),
529
- # dynamically inject 'Content-Encoding: br' header so the browser decompresses it natively.
530
- RequestCallback: lambda { |req, res|
531
- if req.path.end_with?(".wasm") || req.path.end_with?(".data")
532
- # Resolve physical file path from req.path manually since res.filename is nil at this stage
533
- local_path = File.join(File.expand_path("doc", __dir__), req.path)
534
- if File.exist?(local_path)
535
- first_bytes = begin
536
- File.binread(local_path, 4)
537
- rescue StandardError
538
- nil
539
- end
540
- res["Content-Encoding"] = "br" if first_bytes != "\x00asm"
541
- end
542
- end
543
- }
544
- )
545
-
546
- puts "=================================================="
547
- puts " Starting local documentation server at:"
548
- puts " http://localhost:#{port}/"
549
- puts " Press Ctrl+C to stop the server"
550
- puts "=================================================="
551
-
552
- trap("INT") { server.shutdown }
553
-
554
- begin
555
- server.start
556
- rescue Errno::EADDRINUSE
557
- warn "\n[Error] Port #{port} is already in use by another process."
558
- warn "Please stop the other process or specify a different port."
559
- warn "Example: PORT=8080 bundle exec rake doc:preview\n\n"
560
- exit 1
561
- end
562
- end
563
- end
564
- desc "Generate RBS signature files from inline annotations"
565
- task :sig do
566
- files = FileList["lib/**/*.rb"].to_a
567
- sh "bundle", "exec", "rbs-inline", "--output=sig/generated", "--base=lib", *files
568
- end
569
-
570
- desc "Run static type checking with Steep"
571
- task typecheck: :sig do
572
- sh "bundle exec steep check"
573
- end
574
-
575
- namespace :mutant do
576
- desc "Run mutation testing on pure logic and algorithms (coordinates, serial, conversion)"
577
- task :pure do
578
- default_subjects = [
579
- "Xlsxrb::Elements::CoordinateAccess#cells",
580
- "Xlsxrb::Elements::CoordinateAccess#[]",
581
- "Xlsxrb::Elements::Cell#ref",
582
- "Xlsxrb::Elements::Cell#to_i",
583
- "Xlsxrb::Elements::Cell#to_f",
584
- "Xlsxrb::Elements::Cell#content",
585
- "Xlsxrb::Elements::Cell#to_s",
586
- "Xlsxrb::Elements::Cell#valid?",
587
- "Xlsxrb::Elements::Cell.calculate_column_letter",
588
- "Xlsxrb::Elements::Cell.calculate_column_index",
589
- "Xlsxrb::Elements::Cell.valid_coordinates?",
590
- "Xlsxrb::Elements::Cell.valid_value?",
591
- "Xlsxrb::Ooxml::Utils.serial_to_date",
592
- "Xlsxrb::Ooxml::Utils.date_to_serial",
593
- "Xlsxrb::Ooxml::Utils.datetime_to_serial",
594
- "Xlsxrb::Ooxml::Utils.serial_to_datetime",
595
- "Xlsxrb::Ooxml::ZipGenerator.le16",
596
- "Xlsxrb::Ooxml::ZipGenerator.le32",
597
- "Xlsxrb::StreamRow#valid?",
598
- "Xlsxrb::StreamRow#unmapped_data",
599
- "Xlsxrb::StreamRow#errors",
600
- "Xlsxrb::StreamRow#values",
601
- "Xlsxrb::Elements::Worksheet.valid_name?",
602
- "Xlsxrb::Elements::Worksheet#valid?",
603
- "Xlsxrb::Elements::Worksheet#load",
604
- "Xlsxrb::Ooxml::Cfb::Reader.cfb?",
605
- "Xlsxrb::Ooxml::Cfb::DirEntry#stream?",
606
- "Xlsxrb::Ooxml::Cfb::DirEntry#root?",
607
- "Xlsxrb::Ooxml::Cfb::DirEntry#storage?",
608
- "Xlsxrb::Elements::Column.valid_index?",
609
- "Xlsxrb::Elements::Column.validate",
610
- "Xlsxrb::Elements::Column#valid?",
611
- "Xlsxrb::Elements::Row.valid_index?",
612
- "Xlsxrb::Elements::Row#valid?",
613
- "Xlsxrb::Elements::Workbook#valid?",
614
- "Xlsxrb::Ooxml::XmlBuilder.escape",
615
- "Xlsxrb::Ooxml::ZipGenerator.dos_datetime",
616
- "Xlsxrb::DslHelpers.normalize_column_indices"
617
- ].join(" ")
618
- subjects = ENV["MUTANT_SUBJECTS"] || default_subjects
619
- test_files = [
620
- "./test/xlsxrb/elements_test.rb",
621
- "./test/xlsxrb/ooxml_test.rb",
622
- "./test/xlsxrb/ooxml/utils_test.rb",
623
- "./test/xlsxrb/ooxml/crypto_test.rb",
624
- "./test/xlsxrb/ooxml/cfb_test.rb",
625
- "./test/zip_generator_test.rb",
626
- "./test/xlsxrb/stream_row_test.rb",
627
- "./test/xlsxrb/style_builder_test.rb",
628
- "./test/xlsxrb/dsl_helpers_test.rb"
629
- ].select { |f| File.exist?(f) }
630
- requires = test_files.map { |f| "-r #{f}" }.join(" ")
631
- sh "bundle exec mutant run --usage opensource #{requires} -- #{subjects}"
632
- end
633
- end
data/Steepfile DELETED
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- target :lib do
4
- signature "sig"
5
- check "lib"
6
-
7
- library "date"
8
- library "time"
9
- library "securerandom"
10
- library "openssl"
11
- library "pathname"
12
- library "tempfile"
13
- library "bigdecimal"
14
-
15
- repo_path "sig"
16
-
17
- configure_code_diagnostics(Steep::Diagnostic::Ruby.lenient)
18
- end