philiprehberger-csv_builder 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9810b1f04bb59659c6636dab4aefe4213551a4bdd8b75b5ad9b4311e2d42694b
4
- data.tar.gz: 4b9e23bc25d7cbf74ca3aaaf9d549ec2de92a5b43fd4aca0f061d08b21240615
3
+ metadata.gz: 2f0d8a20ab069951c75151dcb5d3892a7c86ba7ed94b8ab09a9ef03baf2bf0a2
4
+ data.tar.gz: 7b1efd9f802aa42886292127d3a4417f0aabaf5d5673049819c915cd3b4e5b76
5
5
  SHA512:
6
- metadata.gz: 9b6811067d8f0813b944ed4a0a562a4f90220be41733953209000b3287552a663e0774f66ac2f53053dbccce04e6f90f30e1e43d9b727da7b3c0857b5ce93e27
7
- data.tar.gz: ee2920705b11bc315d152ef2c5a781164150d5e47181f61d01eaad1390f1e23c6734eaf8c14a8f588df78985b13f82d1f10f0cf53473c3c00c607b1c956a38b1
6
+ metadata.gz: 8f99b2921a5871ac26e0d516f773cf644a310c6f3a72f2b999acb2f18fa4c2f3b049723421bd0247344736e65d7b0c186d9091bafdbde955fea80d3fdaf52b50
7
+ data.tar.gz: 791c95f2680441487f40314c5e265c6b4f7294c2260ec21e67670572f25b817ee958f7cd506794482b30447fc64b069facfc6f47a612b567f3828a4a1fdda1d5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-04-03
11
+
12
+ ### Added
13
+ - Custom CSV delimiters via `delimiter:` option
14
+ - Custom quote character via `quote_char:` option
15
+ - Column header aliasing via `header:` option on columns
16
+ - Record filtering via `filter`
17
+ - Auto-incrementing row numbers via `row_number`
18
+ - Streaming output via `to_io`
19
+
20
+ ## [0.1.5] - 2026-03-31
21
+
22
+ ### Added
23
+ - Add GitHub issue templates, dependabot config, and PR template
24
+
25
+ ## [0.1.4] - 2026-03-31
26
+
27
+ ### Changed
28
+ - Standardize README badges, support section, and license format
29
+
10
30
  ## [0.1.3] - 2026-03-24
11
31
 
12
32
  ### Fixed
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Tests](https://github.com/philiprehberger/rb-csv-builder/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-csv-builder/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/philiprehberger-csv_builder.svg)](https://rubygems.org/gems/philiprehberger-csv_builder)
5
- [![License](https://img.shields.io/github/license/philiprehberger/rb-csv-builder)](LICENSE)
5
+ [![Last updated](https://img.shields.io/github/last-commit/philiprehberger/rb-csv-builder)](https://github.com/philiprehberger/rb-csv-builder/commits/main)
6
6
 
7
7
  Declarative CSV builder with column mapping and streaming output
8
8
 
@@ -71,6 +71,109 @@ end
71
71
  builder.to_file('output.csv')
72
72
  ```
73
73
 
74
+ ### Custom Delimiters
75
+
76
+ ```ruby
77
+ builder = Philiprehberger::CsvBuilder.build(records, delimiter: "\t") do
78
+ column :name
79
+ column :email
80
+ end
81
+
82
+ puts builder.to_csv
83
+ # name email
84
+ # Alice alice@example.com
85
+ # Bob bob@example.com
86
+ ```
87
+
88
+ You can also set a custom quote character:
89
+
90
+ ```ruby
91
+ builder = Philiprehberger::CsvBuilder.build(records, quote_char: "'") do
92
+ column :name
93
+ column :email
94
+ end
95
+ ```
96
+
97
+ ### Column Aliases
98
+
99
+ ```ruby
100
+ builder = Philiprehberger::CsvBuilder.build(records) do
101
+ column :name, header: 'Full Name'
102
+ column :email, header: 'Email Address'
103
+ column(:status, header: 'Active?') { |r| r[:active] ? 'Yes' : 'No' }
104
+ end
105
+
106
+ puts builder.to_csv
107
+ # Full Name,Email Address,Active?
108
+ # Alice,alice@example.com,Yes
109
+ # Bob,bob@example.com,No
110
+ ```
111
+
112
+ ### Filtering
113
+
114
+ ```ruby
115
+ builder = Philiprehberger::CsvBuilder.build(records) do
116
+ column :name
117
+ column :email
118
+ filter { |r| r[:active] }
119
+ end
120
+
121
+ puts builder.to_csv
122
+ # name,email
123
+ # Alice,alice@example.com
124
+ ```
125
+
126
+ Multiple filters are combined with AND logic:
127
+
128
+ ```ruby
129
+ builder = Philiprehberger::CsvBuilder.build(records) do
130
+ column :name
131
+ filter { |r| r[:active] }
132
+ filter { |r| r[:name].start_with?('A') }
133
+ end
134
+ ```
135
+
136
+ ### Row Numbers
137
+
138
+ ```ruby
139
+ builder = Philiprehberger::CsvBuilder.build(records) do
140
+ column :name
141
+ column :email
142
+ row_number
143
+ end
144
+
145
+ puts builder.to_csv
146
+ # #,name,email
147
+ # 1,Alice,alice@example.com
148
+ # 2,Bob,bob@example.com
149
+ ```
150
+
151
+ Customize the header label:
152
+
153
+ ```ruby
154
+ row_number(header: 'Row')
155
+ ```
156
+
157
+ ### Streaming
158
+
159
+ ```ruby
160
+ File.open('output.csv', 'w') do |file|
161
+ builder = Philiprehberger::CsvBuilder.build(records) do
162
+ column :name
163
+ column :email
164
+ end
165
+
166
+ builder.to_io(file)
167
+ end
168
+ ```
169
+
170
+ Works with any IO object, including `StringIO`:
171
+
172
+ ```ruby
173
+ io = StringIO.new
174
+ builder.to_io(io)
175
+ ```
176
+
74
177
  ### Headers
75
178
 
76
179
  ```ruby
@@ -86,10 +189,13 @@ builder.headers # => ["name", "email"]
86
189
 
87
190
  | Method | Description |
88
191
  |--------|-------------|
89
- | `CsvBuilder.build(records, &block)` | Build a CSV using the column DSL |
90
- | `Builder#column(name, &block)` | Define a column with optional transform |
192
+ | `CsvBuilder.build(records, delimiter:, quote_char:, &block)` | Build a CSV using the column DSL |
193
+ | `Builder#column(name, header:, &block)` | Define a column with optional alias and transform |
194
+ | `Builder#filter(&block)` | Filter records (block returns true to include) |
195
+ | `Builder#row_number(header:)` | Add auto-incrementing row number column |
91
196
  | `Builder#to_csv` | Generate CSV as a string |
92
197
  | `Builder#to_file(path)` | Write CSV to a file |
198
+ | `Builder#to_io(io)` | Stream CSV to any IO object |
93
199
  | `Builder#headers` | Return column header names |
94
200
 
95
201
  ## Development
@@ -100,6 +206,24 @@ bundle exec rspec
100
206
  bundle exec rubocop
101
207
  ```
102
208
 
209
+ ## Support
210
+
211
+ If you find this project useful:
212
+
213
+ ⭐ [Star the repo](https://github.com/philiprehberger/rb-csv-builder)
214
+
215
+ 🐛 [Report issues](https://github.com/philiprehberger/rb-csv-builder/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
216
+
217
+ 💡 [Suggest features](https://github.com/philiprehberger/rb-csv-builder/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
218
+
219
+ ❤️ [Sponsor development](https://github.com/sponsors/philiprehberger)
220
+
221
+ 🌐 [All Open Source Projects](https://philiprehberger.com/open-source-packages)
222
+
223
+ 💻 [GitHub Profile](https://github.com/philiprehberger)
224
+
225
+ 🔗 [LinkedIn Profile](https://www.linkedin.com/in/philiprehberger)
226
+
103
227
  ## License
104
228
 
105
- MIT
229
+ [MIT](LICENSE)
@@ -14,19 +14,45 @@ module Philiprehberger
14
14
  attr_reader :records
15
15
 
16
16
  # @param records [Array] the source records
17
- def initialize(records)
17
+ # @param delimiter [String] the column separator (default: ",")
18
+ # @param quote_char [String] the quote character (default: '"')
19
+ def initialize(records, delimiter: ',', quote_char: '"')
18
20
  @records = records
19
21
  @columns = []
22
+ @filters = []
23
+ @row_number_header = nil
24
+ @delimiter = delimiter
25
+ @quote_char = quote_char
20
26
  end
21
27
 
22
28
  # Define a column
23
29
  #
24
30
  # @param name [Symbol, String] the column name
31
+ # @param header [String, nil] optional custom header label
25
32
  # @yield [record] optional block to transform the value
26
33
  # @yieldparam record [Object] the source record
27
34
  # @return [self]
28
- def column(name, &block)
29
- @columns << Column.new(name, &block)
35
+ def column(name, header: nil, &block)
36
+ @columns << Column.new(name, header: header, &block)
37
+ self
38
+ end
39
+
40
+ # Add a filter to exclude records
41
+ #
42
+ # @yield [record] block that returns true to include the record
43
+ # @yieldparam record [Object] the source record
44
+ # @return [self]
45
+ def filter(&block)
46
+ @filters << block
47
+ self
48
+ end
49
+
50
+ # Add an auto-incrementing row number as the first column
51
+ #
52
+ # @param header [String] the header label for the row number column
53
+ # @return [self]
54
+ def row_number(header: '#')
55
+ @row_number_header = header
30
56
  self
31
57
  end
32
58
 
@@ -34,17 +60,29 @@ module Philiprehberger
34
60
  #
35
61
  # @return [Array<String>]
36
62
  def headers
37
- @columns.map(&:header)
63
+ base = @columns.map(&:header)
64
+ @row_number_header ? [@row_number_header] + base : base
65
+ end
66
+
67
+ # Return the filtered records
68
+ #
69
+ # @return [Array]
70
+ def filtered_records
71
+ result = @records
72
+ @filters.each do |f|
73
+ result = result.select(&f)
74
+ end
75
+ result
38
76
  end
39
77
 
40
78
  # Generate the CSV as a string
41
79
  #
42
80
  # @return [String]
43
81
  def to_csv
44
- CSV.generate do |csv|
82
+ CSV.generate(**csv_options) do |csv|
45
83
  csv << headers
46
- @records.each do |record|
47
- csv << @columns.map { |col| col.extract(record) }
84
+ filtered_records.each_with_index do |record, index|
85
+ csv << build_row(record, index)
48
86
  end
49
87
  end
50
88
  end
@@ -56,6 +94,35 @@ module Philiprehberger
56
94
  def to_file(path)
57
95
  File.write(path, to_csv)
58
96
  end
97
+
98
+ # Stream CSV to any IO object
99
+ #
100
+ # @param io [IO, StringIO] the IO object to write to
101
+ # @return [void]
102
+ def to_io(io)
103
+ csv = CSV.new(io, **csv_options)
104
+ csv << headers
105
+ filtered_records.each_with_index do |record, index|
106
+ csv << build_row(record, index)
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ # @return [Hash] CSV library options
113
+ def csv_options
114
+ { col_sep: @delimiter, quote_char: @quote_char }
115
+ end
116
+
117
+ # Build a single row array for the given record
118
+ #
119
+ # @param record [Object] the source record
120
+ # @param index [Integer] zero-based row index
121
+ # @return [Array]
122
+ def build_row(record, index)
123
+ row = @columns.map { |col| col.extract(record) }
124
+ @row_number_header ? [index + 1] + row : row
125
+ end
59
126
  end
60
127
  end
61
128
  end
@@ -11,9 +11,11 @@ module Philiprehberger
11
11
  attr_reader :transform
12
12
 
13
13
  # @param name [Symbol, String] the column name (also used as the record key)
14
+ # @param header [String, nil] optional custom header label
14
15
  # @param transform [Proc, nil] optional block to transform the value
15
- def initialize(name, &transform)
16
+ def initialize(name, header: nil, &transform)
16
17
  @name = name.to_sym
18
+ @custom_header = header
17
19
  @transform = block_given? ? transform : nil
18
20
  end
19
21
 
@@ -37,7 +39,7 @@ module Philiprehberger
37
39
  #
38
40
  # @return [String]
39
41
  def header
40
- @name.to_s
42
+ @custom_header || @name.to_s
41
43
  end
42
44
  end
43
45
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module CsvBuilder
5
- VERSION = '0.1.3'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -11,14 +11,16 @@ module Philiprehberger
11
11
  # Build a CSV from records using a declarative DSL
12
12
  #
13
13
  # @param records [Array] the source records
14
+ # @param delimiter [String] the column separator (default: ",")
15
+ # @param quote_char [String] the quote character (default: '"')
14
16
  # @yield [builder] the builder instance for defining columns
15
17
  # @yieldparam builder [Builder]
16
18
  # @return [Builder] the configured builder
17
19
  # @raise [Error] if no block is given
18
- def self.build(records, &block)
20
+ def self.build(records, delimiter: ',', quote_char: '"', &block)
19
21
  raise Error, 'A block is required' unless block
20
22
 
21
- builder = Builder.new(records)
23
+ builder = Builder.new(records, delimiter: delimiter, quote_char: quote_char)
22
24
  builder.instance_eval(&block)
23
25
  builder
24
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-csv_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.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-03-25 00:00:00.000000000 Z
11
+ date: 2026-04-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Build CSV files from record collections using a declarative DSL with
14
14
  column definitions, custom transforms, and file output support.
@@ -25,11 +25,11 @@ files:
25
25
  - lib/philiprehberger/csv_builder/builder.rb
26
26
  - lib/philiprehberger/csv_builder/column.rb
27
27
  - lib/philiprehberger/csv_builder/version.rb
28
- homepage: https://github.com/philiprehberger/rb-csv-builder
28
+ homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-csv_builder
29
29
  licenses:
30
30
  - MIT
31
31
  metadata:
32
- homepage_uri: https://github.com/philiprehberger/rb-csv-builder
32
+ homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-csv_builder
33
33
  source_code_uri: https://github.com/philiprehberger/rb-csv-builder
34
34
  changelog_uri: https://github.com/philiprehberger/rb-csv-builder/blob/main/CHANGELOG.md
35
35
  bug_tracker_uri: https://github.com/philiprehberger/rb-csv-builder/issues