xlsxrb 0.1.11 → 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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -34
  3. data/README.md +26 -21
  4. data/lib/xlsxrb/chart_builder.rb +18 -18
  5. data/lib/xlsxrb/dsl_helpers.rb +175 -0
  6. data/lib/xlsxrb/elements/cell.rb +56 -22
  7. data/lib/xlsxrb/elements/column.rb +27 -3
  8. data/lib/xlsxrb/elements/row.rb +27 -8
  9. data/lib/xlsxrb/elements/workbook.rb +27 -14
  10. data/lib/xlsxrb/elements/worksheet.rb +17 -1
  11. data/lib/xlsxrb/ooxml/cfb.rb +9 -6
  12. data/lib/xlsxrb/ooxml/crypto/agile.rb +5 -5
  13. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +784 -0
  14. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +3478 -0
  15. data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +1796 -0
  16. data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +428 -0
  17. data/lib/xlsxrb/ooxml/reader/listeners.rb +4 -6446
  18. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +3 -5
  19. data/lib/xlsxrb/ooxml/utils.rb +5 -6
  20. data/lib/xlsxrb/ooxml/workbook_writer.rb +5 -7
  21. data/lib/xlsxrb/ooxml/worksheet_parser.rb +172 -9
  22. data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +1602 -0
  23. data/lib/xlsxrb/ooxml/writer/features_xml.rb +731 -0
  24. data/lib/xlsxrb/ooxml/writer/styles_xml.rb +348 -0
  25. data/lib/xlsxrb/ooxml/writer.rb +9 -2646
  26. data/lib/xlsxrb/ooxml/xml_builder.rb +31 -2
  27. data/lib/xlsxrb/ooxml/zip_generator.rb +26 -5
  28. data/lib/xlsxrb/ooxml/zip_reader.rb +364 -78
  29. data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
  30. data/lib/xlsxrb/stream_sheet.rb +94 -6
  31. data/lib/xlsxrb/stream_writer.rb +48 -115
  32. data/lib/xlsxrb/style_builder.rb +10 -1
  33. data/lib/xlsxrb/version.rb +1 -1
  34. data/lib/xlsxrb/workbook_builder.rb +1 -1
  35. data/lib/xlsxrb/worksheet_builder.rb +19 -81
  36. data/lib/xlsxrb.rb +449 -171
  37. data/sig/generated/xlsxrb/chart_builder.rbs +36 -36
  38. data/sig/generated/xlsxrb/dsl_helpers.rbs +92 -0
  39. data/sig/generated/xlsxrb/elements/cell.rbs +29 -0
  40. data/sig/generated/xlsxrb/elements/column.rbs +32 -21
  41. data/sig/generated/xlsxrb/elements/row.rbs +94 -14
  42. data/sig/generated/xlsxrb/elements/workbook.rbs +102 -10
  43. data/sig/generated/xlsxrb/elements/worksheet.rbs +9 -0
  44. data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +173 -0
  45. data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +400 -0
  46. data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +442 -0
  47. data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +80 -0
  48. data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +0 -1072
  49. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +0 -2
  50. data/sig/generated/xlsxrb/ooxml/utils.rbs +2 -0
  51. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +5 -4
  52. data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +79 -0
  53. data/sig/generated/xlsxrb/ooxml/writer/features_xml.rbs +83 -0
  54. data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +53 -0
  55. data/sig/generated/xlsxrb/ooxml/writer.rbs +6 -185
  56. data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +18 -0
  57. data/sig/generated/xlsxrb/ooxml/zip_generator.rbs +15 -0
  58. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +96 -8
  59. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
  60. data/sig/generated/xlsxrb/stream_sheet.rbs +43 -3
  61. data/sig/generated/xlsxrb/style_builder.rbs +7 -0
  62. data/sig/generated/xlsxrb.rbs +18 -11
  63. metadata +22 -144
  64. data/.devcontainer/Dockerfile +0 -65
  65. data/.devcontainer/devcontainer.json +0 -17
  66. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
  67. data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
  68. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +0 -9
  69. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +0 -4
  70. data/.gem_rbs_collection/concurrent-ruby/1.1/atomic_reference.rbs +0 -16
  71. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +0 -96
  72. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +0 -4
  73. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +0 -68
  74. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +0 -249
  75. data/.gem_rbs_collection/concurrent-ruby/1.1/set.rbs +0 -4
  76. data/.gem_rbs_collection/concurrent-ruby/1.1/timer_task.rbs +0 -47
  77. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +0 -5
  78. data/.gem_rbs_collection/csv/3.3/.rbs_meta.yaml +0 -9
  79. data/.gem_rbs_collection/csv/3.3/csv.rbs +0 -3871
  80. data/.gem_rbs_collection/csv/3.3/manifest.yaml +0 -3
  81. data/.gem_rbs_collection/lint_roller/1.1/.rbs_meta.yaml +0 -9
  82. data/.gem_rbs_collection/lint_roller/1.1/lint_roller.rbs +0 -48
  83. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +0 -9
  84. data/.gem_rbs_collection/listen/3.9/listen.rbs +0 -25
  85. data/.gem_rbs_collection/listen/3.9/listener.rbs +0 -24
  86. data/.gem_rbs_collection/logger/1.7/.rbs_meta.yaml +0 -9
  87. data/.gem_rbs_collection/logger/1.7/formatter.rbs +0 -45
  88. data/.gem_rbs_collection/logger/1.7/log_device.rbs +0 -100
  89. data/.gem_rbs_collection/logger/1.7/logger.rbs +0 -796
  90. data/.gem_rbs_collection/logger/1.7/manifest.yaml +0 -2
  91. data/.gem_rbs_collection/logger/1.7/period.rbs +0 -17
  92. data/.gem_rbs_collection/logger/1.7/severity.rbs +0 -34
  93. data/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +0 -9
  94. data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +0 -2332
  95. data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +0 -4
  96. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
  97. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
  98. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
  99. data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
  100. data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -194
  101. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
  102. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
  103. data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
  104. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
  105. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
  106. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
  107. data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
  108. data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
  109. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
  110. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
  111. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
  112. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -208
  113. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +0 -9
  114. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +0 -903
  115. data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +0 -9
  116. data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +0 -8
  117. data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +0 -42
  118. data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +0 -5
  119. data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +0 -47
  120. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +0 -30
  121. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +0 -9
  122. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +0 -7
  123. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +0 -19
  124. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +0 -31
  125. data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +0 -18
  126. data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +0 -12
  127. data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +0 -11
  128. data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +0 -13
  129. data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +0 -95
  130. data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +0 -31
  131. data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +0 -58
  132. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +0 -24
  133. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +0 -17
  134. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +0 -23
  135. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +0 -22
  136. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +0 -30
  137. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +0 -20
  138. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +0 -15
  139. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +0 -26
  140. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +0 -21
  141. data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +0 -131
  142. data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +0 -14
  143. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +0 -33
  144. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +0 -21
  145. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +0 -63
  146. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +0 -55
  147. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +0 -35
  148. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +0 -7
  149. data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +0 -10
  150. data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +0 -22
  151. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +0 -29
  152. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +0 -17
  153. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +0 -13
  154. data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +0 -10
  155. data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +0 -8
  156. data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +0 -6
  157. data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +0 -30
  158. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +0 -10
  159. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +0 -10
  160. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +0 -5
  161. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +0 -15
  162. data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +0 -3
  163. data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +0 -40
  164. data/Rakefile +0 -567
  165. data/Steepfile +0 -18
  166. data/benchmark.rb +0 -406
  167. data/docs/ARCHITECTURE.md +0 -514
  168. data/docs/DEVELOPMENT.md +0 -133
  169. data/docs/PEER_LIBRARIES.md +0 -121
  170. data/docs/QUALITY_ASSURANCE.md +0 -25
  171. data/docs/SPEC_SOURCES.md +0 -48
  172. data/docs/assets/benchmark_results.svg +0 -144
  173. data/docs/assets/lsp_autocompletion.png +0 -0
  174. data/docs/assets/playground_preview.png +0 -0
  175. data/docs/coi-serviceworker.js +0 -82
  176. data/docs/office_thread.js +0 -77
  177. data/docs/preview.html +0 -719
  178. data/docs/visual/VisualGallery.md +0 -4672
  179. data/docs/wasm/ruby.wasm +0 -0
  180. data/docs/wasm/wasm_doc_helper.css +0 -351
  181. data/docs/wasm/wasm_doc_helper.js +0 -439
  182. data/docs/zeta.js +0 -1107
  183. data/rbs_collection.lock.yaml +0 -252
  184. data/rbs_collection.yaml +0 -19
  185. data/vendor/sdk_runner/Program.cs +0 -93
  186. data/vendor/sdk_runner/sdk_runner.csproj +0 -13
data/docs/ARCHITECTURE.md DELETED
@@ -1,514 +0,0 @@
1
- # Xlsxrb Architecture
2
-
3
- Xlsxrb uses a multi-layered architecture to separate low-level OpenXML specification details from a high-level, idiomatic Ruby API. This separation of concerns ensures the library is both robust against the complex OpenXML spec and user-friendly for Ruby developers.
4
-
5
- ## Dependency Constraints
6
-
7
- Xlsxrb uses **only** the Ruby standard library and Bundled Gems:
8
-
9
- | Library | Purpose |
10
- | :--- | :--- |
11
- | `rexml` (bundled gem) | SAX2-based XML parsing and DOM-based XML generation |
12
- | `zlib` (stdlib) | ZIP deflate/inflate compression |
13
- | `stringio` (stdlib) | In-memory IO for streaming |
14
- | `date` (stdlib) | Excel serial-number ↔ `Date` / `Time` conversion |
15
- | `openssl`, `securerandom` (stdlib) | Password hashing for sheet/workbook protection |
16
-
17
- **No third-party gems** (e.g. Nokogiri, rubyzip) are permitted as runtime dependencies.
18
-
19
- ---
20
-
21
- ## Coding Policies
22
-
23
- ### 🚫 The `method_missing` Policy
24
- As a strict rule, **we do not accept dynamic method definitions using `method_missing`** anywhere in the codebase. All user-facing API methods and internal delegations must be explicitly defined in the code. This ensures:
25
- 1. **Type Safety & Static Analysis**: RBS and Steep can completely validate arguments and structures.
26
- 2. **Developer Experience**: IDE autocompletion, jump-to-definition, and YARD documentation work perfectly.
27
- 3. **Traceability**: If a method exists, you can `grep` for it.
28
-
29
- Even in cases where proxy patterns (e.g. `WorksheetProxy`) or OOXML builder mappings (e.g. `ChartBuilder`, `SeriesBuilder`) would traditionally benefit from dynamic delegation to avoid boilerplate, we explicitly generate and write out those delegations in the source code.
30
-
31
- ### 1. **`Xlsxrb` Module is the ONLY Entrypoint:**
32
- The `Xlsxrb` module provides the top-level methods: `read`, `write`, `build`, and `modify`. Users should **never** instantiate internal classes (like `Xlsxrb::Ooxml::WorkbookWriter`) directly.
33
-
34
- 2. **The `@api public` Contract (SemVer Guarantee):**
35
- Any module, class, or method tagged with `# @api public` in its YARD documentation is guaranteed to follow Semantic Versioning.
36
- - Patch versions (0.1.x -> 0.1.y) will not break these APIs.
37
- - Minor versions (0.x.0 -> 0.y.0) will not break these APIs once 1.0.0 is released (during 0.x.x, it is a best-effort promise).
38
- - Major versions (1.x -> 2.x) are the only time breaking changes to `@api public` components are permitted.
39
-
40
- 3. **Block-Yielded Objects are Public APIs:**
41
- All builder objects yielded into blocks (e.g., `writer` in `Xlsxrb.write { |writer| }`, `sheet` in `writer.sheet { |sheet| }`, `chart` in `sheet.chart { |chart| }`) are explicitly marked as `@api public`. Their exposed methods constitute the DSL and are strictly protected by the SemVer contract.
42
-
43
- ---
44
-
45
- ## Directory Structure
46
-
47
- ```
48
- lib/
49
- xlsxrb.rb # Facade: Xlsxrb.read / .write / .build / .modify
50
- xlsxrb/
51
- version.rb # Xlsxrb::VERSION
52
- elements.rb # Requires for Elements layer
53
- stream_row.rb # Lazy/streaming row and cell reader (O(1) memory)
54
- ooxml.rb # Requires for Ooxml layer
55
- ooxml/ # Layer 1 – Low-level OOXML
56
- reader.rb # Xlsxrb::Ooxml::Reader (core reading logic)
57
- writer.rb # Xlsxrb::Ooxml::Writer (core writing logic)
58
- zip_generator.rb # Xlsxrb::Ooxml::ZipGenerator
59
- utils.rb # Xlsxrb::Ooxml::Utils (date/time/hash helpers)
60
- zip_reader.rb # Xlsxrb::Ooxml::ZipReader
61
- zip_writer.rb # Xlsxrb::Ooxml::ZipWriter
62
- xml_parser.rb # Xlsxrb::Ooxml::XmlParser
63
- xml_builder.rb # Xlsxrb::Ooxml::XmlBuilder
64
- shared_strings_parser.rb # Streaming SST reader
65
- styles_parser.rb # styles.xml reader
66
- worksheet_parser.rb # Streaming sheetN.xml reader
67
- worksheet_writer.rb # Streaming sheetN.xml writer
68
- workbook_parser.rb # workbook.xml reader
69
- workbook_writer.rb # workbook.xml / styles / SST writer
70
- elements/ # Layer 2 – Domain model
71
- types.rb # Xlsxrb::Elements::Formula, CellError, RichText
72
- cell.rb # Xlsxrb::Elements::Cell
73
- row.rb # Xlsxrb::Elements::Row
74
- column.rb # Xlsxrb::Elements::Column
75
- worksheet.rb # Xlsxrb::Elements::Worksheet
76
- workbook.rb # Xlsxrb::Elements::Workbook
77
- ```
78
-
79
- ---
80
-
81
- ## Core Architecture
82
-
83
- The library is structured into three distinct layers:
84
-
85
- ### 1. Low-Level Infrastructure (The "OOXML" Layer)
86
-
87
- **Namespace:** `Xlsxrb::Ooxml`
88
-
89
- **Responsibility:**
90
- This layer directly handles ZIP extraction, XML parsing (via SAX), and XML generation. It adheres strictly to the ECMA-376 OpenXML specification.
91
-
92
- * **`Xlsxrb::Ooxml::ZipReader`**: Reads a `.xlsx` ZIP archive entry-by-entry. Accepts a file path or `IO` object. Yields `(entry_name, io)` pairs without loading the entire archive into memory.
93
- * **`Xlsxrb::Ooxml::ZipWriter`**: Streams ZIP local-file-headers and a central directory to a file path or `IO`. Each entry is compressed with `Zlib::Deflate` in a single pass.
94
- * **`Xlsxrb::Ooxml::XmlParser`**: Thin wrapper around `REXML::Parsers::SAX2Parser`. Converts SAX2 events into a Hash/Array tree. Unknown elements are collected as opaque `{ tag:, attrs:, children: }` hashes (see *unmapped_data* below).
95
- * **`Xlsxrb::Ooxml::XmlBuilder`**: Emits well-formed XML strings via `<<` to a writable IO, supporting streaming generation without building a DOM.
96
- * **Part-specific parsers/writers**: `WorksheetParser`, `SharedStringsParser`, `StylesParser`, `WorkbookParser`, etc., each encapsulating the SAX event handling for one OpenXML part.
97
-
98
- ### 2. High-Level Domain Model (The "Elements" Layer) & Streaming Row Layer
99
-
100
- **Namespace:** `Xlsxrb::Elements` and `Xlsxrb::StreamRow`
101
-
102
- **Responsibility:**
103
- This layer provides idiomatic, easy-to-use Ruby objects representing Excel concepts. It utilizes Ruby 3.2+ `Data` classes for immutability and precise structural definition. All domain models are encapsulated here to keep the top-level namespace clean.
104
-
105
- **Core Objects:**
106
- * **`Xlsxrb::Elements::Workbook`**: Represents the entire file structure (`Data` class). Contains `sheets` (Array of Worksheet), shared styles metadata, and `unmapped_data`.
107
- * **`Xlsxrb::Elements::Worksheet`**: Represents a single sheet (`Data` class). Contains `name`, `rows` (Array of Row), `columns` (Array of Column), and sheet-level properties.
108
- * **`Xlsxrb::Elements::Row`**: Represents one in-memory row (`Data` class). Contains `index` (0-based), `cells` (Array of Cell), and row-level attributes.
109
- * **`Xlsxrb::StreamRow`**: Represents a streaming row with lazy cell parsing. Provides `row.each_cell` / `row.each` for $O(1)$ constant memory streaming, caching cells on-demand if indexed or converted to an array.
110
- * **`Xlsxrb::Elements::Column`**: Represents column formatting (`Data` class). Contains `index` (0-based), `width`, and column-level attributes.
111
- * **`Xlsxrb::Elements::Cell`**: Represents a single cell (`Data` class). Contains `row_index`, `column_index` (both 0-based), `value` (Ruby native type), `formula`, `style`, and `unmapped_data`.
112
-
113
- **Design Principles:**
114
- * **Zero-based Indexing:** To maintain consistency with Ruby's core language (Arrays/Enumerable), all indices (rows, columns, and worksheets) are **0-based**. For Excel-style coordination, use string references like `cell("A1")`.
115
- * **Fail-safe Design (Lazy Validation):** Exceptions are not raised during XML parsing. Each class has an `errors` property (Array of String) and a `valid?` method that returns `errors.empty?`.
116
- * **Forward Compatibility:** All classes have an `unmapped_data` property (Hash) to ensure that any unknown XML attributes or elements are retained, preserving file integrity during round-trips.
117
-
118
- ### 3. The Facade / Entrypoint Layer
119
-
120
- **Namespace:** `Xlsxrb`
121
-
122
- **Responsibility:**
123
- Acts as the primary bridge, offering symmetric In-Memory and Streaming APIs.
124
-
125
- | Method | Type | Description |
126
- | :--- | :--- | :--- |
127
- | **`Xlsxrb.read(source, &block)`** | **Streaming** | Streams sheets (`StreamSheet`) and rows (`StreamRow`) with $O(1)$ constant memory. |
128
- | **`Xlsxrb.write(target, &block)`** | **Streaming** | Streams rows directly to file/IO with minimal memory. |
129
- | **`Xlsxrb.read(source)`** | In-Memory | Loads from file path, IO, or raw binary string into a `Workbook`. |
130
- | **`Xlsxrb.write(target, wb)`** / **`Xlsxrb.write(wb)`** | In-Memory | Saves `Workbook` to file/IO, or returns raw binary string (single argument). |
131
- | **`Xlsxrb.build(&block)`** | In-Memory | Builds an immutable `Workbook` using DSL. |
132
- | **`Xlsxrb.modify(source, target, &block)`** | In-Memory | Updates cells/sheets of an existing workbook. |
133
-
134
- ### Facade Expansion Policy
135
-
136
- The long-term API goal is that **all spreadsheet features implemented in the low-level `Ooxml::Writer` layer should be available through the high-level Facade DSL** as well.
137
-
138
- This applies to both:
139
-
140
- * **In-Memory DSL** (`Xlsxrb.build` -> `WorkbookBuilder` / `WorksheetBuilder`)
141
- * **Streaming DSL** (`Xlsxrb.write` -> `StreamWriter`)
142
-
143
- The Facade should not expose only a hand-picked subset forever. If a feature is stable and supported in the low-level writer, the default expectation is that it should eventually gain a high-level entry point.
144
-
145
- ### Facade DSL Conventions
146
-
147
- When adding a new high-level feature, follow these API rules unless there is a clear technical reason not to.
148
-
149
- #### 1. Support both a concise options form and a block form
150
-
151
- Each DSL feature should prefer the same dual entry style now used by chart and style configuration:
152
-
153
- * **Options form** for short, common cases
154
- * **Block form** for larger or nested configuration
155
-
156
- Examples:
157
-
158
- ```ruby
159
- s.add_style("header", bold: true, size: 14, font_color: "FFFF0000")
160
-
161
- s.add_style("header") do |style|
162
- style.bold.size(14).font_color("FFFF0000")
163
- end
164
-
165
- w.add_chart(type: :bar, title: "Sales", series: [{ cat_ref: "A1:A3", val_ref: "B1:B3" }])
166
-
167
- w.add_chart do |chart|
168
- chart.type :bar
169
- chart.title "Sales"
170
- chart.series(cat_ref: "A1:A3", val_ref: "B1:B3")
171
- end
172
- ```
173
-
174
- The options form should stay compact. The block form should become the preferred place for nested or verbose configuration.
175
-
176
- #### 2. Keep naming consistent
177
-
178
- Use naming by intent:
179
-
180
- * `add_*` for adding a new object or definition
181
- * `set_*` for mutating a single property or replacing a single setting
182
- * builder methods inside a block should use the domain name directly where possible (`title`, `series`, `bold`, `fill_color`, etc.)
183
-
184
- Do not introduce one-off verbs for similar concepts unless the low-level feature truly behaves differently.
185
-
186
- #### 3. Preserve scope boundaries
187
-
188
- Each feature should appear in the builder scope that matches its OOXML ownership:
189
-
190
- * **Workbook scope**: workbook-wide metadata, protection, named ranges, shared resources
191
- * **Worksheet scope**: tables, charts, panes, filters, print settings, validations, comments, shapes
192
- * **Row / Cell / Range scope**: formatting or behavior tied to a specific row, cell, or range
193
-
194
- If a low-level feature is workbook-scoped, do not force it into a worksheet-only API just because it is convenient.
195
-
196
- #### 4. Prefer one canonical high-level shape
197
-
198
- For each feature, choose a single primary Facade shape and reuse it across modes:
199
-
200
- * `Xlsxrb.build` and `Xlsxrb.write` should feel structurally similar
201
- * streaming and in-memory APIs may differ internally, but the surface API should remain as close as possible
202
- * differences are acceptable only when memory or ordering constraints make them unavoidable
203
-
204
- #### 5. Keep an escape hatch for advanced cases
205
-
206
- The high-level DSL should cover common and intermediate use cases directly. But it does not need to mirror every obscure OOXML knob one-for-one on day one.
207
-
208
- When a feature has a long tail of advanced attributes:
209
-
210
- * support the common attributes first in the builder DSL
211
- * allow advanced options to pass through as keyword arguments or nested hashes
212
- * avoid blocking implementation progress until every low-level flag has a bespoke DSL method
213
-
214
- #### 6. Never break existing call sites to add symmetry
215
-
216
- High-level API growth must be backward compatible:
217
-
218
- * adding a block form must not remove the options form
219
- * adding an options form must not remove the block form
220
- * existing examples and tests should continue to pass unchanged
221
-
222
- Symmetry is valuable, but compatibility is mandatory.
223
-
224
- ### Facade Rollout Strategy
225
-
226
- When promoting a low-level feature into the high-level DSL, use this order of operations:
227
-
228
- 1. Identify the owning scope (`WorkbookBuilder`, `WorksheetBuilder`, `StreamWriter`, or a nested builder)
229
- 2. Add the options form
230
- 3. Add the block form if the shape becomes nested or verbose
231
- 4. Ensure the streaming and in-memory APIs expose the same concept with the same names whenever possible
232
- 5. Document the shortest example and the richer builder example together
233
-
234
- This keeps the public API coherent as coverage grows.
235
-
236
- ### High-Priority Features For Facade Promotion
237
-
238
- The following low-level features are especially strong candidates for high-level exposure because they are common, composable, and fit the chart/style pattern well:
239
-
240
- * hyperlinks
241
- * auto filters and sort state
242
- * data validation
243
- * conditional formatting
244
- * tables
245
- * comments
246
- * freeze/split panes and selection
247
- * page setup, margins, header/footer, print options
248
- * workbook and sheet protection
249
- * defined names, print area, print titles
250
- * shapes and images
251
- * pivot tables
252
- * document properties
253
-
254
- These should be treated as backlog for Facade parity, not as permanently low-level-only features.
255
-
256
- ---
257
-
258
- ## Data-Flow & Lifecycle
259
-
260
- ### `Xlsxrb.read(source)` — In-Memory Read
261
-
262
- ```
263
- source (path / IO)
264
- │
265
- ▼
266
- Ooxml::ZipReader ── extracts ZIP entries ──► raw bytes per part
267
- │
268
- ▼
269
- Ooxml::SharedStringsParser ── SAX parse xl/sharedStrings.xml ──► string table (Array)
270
- Ooxml::StylesParser ── SAX parse xl/styles.xml ──► styles hash
271
- Ooxml::WorkbookParser ── SAX parse xl/workbook.xml ──► sheet list
272
- │
273
- ▼ (for each sheet)
274
- Ooxml::WorksheetParser ── SAX parse xl/worksheets/sheetN.xml ──►
275
- │ yields (row_index, cells_array, row_attrs, unmapped)
276
- ▼
277
- Elements::Cell / Row / Column / Worksheet
278
- │
279
- ▼
280
- Elements::Workbook ◄── assembled from all worksheets
281
- ```
282
-
283
- ### `Xlsxrb.write(target, workbook)` — In-Memory Write
284
-
285
- ```
286
- Elements::Workbook
287
- │
288
- ▼ (for each worksheet)
289
- Ooxml::WorksheetWriter ── converts Row/Cell → XML fragments ──►
290
- │ streams into Ooxml::ZipWriter entry
291
- ▼
292
- Ooxml::WorkbookWriter ── writes workbook.xml, styles.xml, sharedStrings.xml,
293
- │ [Content_Types].xml, .rels
294
- ▼
295
- Ooxml::ZipWriter ── writes ZIP output ──► target (path / IO)
296
- ```
297
-
298
- ### `Xlsxrb.read(source, &block)` — Streaming Read
299
-
300
- ```
301
- source (path / IO / binary string)
302
- │
303
- ▼
304
- Ooxml::ZipReader ── locates xl/sharedStrings.xml, xl/worksheets/sheetN.xml
305
- │
306
- ▼ (SAX parse SST first — kept in memory as a flat Array of strings)
307
- │
308
- ▼ (then SAX stream worksheet with StreamRow lazy cell scanner)
309
- Ooxml::WorksheetParser ── yields StreamRow to caller's block
310
- │
311
- ▼
312
- caller's block receives StreamRow, streams cells via each_cell with O(1) memory
313
- ```
314
-
315
- Key memory invariant: only **one Row / Cell** (plus the shared-string table) is parsed at any time.
316
-
317
- ### `Xlsxrb.write(target, &block)` — Streaming Write
318
-
319
- ```
320
- caller's block
321
- │
322
- ▼ block receives a StreamWriter context object
323
- │ context.add_row([val1, val2, ...])
324
- │
325
- ▼
326
- Ooxml::WorksheetWriter ── converts array → <row><c>…</c></row> XML
327
- │ writes directly to ZipWriter entry stream
328
- ▼
329
- Ooxml::ZipWriter ── compresses & writes to target
330
- ```
331
-
332
- Key memory invariant: rows are written and flushed immediately; no row Array accumulates.
333
-
334
- ---
335
-
336
- ## Streaming Internals
337
-
338
- ### Unified Event-Based Streaming (`Ooxml::Event`)
339
-
340
- To unify parsing across both streaming and in-memory paths, the OOXML layer utilizes a unified event-based parsing model. Individual parsers (such as `WorksheetParser` and `SharedStringsParser`) implement a streaming `each_event` method that emits a sequence of `Xlsxrb::Ooxml::Event` objects.
341
-
342
- An event contains:
343
- - `type`: a Symbol representing the event type (e.g., `:row_start`, `:cell`, `:row_end`, `:column`, `:hyperlink`, `:sst_item`).
344
- - `args`: an Array containing the event data arguments.
345
- - `source`: a Hash providing context for error reporting (e.g., `{ part: "xl/worksheets/sheet1.xml", row: 0, cell: "A1" }`).
346
-
347
- #### Event Vocabulary:
348
- 1. **Worksheet Events:**
349
- - `:row_start` - `args: [row_index, attrs]`
350
- - `:cell` - `args: [ref, type, style_index, value, formula]`
351
- - `:row_end` - `args: []`
352
- - `:column` - `args: [min, max, width, hidden, custom_width, outline_level]`
353
- - `:hyperlink` - `args: [ref, rid, display, tooltip, location]`
354
- 2. **Shared Strings (SST) Events:**
355
- - `:sst_item` - `args: [string_value]`
356
-
357
- The streaming parser `each_row` (or `parse`) consumes the event stream and folds it into raw row hashes, maintaining a minimal state machine and constant memory footprint.
358
-
359
- ### ZIP Streaming
360
-
361
- `Ooxml::ZipReader` scans local file headers sequentially using `Zlib::Inflate`. It does **not** seek to the central directory — this allows reading from non-seekable IO (pipes, HTTP streams).
362
-
363
- `Ooxml::ZipWriter` writes local file headers immediately, accumulates a central directory index in memory (entry names + offsets only), and writes the central directory + EOCD at `#close`.
364
-
365
- ---
366
-
367
- ## `unmapped_data` & Forward-Compatibility
368
-
369
- When the Ooxml layer encounters an XML element or attribute not in its recognized set:
370
-
371
- 1. **Capture**: The element is stored as a Hash `{ tag: String, attrs: Hash, children: Array, text: String? }`.
372
- 2. **Attach**: The Hash is pushed onto the nearest recognized parent's `unmapped_children` array.
373
- 3. **Surface**: The Elements layer receives these as the `unmapped_data` field — a Hash keyed by parent-context (e.g. `{ row: [...], cell: [...], worksheet: [...] }`).
374
- 4. **Restore**: During write-back (`Ooxml::WorksheetWriter`), `unmapped_data` entries are re-serialized to XML in their original order using `XmlBuilder`, preserving any future spec extensions or vendor-specific markup.
375
-
376
- This ensures that reading then writing an XLSX file does not silently discard unknown content.
377
-
378
- ---
379
-
380
- ## Error Handling & Validation Boundaries
381
-
382
- ### Ooxml Layer (parse-time)
383
-
384
- * **Never raises** on unexpected XML content. Unrecognized elements → `unmapped_data`. Malformed attribute values → stored as-is (raw strings).
385
- * **Raises** only on structural corruption that prevents further parsing (e.g., truncated ZIP, invalid UTF-8, ZIP local header CRC mismatch).
386
-
387
- ### Elements Layer (model-time)
388
-
389
- * Each `Data` class exposes `errors` (frozen Array of String) and `valid?` (`errors.empty?`).
390
- * Validation is performed at construction time:
391
- - `Cell`: value type check, column/row index range
392
- - `Row`: index ≥ 0, cells array consistency
393
- - `Worksheet`: name present, unique row indices
394
- - `Workbook`: at least one sheet, unique sheet names
395
- * Invalid objects **are still created** — the caller decides how to handle `valid? == false`.
396
-
397
- ### Facade Layer
398
-
399
- * `Xlsxrb.read` / `.foreach`: propagate Ooxml-layer structural exceptions. Content-level issues appear in `errors` on returned objects.
400
- * `Xlsxrb.write` / `.generate`: validate the `Workbook` / row data at the boundary and raise `Xlsxrb::Error` for fatal issues (e.g., nil target path). Non-fatal issues (e.g., value truncation) are silently handled.
401
-
402
- ---
403
-
404
- ## Benefits of this Approach
405
-
406
- * **Rubyish Interface:** Methods like `foreach` and `generate` follow Ruby's standard library conventions (e.g., `CSV.foreach`).
407
- * **Clean Namespace:** Users only interact with the `Xlsxrb` module. Internal models are safely isolated within `Elements`.
408
- * **Safety & LSP Support:** `Data` objects provide clear property definitions for editor autocomplete.
409
- * **Constant Memory Streaming:** Both read and write paths support row-at-a-time processing suitable for millions of rows.
410
- * **Future-Proofing:** The `unmapped_data` mechanism and layered design accommodate future features without rewriting the underlying XML logic.
411
-
412
- ---
413
-
414
- ## Facade Quality Gates
415
-
416
- Every new high-level DSL feature must satisfy the following quality rules before it is considered complete.
417
-
418
- ### 1. Both API paths must be covered
419
-
420
- If a feature is intended to exist in both writing modes, tests must cover:
421
-
422
- * `Xlsxrb.build` / `Xlsxrb.write`
423
- * `Xlsxrb.write`
424
-
425
- If a feature can only exist in one mode for a technical reason, that restriction must be documented explicitly in code comments and user-facing docs.
426
-
427
- ### 2. Both entry forms must be covered when both are supported
428
-
429
- If a feature exposes both:
430
-
431
- * an options form
432
- * a block form
433
-
434
- then Facade tests should exercise both forms at least once.
435
-
436
- ### 3. Facade tests are mandatory
437
-
438
- Add or extend tests in `test/facade_test.rb` so the feature is validated at the public API level.
439
-
440
- These tests should verify:
441
-
442
- * the feature can be declared through the high-level DSL
443
- * the generated file can be read back
444
- * the semantic result is present in the parsed workbook or reader output
445
-
446
- ### 4. Contract tests are preferred when structure parity matters
447
-
448
- If the feature should produce equivalent OOXML across streaming and in-memory modes, add or extend `test/contract_test.rb`.
449
-
450
- This is especially important for:
451
-
452
- * shared workbook/worksheet structures
453
- * range-based features
454
- * settings that should serialize identically regardless of API path
455
-
456
- ### 5. E2E coverage is required for new structural output
457
-
458
- If a feature introduces new XML elements, attributes, relationships, or package parts, add an interoperability or E2E test.
459
-
460
- At minimum, verify one of:
461
-
462
- * Open XML SDK validation passes
463
- * the generated XML parts contain the expected structure and are accepted by the reader
464
-
465
- ### 6. Documentation must ship with the feature
466
-
467
- Every new high-level feature should update user-facing docs with:
468
-
469
- * one short example
470
- * one richer example if the feature has a block form or nested configuration
471
- * any important streaming vs in-memory limitation
472
-
473
- ### 7. Keep surface area smaller than implementation detail
474
-
475
- Do not promote every low-level flag into a top-level public method immediately.
476
-
477
- Prefer this order:
478
-
479
- 1. common user-facing options
480
- 2. nested builder methods for grouped concepts
481
- 3. advanced keyword passthrough for rare flags
482
-
483
- This keeps the DSL readable while still allowing high feature coverage.
484
-
485
- ### 8. Backward compatibility is a release gate
486
-
487
- A feature is not complete if it improves symmetry but breaks older call sites, examples, or tests.
488
-
489
- Backward compatibility must be verified before merging any Facade DSL expansion.
490
-
491
- ---
492
-
493
- ## Testing Strategy
494
-
495
- To ensure library robustness and consistency across execution paths, we organize tests into four distinct layers:
496
-
497
- 1. **Unit Tests (`test/xlsxrb/`):**
498
- - Focus on isolated components (such as parsers and writers) without external system dependencies.
499
- - Includes **Round-trip testing** to verify that generated XML can be successfully parsed back by the reader.
500
- - Run via: `bundle exec rake test:unit`
501
-
502
- 2. **Contract Tests (`test/contract/`):**
503
- - Ensures semantic parity between the Streaming and In-Memory API paths.
504
- - Operates by executing identical data scenarios on both APIs and asserting that they serialize to equivalent structures.
505
- - Run via: `bundle exec rake test:contract`
506
-
507
- 3. **Interop (E2E) Tests (`test/e2e/`):**
508
- - Exercises real-world interoperability by validating generated files using the official .NET-based **Open XML SDK** validator, and reading spreadsheets dynamically created by the SDK.
509
- - Run via: `bundle exec rake test:e2e`
510
-
511
- 4. **Visual Examples & VRT (`test/visual/`):**
512
- - **Living Documentation:** Compiles visual DSL scripts under `examples/visual/` into the [Visual Examples Gallery](visual/README.md).
513
- - **Visual Regression Testing:** Renders the generated spreadsheets into PNG files using headless LibreOffice Calc, and calculates pixel differences against reference baselines using ImageMagick.
514
- - Run via: `bundle exec rake test:visual`
data/docs/DEVELOPMENT.md DELETED
@@ -1,133 +0,0 @@
1
- # Development & Contribution Guide
2
-
3
- This document outlines the internal development workflow, test commands, and E2E testing policies for contributors working on `xlsxrb`.
4
-
5
- ## Test Commands
6
-
7
- To run the different tiers of our testing strategy:
8
-
9
- 1. **Unit Tests:**
10
- ```bash
11
- bundle exec rake test:unit
12
- ```
13
- 2. **Contract Tests:**
14
- ```bash
15
- bundle exec rake test:contract
16
- ```
17
- 3. **Property-Based Tests (PBT):**
18
- ```bash
19
- bundle exec rake test:pbt
20
- ```
21
- 4. **Memory & Performance Tests:**
22
- ```bash
23
- bundle exec rake test:perf
24
- ```
25
- 5. **Interoperability (E2E) Tests:**
26
- Requires .NET SDK to be installed (pre-configured in Dev Container).
27
- ```bash
28
- bundle exec rake test:e2e
29
- ```
30
- 6. **Visual Regression Tests (VRT):**
31
- Requires LibreOffice, ImageMagick, and `poppler-utils`.
32
- ```bash
33
- bundle exec rake test:visual
34
- ```
35
- 7. **Run All Tests:**
36
- ```bash
37
- bundle exec rake test
38
- ```
39
-
40
- ---
41
-
42
- ## Development Workflow
43
-
44
- High-level API expansion follows the Facade rules documented in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). In short: if a low-level writer feature is stable, the default expectation is that it should eventually be exposed through the high-level DSL as well, with consistent naming, both streaming and in-memory coverage, backward-compatible options/block forms where practical, and matching Facade-level tests.
45
-
46
- To ensure systematic progress, reliable round-trip compatibility, and strict adherence to the ECMA-376 specification, we follow this iterative development cycle for each new feature:
47
-
48
- 1. **Select a Feature:** Choose a specific element or behavior from the specification to implement.
49
- 2. **Writer Unit Tests:** Write unit tests for the Writer component targeting this feature.
50
- 3. **Writer Implementation:** Implement the Writer functionality.
51
- 4. **Run Writer Tests:** Execute the Writer unit tests. If they fail, return to step 3.
52
- 5. **Writer E2E & Validation:** Test the Writer's generated XLSX file using the Open XML SDK. This includes structural validation using `OpenXmlValidator`. If the test or validation fails, return to step 2.
53
- 6. **Reader Unit Tests:** Write unit tests for the Reader component. Crucially, include round-trip tests to ensure the Reader can accurately parse the output of your Writer.
54
- 7. **Reader Implementation:** Implement the Reader functionality.
55
- 8. **Run Reader Tests:** Execute the Reader unit tests. If they fail, return to step 6 or 7. If the round-trip test reveals a structural flaw in the Writer's output, return all the way back to step 2.
56
- 9. **Reader E2E:** Verify that the Reader can successfully parse a valid XLSX file generated by the Open XML SDK that includes the new feature. If it fails, return to step 6 or 7.
57
- 10. **Full Test Suite:** Run the entire test suite (`rake test`). If any tests fail, trace back to the appropriate step.
58
- 11. **Commit:** Commit the changes. The commit message must clearly describe the specific feature implemented in this cycle.
59
- 12. **Next Feature:** Proceed to the next feature and return to step 1.
60
-
61
- ---
62
-
63
- ## E2E Policy
64
-
65
- E2E tests are required for every new feature. Omitting them is the exception, not the rule, and requires explicit justification.
66
-
67
- A strong signal that E2E should not be omitted: if you are adding a new XML element, a new attribute on a top-level structure, or a new public API parameter, E2E is expected.
68
-
69
- Omission is only acceptable when **all** of the following hold:
70
-
71
- 1. The change adds a minor attribute to an XML structure that is **already exercised end-to-end** by an existing E2E scenario for the same element.
72
- 2. No new XML element or branch is introduced.
73
- 3. Unit tests and round-trip tests fully cover the new behaviour.
74
- 4. `rake test` passes with Open XML SDK validation included.
75
- 5. The commit message explicitly names the existing E2E scenario that provides coverage and states why a new scenario adds no value.
76
-
77
- ---
78
-
79
- ## Development via Dev Container
80
-
81
- The project is pre-configured with a Dev Container to simplify local environment setup (installing .NET, LibreOffice, ImageMagick, and Noto fonts).
82
-
83
- ### VS Code (GUI)
84
- You can open this repository in VS Code and select **"Dev Containers: Reopen in Container"** from the Command Palette.
85
-
86
- ### Terminal (Devcontainer CLI)
87
- If you prefer to use the terminal instead of VS Code, you can run the devcontainer using the official `@devcontainers/cli`:
88
-
89
- 1. Install the CLI on your host machine (if not already installed):
90
- ```bash
91
- npm install -g @devcontainers/cli
92
- ```
93
- 2. Use the helper script `bin/devcontainer` to start and interact with the container:
94
- - **Interactive shell** (Automatically shares your host's Git/AI tool configs as read-only):
95
- ```bash
96
- bin/devcontainer
97
- ```
98
- - **Run commands directly**:
99
- ```bash
100
- bin/devcontainer rake test
101
- ```
102
-
103
- ### Customizing with Personal Overrides (e.g., Dotfiles, API Keys, Shell History)
104
-
105
- The `bin/devcontainer` script dynamically parses a personal configuration patch and maps it to devcontainer CLI flags on startup. This is a workaround for a specification limit of devcontainer CLI's `--override-config` flag, which acts as a complete replacement of the configuration rather than a partial merge, wiping out base configurations like build settings.
106
-
107
- To configure this, create a JSON (or JSONC) file at `${XDG_CONFIG_HOME:-~/.config}/devcontainer/override.jsonc` (or `override.json`) on your host machine.
108
-
109
- **Example: Persisting histories, forwarding API keys, and Auto-installing Dotfiles**
110
- ```json
111
- {
112
- "mounts": [
113
- // Volume for general persistent directories (e.g., Claude Code history in ~/.local/share/claude)
114
- "type=volume,source=xlsxrb-local-share,target=/home/vscode/.local/share"
115
- ],
116
- "containerEnv": {
117
- // Explicitly forward API keys from your host shell to the container environment
118
- "OPENAI_API_KEY": "${localEnv:OPENAI_API_KEY}",
119
- "ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}",
120
- "GEMINI_API_KEY": "${localEnv:GEMINI_API_KEY}"
121
- },
122
- "dotfiles": {
123
- "repository": "https://github.com/<your-github-username>/dotfiles.git",
124
- "targetPath": "~/dotfiles",
125
- "installCommand": "install.sh"
126
- }
127
- }
128
- ```
129
-
130
- You can also use a custom file path by exporting the `DEVCONTAINER_OVERRIDE_CONFIG` environment variable in your host shell:
131
- ```bash
132
- export DEVCONTAINER_OVERRIDE_CONFIG="/path/to/your/custom-override.json"
133
- ```