philiprehberger-csv_builder 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +49 -1
- data/lib/philiprehberger/csv_builder/builder.rb +51 -6
- data/lib/philiprehberger/csv_builder/version.rb +1 -1
- data/lib/philiprehberger/csv_builder.rb +8 -2
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 921263332f87142c3cfcdc4483e601a9ab51ce54cd53481988cc75b3b46eb7ed
|
|
4
|
+
data.tar.gz: fa347e0127c702eca2a1c4104ca1b5eada5c43458660884c9b6f9028753a8f80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9fcc0e96415c5b1d5b55e257963c48881ec879809b652ae7604138d524df50264577650e60bb239cf6b838b5fbae2bd03e6b76d5d6cdb0b3225528dd6a5d9a6
|
|
7
|
+
data.tar.gz: c7a65b052c99facd38baee4f16f2cca0bbd36ae5906ce6ca1302da36797c6b1b585066e096b1aec8432dacbd8d1167ab23f9cb522123e4253c3088b8643c14b4
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-04-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- UTF-8 BOM support via `bom: true` option for Excel-compatible output
|
|
14
|
+
- Custom output encoding via `encoding:` option
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Bug report template now requires Ruby version and gem version fields
|
|
18
|
+
- Feature request template now includes "Alternatives considered" field
|
|
19
|
+
|
|
20
|
+
## [0.4.0] - 2026-04-10
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `Builder#footer(&block)` appends a computed summary row after all data rows
|
|
24
|
+
- `Builder#limit(n)` caps output to N rows
|
|
25
|
+
- `Builder#offset(n)` skips the first N filtered/sorted records
|
|
26
|
+
|
|
10
27
|
## [0.3.0] - 2026-04-09
|
|
11
28
|
|
|
12
29
|
### Added
|
|
@@ -57,3 +74,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
57
74
|
- File output via `to_file`
|
|
58
75
|
- Support for hash records with symbol and string keys
|
|
59
76
|
- Proper CSV escaping for values with commas and quotes
|
|
77
|
+
|
|
78
|
+
[0.5.0]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.5.0
|
|
79
|
+
[0.4.0]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.4.0
|
|
80
|
+
[0.3.0]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.3.0
|
|
81
|
+
[0.2.0]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.2.0
|
|
82
|
+
[0.1.5]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.1.5
|
|
83
|
+
[0.1.4]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.1.4
|
|
84
|
+
[0.1.3]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.1.3
|
|
85
|
+
[0.1.2]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.1.2
|
|
86
|
+
[0.1.1]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.1.1
|
|
87
|
+
[0.1.0]: https://github.com/philiprehberger/rb-csv-builder/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -173,6 +173,51 @@ builder = Philiprehberger::CsvBuilder.build(records) do
|
|
|
173
173
|
end
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
### Footer Row
|
|
177
|
+
|
|
178
|
+
Append a computed summary row after all data rows:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
builder = Philiprehberger::CsvBuilder.build(records) do
|
|
182
|
+
column :name
|
|
183
|
+
column(:amount) { |r| r[:amount] }
|
|
184
|
+
footer { |recs| ['Total', recs.sum { |r| r[:amount] }] }
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Pagination
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
builder = Philiprehberger::CsvBuilder.build(records) do
|
|
192
|
+
column :name
|
|
193
|
+
offset 10 # skip first 10 records
|
|
194
|
+
limit 25 # output at most 25 rows
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`offset` and `limit` are applied after filtering and sorting.
|
|
199
|
+
|
|
200
|
+
### Excel-Compatible Output (BOM)
|
|
201
|
+
|
|
202
|
+
Prepend a UTF-8 BOM so Excel opens the CSV with correct encoding:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
builder = Philiprehberger::CsvBuilder.build(records, bom: true) do
|
|
206
|
+
column :name
|
|
207
|
+
column :email
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
builder.to_file('export.csv')
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Custom Encoding
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
builder = Philiprehberger::CsvBuilder.build(records, encoding: 'ISO-8859-1') do
|
|
217
|
+
column :name
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
176
221
|
### Streaming
|
|
177
222
|
|
|
178
223
|
```ruby
|
|
@@ -208,10 +253,13 @@ builder.headers # => ["name", "email"]
|
|
|
208
253
|
|
|
209
254
|
| Method | Description |
|
|
210
255
|
|--------|-------------|
|
|
211
|
-
| `CsvBuilder.build(records, delimiter:, quote_char:, &block)` | Build a CSV using the column DSL |
|
|
256
|
+
| `CsvBuilder.build(records, delimiter:, quote_char:, bom:, encoding:, &block)` | Build a CSV using the column DSL |
|
|
212
257
|
| `Builder#column(name, header:, &block)` | Define a column with optional alias and transform |
|
|
213
258
|
| `Builder#filter(&block)` | Filter records (block returns true to include) |
|
|
214
259
|
| `Builder#sort_by(direction:, &block)` | Sort records by block key (`:asc` or `:desc`) |
|
|
260
|
+
| `Builder#footer(&block)` | Append a computed footer row (block receives filtered records) |
|
|
261
|
+
| `Builder#limit(n)` | Cap output to N rows |
|
|
262
|
+
| `Builder#offset(n)` | Skip first N filtered/sorted records |
|
|
215
263
|
| `Builder#row_number(header:)` | Add auto-incrementing row number column |
|
|
216
264
|
| `Builder#to_csv` | Generate CSV as a string |
|
|
217
265
|
| `Builder#to_file(path)` | Write CSV to a file |
|
|
@@ -16,7 +16,9 @@ module Philiprehberger
|
|
|
16
16
|
# @param records [Array] the source records
|
|
17
17
|
# @param delimiter [String] the column separator (default: ",")
|
|
18
18
|
# @param quote_char [String] the quote character (default: '"')
|
|
19
|
-
|
|
19
|
+
# @param bom [Boolean] prepend UTF-8 BOM (default: false)
|
|
20
|
+
# @param encoding [String] output encoding name (default: "UTF-8")
|
|
21
|
+
def initialize(records, delimiter: ',', quote_char: '"', bom: false, encoding: 'UTF-8')
|
|
20
22
|
@records = records
|
|
21
23
|
@columns = []
|
|
22
24
|
@filters = []
|
|
@@ -25,6 +27,11 @@ module Philiprehberger
|
|
|
25
27
|
@quote_char = quote_char
|
|
26
28
|
@sort_by = nil
|
|
27
29
|
@sort_direction = :asc
|
|
30
|
+
@limit_count = nil
|
|
31
|
+
@offset_count = nil
|
|
32
|
+
@footer_block = nil
|
|
33
|
+
@bom = bom
|
|
34
|
+
@encoding = encoding
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
# Sort records before CSV output
|
|
@@ -36,13 +43,42 @@ module Philiprehberger
|
|
|
36
43
|
# @raise [Error] if direction is not :asc or :desc
|
|
37
44
|
def sort_by(direction: :asc, &block)
|
|
38
45
|
raise Error, 'A block is required for sort_by' unless block
|
|
39
|
-
raise Error, "direction must be :asc or :desc (got #{direction.inspect})" unless %i[asc
|
|
46
|
+
raise Error, "direction must be :asc or :desc (got #{direction.inspect})" unless %i[asc
|
|
47
|
+
desc].include?(direction)
|
|
40
48
|
|
|
41
49
|
@sort_by = block
|
|
42
50
|
@sort_direction = direction
|
|
43
51
|
self
|
|
44
52
|
end
|
|
45
53
|
|
|
54
|
+
# Limit the number of output rows
|
|
55
|
+
#
|
|
56
|
+
# @param n [Integer] maximum rows
|
|
57
|
+
# @return [self]
|
|
58
|
+
def limit(n)
|
|
59
|
+
@limit_count = n
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Skip the first N filtered/sorted records
|
|
64
|
+
#
|
|
65
|
+
# @param n [Integer] number of rows to skip
|
|
66
|
+
# @return [self]
|
|
67
|
+
def offset(n)
|
|
68
|
+
@offset_count = n
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Append a computed footer row after all data rows
|
|
73
|
+
#
|
|
74
|
+
# @yield [Array] filtered records
|
|
75
|
+
# @yieldreturn [Array] footer row values
|
|
76
|
+
# @return [self]
|
|
77
|
+
def footer(&block)
|
|
78
|
+
@footer_block = block
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
46
82
|
# Define a column
|
|
47
83
|
#
|
|
48
84
|
# @param name [Symbol, String] the column name
|
|
@@ -94,6 +130,8 @@ module Philiprehberger
|
|
|
94
130
|
result = result.sort_by(&@sort_by)
|
|
95
131
|
result = result.reverse if @sort_direction == :desc
|
|
96
132
|
end
|
|
133
|
+
result = result.drop(@offset_count) if @offset_count
|
|
134
|
+
result = result.first(@limit_count) if @limit_count
|
|
97
135
|
result
|
|
98
136
|
end
|
|
99
137
|
|
|
@@ -101,12 +139,16 @@ module Philiprehberger
|
|
|
101
139
|
#
|
|
102
140
|
# @return [String]
|
|
103
141
|
def to_csv
|
|
104
|
-
|
|
142
|
+
recs = filtered_records
|
|
143
|
+
csv_string = CSV.generate(**csv_options) do |csv|
|
|
105
144
|
csv << headers
|
|
106
|
-
|
|
145
|
+
recs.each_with_index do |record, index|
|
|
107
146
|
csv << build_row(record, index)
|
|
108
147
|
end
|
|
148
|
+
csv << @footer_block.call(recs) if @footer_block
|
|
109
149
|
end
|
|
150
|
+
csv_string = csv_string.encode(@encoding) unless @encoding == 'UTF-8'
|
|
151
|
+
@bom ? "\xEF\xBB\xBF#{csv_string}" : csv_string
|
|
110
152
|
end
|
|
111
153
|
|
|
112
154
|
# Write the CSV to a file
|
|
@@ -114,7 +156,7 @@ module Philiprehberger
|
|
|
114
156
|
# @param path [String] the output file path
|
|
115
157
|
# @return [void]
|
|
116
158
|
def to_file(path)
|
|
117
|
-
File.
|
|
159
|
+
File.binwrite(path, to_csv)
|
|
118
160
|
end
|
|
119
161
|
|
|
120
162
|
# Stream CSV to any IO object
|
|
@@ -122,11 +164,14 @@ module Philiprehberger
|
|
|
122
164
|
# @param io [IO, StringIO] the IO object to write to
|
|
123
165
|
# @return [void]
|
|
124
166
|
def to_io(io)
|
|
167
|
+
io.write("\xEF\xBB\xBF") if @bom
|
|
168
|
+
recs = filtered_records
|
|
125
169
|
csv = CSV.new(io, **csv_options)
|
|
126
170
|
csv << headers
|
|
127
|
-
|
|
171
|
+
recs.each_with_index do |record, index|
|
|
128
172
|
csv << build_row(record, index)
|
|
129
173
|
end
|
|
174
|
+
csv << @footer_block.call(recs) if @footer_block
|
|
130
175
|
end
|
|
131
176
|
|
|
132
177
|
private
|
|
@@ -13,14 +13,20 @@ module Philiprehberger
|
|
|
13
13
|
# @param records [Array] the source records
|
|
14
14
|
# @param delimiter [String] the column separator (default: ",")
|
|
15
15
|
# @param quote_char [String] the quote character (default: '"')
|
|
16
|
+
# @param bom [Boolean] prepend UTF-8 BOM for Excel compatibility (default: false)
|
|
17
|
+
# @param encoding [String] output encoding name (default: "UTF-8")
|
|
16
18
|
# @yield [builder] the builder instance for defining columns
|
|
17
19
|
# @yieldparam builder [Builder]
|
|
18
20
|
# @return [Builder] the configured builder
|
|
19
21
|
# @raise [Error] if no block is given
|
|
20
|
-
def self.build(records, delimiter: ',', quote_char: '"', &block)
|
|
22
|
+
def self.build(records, delimiter: ',', quote_char: '"', bom: false, encoding: 'UTF-8', &block)
|
|
21
23
|
raise Error, 'A block is required' unless block
|
|
22
24
|
|
|
23
|
-
builder = Builder.new(
|
|
25
|
+
builder = Builder.new(
|
|
26
|
+
records,
|
|
27
|
+
delimiter: delimiter, quote_char: quote_char,
|
|
28
|
+
bom: bom, encoding: encoding
|
|
29
|
+
)
|
|
24
30
|
builder.instance_eval(&block)
|
|
25
31
|
builder
|
|
26
32
|
end
|
metadata
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-csv_builder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Build CSV files from record collections using a declarative DSL with
|
|
14
|
-
column definitions, custom transforms,
|
|
14
|
+
column definitions, custom transforms, filtering, sorting, pagination via limit/offset,
|
|
15
|
+
computed footer rows, row numbers, streaming output, and custom delimiters.
|
|
15
16
|
email:
|
|
16
17
|
- me@philiprehberger.com
|
|
17
18
|
executables: []
|