simple_csv 0.2.2 → 1.0.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
- SHA1:
3
- metadata.gz: 00ca4ef7b136f13cf68ada6db2d5eef9b1f37d97
4
- data.tar.gz: a7330cea3184fad6d2c44c9c2237b89a433ea8eb
2
+ SHA256:
3
+ metadata.gz: 3991cdfe21c73188eea30108998f63db2223aacfb3887936f300118f786a4488
4
+ data.tar.gz: 5842b9b5d17f74951041e125405e622c30e21787b3ce5326640b849ad3850a21
5
5
  SHA512:
6
- metadata.gz: 96c993bbe4a663dc0ce36226ba8928836f26a6918526cca40e818b8a26b3906a79af5e8da1445d1c5903ec47f50468c7321d9c2a19edaef7b33ec5c3f5b378a3
7
- data.tar.gz: 084054a3e03682f0f4ef418f74e47f6b1e7e9ef72c87d7189374c9189b7ee1c4ab139ea1b9ffa7e71701c67ed89d3ebc47c1b7b09f8bc96ac07444a2cd588030
6
+ metadata.gz: dbd27b6e8ad4e4021a51866829c6ae285b6e7622fa44370d18fff205c8b49daa379ebf599f40347b7a05337785e17d0737cdcf0ea7a64fc8b7b9290a2caffd99
7
+ data.tar.gz: 0f8135d7a03e7bdb27218b6aa35b96d34288ab729c449ce0bcda51cd2c6700ae207c485095ecc8411fc3886f6e5c0d43937c9df75b6823453a1e5e06a8dd6ac9
data/.gitignore CHANGED
@@ -6,6 +6,6 @@
6
6
  /doc/
7
7
  /pkg/
8
8
  /spec/reports/
9
- /tmp/
10
- /**/output.csv
11
- /**/input.csv
9
+ tmp/*
10
+ !tmp/
11
+ .tool-versions
data/.travis.yml CHANGED
@@ -4,7 +4,11 @@ cache: bundler
4
4
  rvm:
5
5
  - 2.1.0
6
6
  - 2.2.0
7
- - 2.2.2
8
7
  - 2.3.0
9
- - 2.3.3
10
- before_install: gem install bundler -v 1.13.6
8
+ - 2.4.0
9
+ - 2.5.0
10
+ - 2.5.0
11
+ - 2.6.0-preview2
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: 2.6.0-preview2
data/README.md CHANGED
@@ -1,3 +1,317 @@
1
- # Readme
1
+ # SimpleCsv
2
+ SimpleCsv is a simple gem that allows you to interact with CSV's in a more friendly way.
3
+ See the examples given below for more details :)
2
4
 
3
- The documentation has been moved here: [https://sidofc.github.io/projects/simple-csv/](https://sidofc.github.io/projects/simple-csv/)
5
+ ## Status
6
+
7
+ ![Licence](https://img.shields.io/badge/license-MIT-E9573F.svg)
8
+ [![Gem Version](https://img.shields.io/gem/v/simple_csv.svg?colorB=E9573F&style=square)](rubygems.org/gems/simple_csv)
9
+ [![Issues](https://img.shields.io/github/issues/SidOfc/simple_csv.svg)](https://github.com/SidOfc/simple_csv/issues)
10
+ [![Build Status](https://img.shields.io/travis/SidOfc/simple_csv.svg)](https://travis-ci.org/SidOfc/simple_csv)
11
+ [![Coverage Status](https://img.shields.io/coveralls/SidOfc/simple_csv.svg)](https://coveralls.io/github/SidOfc/simple_csv?branch=master)
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'simple_csv'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install simple_csv
30
+
31
+ ## Usage
32
+
33
+ ### General
34
+
35
+ By default, the settings used will be those of `CSV::DEFAULT_OPTIONS` generally.
36
+ `SimpleCsv` sets the `:headers` property to true by default, this is due to the nature of `SimpleCsv`.
37
+
38
+ Headers have to be defined either before reading or generating a CSV.
39
+ Since `:headers` is now `true` by default, `SimpleCsv` will allow `CSV` to parse the first line as headers.
40
+ These headers are then converted in method calls to use within an `SimpleCsv::Reader#each_row` loop.
41
+
42
+ If however, your file lacks headers, you have the ability to set `:has_headers` to false and supply headers manually before calling `SimpleCsv::Reader#each_row`.
43
+ The headers will be picked up and used instead of the first line.
44
+
45
+ #### SimpleCsv default settings
46
+
47
+ These are the settings that will be merged with settings passed through either `SimpleCsv#generate` or `SimpleCsv#read`
48
+
49
+ | setting | value |
50
+ | ---------------------- | --------------------------------------- |
51
+ | `:col_sep` | `","` |
52
+ | `:row_sep` | `:auto` |
53
+ | `:quote_char` | `"\"` |
54
+ | `:field_size_limit` | `nil` |
55
+ | `:converters` | `[:all, :blank_to_nil, :null_to_nil]` |
56
+ | `:unconverted_fields` | `nil` |
57
+ | `:headers` | `true` |
58
+ | `:return_headers` | `false` |
59
+ | `:header_converters` | `nil` |
60
+ | `:skip_blanks` | `false` |
61
+ | `:force_quotes` | `true` |
62
+ | `:skip_lines` | `nil` |
63
+
64
+ The following settings differ from the `CSV::DEFAULT_OPTIONS`
65
+
66
+ * `:converters` is set to `[:all, :blank_to_nil, :null_to_nil]`
67
+ * `:headers` is `true` by default
68
+ * `:force_quotes` is `true` by default
69
+
70
+ This essentially means that when reading a CSV file, headers are required otherwise a `SimpleCsv::HeadersNotSet` exception will be thrown.
71
+ Also, when reading a CSV, all values will be parsed to their respective types, so `"1"` would become `1` as end value.
72
+
73
+ #### SimpleCsv::Writer additional default settings
74
+
75
+ Additionally, `SimpleCsv::Writer` has one additional default setting that ensures an entire row is written before being able to write another one.
76
+ This setting enforces you to call each method once before calling one of them again, if this condition is not met a `SimpleCsv::RowNotComplete` exception will be thrown
77
+
78
+ | setting | value |
79
+ |------------------------|---------------------------------------|
80
+ |`:force_row_completion` | `true` |
81
+
82
+ #### Setting aliasses
83
+
84
+ An _alias_ can be used instead of it's respective _setting_.
85
+
86
+ | setting | alias |
87
+ |---------------|---------------|
88
+ | `:col_sep` | `:seperator` |
89
+ | `headers` | `has_headers` |
90
+
91
+ #### Converters
92
+
93
+ The above `:converters` option is set to include `:all` converters and additionally, `:blank_to_nil` and `:null_to_nil`
94
+ The code for these two can be summed up in two lines:
95
+
96
+ ```ruby
97
+ CSV::Converters[:blank_to_nil] = ->(f) { f && f.empty? ? nil : f }
98
+ CSV::Converters[:null_to_nil] = ->(f) { f && f == 'NULL' ? nil : f }
99
+ ```
100
+
101
+ What they do replace empty values or the string `'NULL'` by nil within a column when it's being parsed.
102
+ For now, these are the default two and they are always used unless the `:converters` option is set to `nil` within `SimpleCsv#generate` or `SimpleCsv#read`
103
+
104
+ ### Generating a CSV file
105
+
106
+ ```ruby
107
+ SimpleCsv.generate path, options = { ... }, &block
108
+ ```
109
+
110
+ The `SimpleCsv#generate` method takes a (required) path, an (optional) hash of options and a (required) block to start building a CSV file.
111
+ To generate a CSV file we use `SimpleCsv#generate` (using the [faker](https://github.com/stympy/faker) gem to provide fake data)
112
+
113
+ While writing a row to a CSV, the value of a set property can be accessed by calling that property method again without arguments (See the "inspect a value" comment in the following example).
114
+
115
+ ```ruby
116
+ require 'faker'
117
+
118
+ # use SimpleCsv.generate('output.csv', seperator: '|') to generate a CSV with a pipe character as seperator
119
+ SimpleCsv.generate('output.csv') do
120
+ # first define the headers
121
+ headers :first_name, :last_name, :birth_date, :employed_at
122
+
123
+ # loop something
124
+ 100.times do
125
+ # insert data in each field defined in headers to insert a row.
126
+ first_name Faker::Name.first_name
127
+ # inspect a value
128
+ p first_name
129
+ last_name Faker::Name.last_name
130
+ birth_date Faker::Date.between(Date.today << 900, Date.today << 200)
131
+ employed_at [Faker::Company.name, nil].sample
132
+ end
133
+ end
134
+ ```
135
+
136
+ This method passes any unknown method to its caller (`main Object` if none).
137
+ If you need a reference to the instance of the current writer from within the block, it takes an optional argument:
138
+
139
+ ```ruby
140
+ SimpleCsv.generate ... do |writer|
141
+ # writer is a reference to the self of this block.
142
+ # the following two are equivelant (assuming 'name' column exists in the CSV):
143
+
144
+ writer.name 'SidOfc'
145
+ name 'SidOfc'
146
+ end
147
+ ```
148
+
149
+ ### Reading a CSV file
150
+
151
+ ```ruby
152
+ SimpleCsv.read path, options = { ... }, &block
153
+ ```
154
+
155
+ The `SimpleCsv#generate` method takes a (required) path, an (optional) hash of options and a (required) block to start reading a CSV file.
156
+
157
+ To read a CSV file we use `SimpleCsv#read`, we will pass it a file path and a block as arguments.
158
+ Within the block we define the headers present in the file, these will be transformed into methods you can call within `SimpleCsv::Reader#each_row` to get that property's current value
159
+
160
+ ```ruby
161
+ SimpleCsv.read('input.csv') do
162
+ # assumes headers are set, they will be read and callable within each_row
163
+
164
+ each_row do
165
+ puts [first_name, last_name, birth_date, employed_at].compact.join ', '
166
+ end
167
+ end
168
+ ```
169
+
170
+ This method passes any unknown method to its caller (`main Object` if none).
171
+ If you need a reference to the instance of the current reader from within the block, it takes an optional argument:
172
+
173
+ ```ruby
174
+ SimpleCsv.read ... do |reader|
175
+ # reader is a reference to the self of this block.
176
+ # all the following are equivelant:
177
+
178
+ # the 'each_row' and `in_groups_of` methods also get a reference to self.
179
+ each_row do |reader_too|
180
+ puts reader_too.name
181
+ puts reader.name
182
+ puts name
183
+ end
184
+
185
+ in_groups_of 100 do |other_reader|
186
+ puts other_reader.name
187
+ puts reader.name
188
+ puts name
189
+ end
190
+ end
191
+ ```
192
+
193
+ ### Reading a CSV file without headers
194
+
195
+ If we have a CSV file that does not contain headers we can use the following setup.
196
+ Setting `:has_headers` to `false` means we do not expect the first line to be headers.
197
+ Therefore we have to explicitly define the headers before looping the CSV.
198
+
199
+ ```ruby
200
+ SimpleCsv.read('headerless.csv', has_headers: false) do
201
+ # first define the headers in the file manually if the file does not have them
202
+ headers :first_name, :last_name, :birth_date, :employed_at
203
+
204
+ each_row do
205
+ # print each field defined in headers (that is not nil)
206
+ puts [first_name, last_name, birth_date, employed_at].compact.join ', '
207
+ end
208
+ end
209
+ ```
210
+
211
+ ### Transforming s CSV file
212
+
213
+ When you want to alter or reduce the output of a given CSV file, `SimpleCsv#transform` can be used.
214
+ This allows you to apply call a block for each value in a specified column, you can also control the output headers to remove clutter from the input file.
215
+
216
+ A transformation is defined by calling the header you wish to modify with a block that performs the modification.
217
+ In below example, a CSV with columns `:name`, `:username`, `:age` and `:interests` is assumed. The `:age` of every row
218
+ will be incremented because `age` was defined with the block. **Only** `headers` _and_ `output_headers` are supported within the transform block.
219
+
220
+ ```ruby
221
+ SimpleCsv.transform('people.csv', output: 'people2.csv') do
222
+ # define specific output headers, other columns will not be added to output csv file
223
+ output_headers :name, :username, :age, :interests
224
+
225
+ # everyone got one year older, increment all ages.
226
+ age { |n| n + 1 }
227
+
228
+ # replace all names with "#{name}_old".
229
+ name { |s| "#{name}_old" }
230
+ end
231
+ ```
232
+
233
+ The above example will create a file called `people2.csv` that contains the result data. The original file is **not** destroyed.
234
+ There is one additional option for `SimpleCsv#transform` which is the `:output` option.
235
+ When this option not set, the returned file will have the same name as the input CSV followed by a timestamp
236
+ formatted in the following format: `[input_csv]-[%d-%m-%Y-%S&7N].csv` (`[input_csv]` will have `.csv` extension stripped and reapplied).
237
+ See Ruby's [`Time#strftime`](https://ruby-doc.org/core-2.5.0/Time.html) documentation for more information on formatting flags used.
238
+
239
+ If you need a reference to the instance of the current reader from within the block, it takes an optional argument:
240
+
241
+ ```ruby
242
+ SimpleCsv.transform ... do |transformer|
243
+ # transformer is a reference to the self of this block.
244
+ # all the following are equivelant (assuming "age" property exists):
245
+
246
+ transformer.age { |n| n * 2 }
247
+ age { |n| n * 2 }
248
+ end
249
+ ```
250
+
251
+ ### Batch operations
252
+
253
+ If we have a large CSV we might want to batch operations (say, if we are inserting this data into a database or through an API).
254
+ For this we can use `SimpleCsv::Reader#in_groups_of` and pass the size of the group.
255
+ Within that we call `SimpleCsv::Reader#each_row` as usual
256
+
257
+ ```ruby
258
+ SimpleCsv.read('input.csv') do
259
+ # assumes headers are set, they will be read and callable within each_row
260
+
261
+ in_groups_of(100) do
262
+ each_row do
263
+ puts [first_name, last_name, birth_date, employed_at].compact.join ', '
264
+ end
265
+ # execute after every 100 rows
266
+ sleep 2
267
+ end
268
+ end
269
+ ```
270
+
271
+ ### Aliassing existing headers
272
+
273
+ Should you want to map existing headers to different names, this is possible by passing a hash at the end with key value pairs.
274
+ When generating a CSV file, aliasses are ignored and therefore should not be passed.
275
+
276
+ When defining columns manually using `headers` for a file without headers, ALL columns must be named before defining aliasses.
277
+ This means that if your CSV exists of 3 columns, 3 headers must be defined before aliassing any of those to something shorter or more concise.
278
+
279
+ To create an alias `date_of_birth` of `birth_date` *(In a CSV file without headers)* we would write *(notice `:birth_date` is present twice, once as column entry, and once more as key for an alias)*:
280
+
281
+ ```ruby
282
+ headers :first_name, :last_name, :employed_at, :birth_date, birth_date: :date_of_birth
283
+ ```
284
+
285
+ This allows you to use a method `#date_of_birth` inside any `#each_row` in addition to `#birth_date`:
286
+
287
+ ```ruby
288
+ SimpleCsv.read ... do
289
+ headers :name, :age, :employed_at, employed_at: :job
290
+
291
+ each_row do
292
+ puts "#{name} is #{age} old and works at #{job}"
293
+ end
294
+ end
295
+ ```
296
+
297
+ ## Development
298
+
299
+ After checking out the repo, run `bundle` to install dependencies. Then, run `rspec` to run the tests. You can also use the `bin/console` file to play around and debug.
300
+
301
+ To install this gem onto your local machine, run `rake install`.
302
+
303
+ To release a new version:
304
+
305
+ * Run CI in dev branch, if tests pass, merge into master
306
+ * Update version number in _lib/simple_csv/version.rb_ according to [symver](http://semver.org/)
307
+ * Update _README.md_ to reflect your changes
308
+ * run `rake release` to push commits, create a tag for the current commit and push the `.gem` file to RubyGems
309
+
310
+ ## Contributing
311
+
312
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sidofc/simple_csv. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
313
+
314
+
315
+ ## License
316
+
317
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/bin/console CHANGED
@@ -5,4 +5,25 @@ require 'pry'
5
5
  require 'faker'
6
6
  require 'simple_csv'
7
7
 
8
- Pry.start
8
+ # SimpleCsv.generate('sample.csv') do
9
+ # headers :name, :age
10
+
11
+ # 10.times do
12
+ # name Faker::Name.name
13
+ # age Faker::Number.between(20, 120)
14
+ # end
15
+ # end
16
+
17
+ SimpleCsv.transform('spec/files/result.csv', output: 'result.csv') do
18
+ output_headers 'user name'
19
+
20
+ user_name { |n| n * 2 }
21
+ end
22
+
23
+ SimpleCsv.read('result.csv') do
24
+ p headers
25
+
26
+ each_row do
27
+ p headers.map { |h| send(h) }
28
+ end
29
+ end
data/lib/simple_csv.rb CHANGED
@@ -4,6 +4,7 @@ require 'simple_csv/version'
4
4
  require 'simple_csv/settings'
5
5
  require 'simple_csv/base'
6
6
  require 'simple_csv/reader'
7
+ require 'simple_csv/transformer'
7
8
  require 'simple_csv/writer'
8
9
 
9
10
  module SimpleCsv
@@ -30,22 +31,27 @@ module SimpleCsv
30
31
  end
31
32
 
32
33
  def self.read(path, **options, &block)
33
- initialize_converters unless converters_initialized
34
+ initialize_converters unless converters_initialized?
34
35
  Reader.new path, options, &block
35
36
  end
36
37
 
37
38
  def self.generate(path, **options, &block)
38
- initialize_converters unless converters_initialized
39
+ initialize_converters unless converters_initialized?
39
40
  Writer.new path, options, &block
40
41
  end
41
42
 
43
+ def self.transform(path, **options, &block)
44
+ initialize_converters unless converters_initialized?
45
+ Transformer.new path, options, &block
46
+ end
47
+
42
48
  def self.initialize_converters
43
49
  CSV::Converters[:blank_to_nil] = ->(f) { f && f.empty? ? nil : f }
44
50
  CSV::Converters[:null_to_nil] = ->(f) { f && f == 'NULL' ? nil : f }
45
51
  @converters_initialized = true
46
52
  end
47
53
 
48
- def self.converters_initialized
54
+ def self.converters_initialized?
49
55
  @converters_initialized
50
56
  end
51
57
  end
@@ -23,12 +23,32 @@ module SimpleCsv
23
23
  @col_map.merge! stringify_col_map(col_map) if col_map.any?
24
24
 
25
25
  @headers_set ||= @headers.any?
26
+ @headers.uniq!
26
27
  @headers
27
28
  end
28
29
 
29
- def alias_to_friendly_headers
30
+ def find_headers
31
+ first_line.split(detect_delimiter).map { |h| h.gsub(/^"*|"*$/, '') }
32
+ end
33
+
34
+ def detect_delimiter
35
+ line = first_line
36
+ @delimiters = COMMON_DELIMITERS.map { |sep| [sep, line.scan(sep).length] }
37
+ .sort { |a, b| b[1] <=> a[1] }
38
+ @delimiter ||= @delimiters[0][0]
39
+ end
40
+
41
+ def first_line
42
+ @first_line ||= File.open @csv_path, &:readline
43
+ end
44
+
45
+ def headers?
46
+ @headers_set
47
+ end
48
+
49
+ def alias_to_friendly_headers(names = @headers)
30
50
  @col_map ||= {}
31
- aliasses = headers.each_with_object({}) do |hdr, h|
51
+ aliasses = names.each_with_object({}) do |hdr, h|
32
52
  n = hdr.to_s.strip.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
33
53
  .gsub(/[^\w]|\s/, '_')
34
54
  h[n] = hdr unless @col_map.key? n
@@ -4,19 +4,20 @@ module SimpleCsv
4
4
 
5
5
  def initialize(path, **opts, &block)
6
6
  @csv_path = File.expand_path path
7
+ @caller_self = eval 'self', block.binding
7
8
 
8
9
  opts[:seperator] ||= detect_delimiter
9
10
  settings.apply opts
10
11
 
11
12
  load_csv_with_auto_headers if settings.for_csv[:headers]
12
13
 
13
- instance_eval(&block)
14
+ instance_exec(self, &block)
14
15
  end
15
16
 
16
17
  def in_groups_of(size, &block)
17
18
  @original.each_slice(size) do |group|
18
19
  @csv = group
19
- instance_eval(&block)
20
+ instance_exec(self, &block)
20
21
  end
21
22
  @index = nil
22
23
  @csv = @original
@@ -29,7 +30,7 @@ module SimpleCsv
29
30
 
30
31
  @csv.each do |record|
31
32
  @record = record
32
- instance_eval(&block)
33
+ instance_exec(self, &block)
33
34
  @index += 1 if @index
34
35
  end
35
36
  end
@@ -43,7 +44,7 @@ module SimpleCsv
43
44
  end
44
45
 
45
46
  def load_csv_with_manual_headers
46
- SimpleCsv.csv_manually_set_headers! unless @headers_set
47
+ SimpleCsv.csv_manually_set_headers! unless headers?
47
48
  csv_arr = CSV.open(@csv_path).to_a
48
49
 
49
50
  if csv_arr.first.size == headers.size
@@ -55,30 +56,11 @@ module SimpleCsv
55
56
  end
56
57
  end
57
58
 
58
- def find_headers
59
- first_line.split(detect_delimiter).map { |h| h.gsub(/^"*|"*$/, '') }
60
- end
61
-
62
- def detect_delimiter
63
- line = first_line
64
- @delimiters = COMMON_DELIMITERS.map { |sep| [sep, line.scan(sep).length] }
65
- .sort { |a, b| b[1] <=> a[1] }
66
- @delimiter ||= @delimiters[0][0]
67
- end
68
-
69
- def first_line
70
- @first_line ||= File.open @csv_path, &:readline
71
- end
72
-
73
- def respond_to_missing?(mtd, include_private)
74
- headers.include?(m) || @col_map.key?(m)
75
- end
76
-
77
59
  def method_missing(mtd, *args, &block)
78
60
  m = mtd.to_s
79
61
  return @record[m] if headers.include?(m)
80
62
  return @record[@col_map[m]] if @col_map.key?(m)
81
- super
63
+ @caller_self.send mtd, *args, &block
82
64
  end
83
65
  end
84
66
  end
@@ -4,7 +4,7 @@ module SimpleCsv
4
4
  force_quotes: true,
5
5
  converters: [:all, :blank_to_nil, :null_to_nil] }.freeze
6
6
  ALIASSED = { seperator: :col_sep, has_headers: :headers }.freeze
7
- INVERTED_ALIASSES = ALIASSED.to_a.map(&:reverse).to_h
7
+ INVERTED_ALIASSES = ALIASSED.to_a.map(&:reverse).to_h.freeze
8
8
 
9
9
  def initialize(**opts)
10
10
  @settings = DEFAULTS.dup.merge opts
@@ -0,0 +1,68 @@
1
+ module SimpleCsv
2
+ class Transformer < Base
3
+ DEFAULT_FILENAME = 'converted.csv'
4
+
5
+ def initialize(path, **opts, &block)
6
+ @transforms = {}
7
+ @output_headers = []
8
+
9
+ @caller_self = eval 'self', block.binding
10
+
11
+ if settings.for_csv[:headers]
12
+ @csv_path = File.expand_path path
13
+ headers(*find_headers) unless headers.any?
14
+ end
15
+
16
+ instance_exec(self, &block)
17
+
18
+ apply_transforms path, **opts
19
+ end
20
+
21
+ def output_headers(*out_headers)
22
+ return @output_headers if @output_headers.any?
23
+
24
+ @output_headers = out_headers.map(&:to_s)
25
+ alias_to_friendly_headers @output_headers
26
+ @output_headers
27
+ end
28
+
29
+ private
30
+
31
+ def apply_transforms(path, **opts)
32
+ received_headers = headers
33
+ timestamp = Time.new.strftime '%d-%m-%Y-%S%7N'
34
+ output_path = opts.delete(:output) || "#{path.split('.')[0..-2].join}-#{timestamp}.csv"
35
+ output_headers = @output_headers.any? ? @output_headers : received_headers
36
+
37
+ SimpleCsv.read path, opts do |reader|
38
+ SimpleCsv.generate output_path, opts do |writer|
39
+ writer.headers *output_headers
40
+
41
+ reader.each_row do
42
+ output_headers.each do |column|
43
+ transform = find_transform column
44
+ result = transform ? transform.call(reader.send(column))
45
+ : reader.send(column)
46
+
47
+ writer.send column, result
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def find_transform(column)
55
+ @transforms[(@col_map.key(column.to_s) || column).to_sym]
56
+ end
57
+
58
+ def method_missing(mtd, *args, &block)
59
+ mstr = mtd.to_s
60
+
61
+ if headers.include?(mstr) || @output_headers.include?(mstr) || @col_map.key?(mstr)
62
+ @transforms[mtd] = block || args.first unless @transforms.key? mtd
63
+ else
64
+ @caller_self.send mtd, *args, &block
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module SimpleCsv
2
- VERSION = '0.2.2'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -1,28 +1,36 @@
1
1
  module SimpleCsv
2
2
  class Writer < Base
3
3
  def initialize(path, **opts, &block)
4
+ @caller_self = eval 'self', block.binding
5
+
4
6
  settings.apply({force_row_completion: true}, opts)
5
7
  CSV.open(File.expand_path(path), 'w', settings.for_csv) do |csv|
6
8
  @csv = csv
7
9
  @last_row = {}
8
10
  @current_row = {}
9
- instance_eval(&block)
11
+ instance_exec(self, &block)
10
12
  end
11
13
  end
12
14
 
13
- private
14
-
15
- def respond_to_missing?(mtd, include_private = false)
16
- super unless headers.include?(mtd.to_s) || @col_map.key?(mtd.to_s)
15
+ def headers(*args)
16
+ super
17
+ (@csv << @headers) && @headers_written = true if !@headers_written && @csv
18
+ @headers
17
19
  end
18
20
 
21
+ private
22
+
19
23
  def method_missing(mtd, *args, &block)
20
24
  SimpleCsv.csv_manually_set_headers! unless @headers_written
25
+
21
26
  current_val = @current_row[mtd] if @current_row.key?(mtd)
22
27
  current_val = @last_row[mtd] if @last_row.key?(mtd)
23
28
 
24
29
  return current_val if args.empty? && current_val
25
- super unless headers.include?(mtd.to_s) || @col_map.key?(mtd.to_s)
30
+
31
+ unless headers.include?(mtd.to_s) || @col_map.key?(mtd.to_s)
32
+ return @caller_self.send mtd, *args, &block
33
+ end
26
34
 
27
35
  if settings.force_row_completion && @current_row.key?(mtd) && args.any?
28
36
  SimpleCsv.row_not_complete!(mtd, args.first)
@@ -37,11 +45,5 @@ module SimpleCsv
37
45
  @current_row = {}
38
46
  current_val
39
47
  end
40
-
41
- def headers(*args)
42
- super
43
- (@csv << @headers) && @headers_written = true if !@headers_written && @csv
44
- @headers
45
- end
46
48
  end
47
49
  end
data/simple_csv.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = 'CSV DSL'
13
13
  spec.description = 'A simple DSL for reading and generating CSV files'
14
- spec.homepage = 'https://sidofc.github.io/projects/simple-csv/'
14
+ spec.homepage = 'https://github.com/SidOfc/simple_csv'
15
15
  spec.license = 'MIT'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = '>= 2.1'
26
26
 
27
- spec.add_development_dependency 'bundler', '~> 1.13'
28
- spec.add_development_dependency 'rake', '~> 10.0'
29
- spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'bundler'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'rspec'
30
30
  spec.add_development_dependency 'pry'
31
31
  spec.add_development_dependency 'pry-byebug'
32
32
  spec.add_development_dependency 'faker'
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sidney Liebrand
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-31 00:00:00.000000000 Z
11
+ date: 2018-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.13'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.13'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '3.0'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '3.0'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: pry
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -130,10 +130,12 @@ files:
130
130
  - lib/simple_csv/base.rb
131
131
  - lib/simple_csv/reader.rb
132
132
  - lib/simple_csv/settings.rb
133
+ - lib/simple_csv/transformer.rb
133
134
  - lib/simple_csv/version.rb
134
135
  - lib/simple_csv/writer.rb
135
136
  - simple_csv.gemspec
136
- homepage: https://sidofc.github.io/projects/simple-csv/
137
+ - tmp/.gitkeep
138
+ homepage: https://github.com/SidOfc/simple_csv
137
139
  licenses:
138
140
  - MIT
139
141
  metadata: {}
@@ -153,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
155
  version: '0'
154
156
  requirements: []
155
157
  rubyforge_project:
156
- rubygems_version: 2.6.8
158
+ rubygems_version: 2.7.6
157
159
  signing_key:
158
160
  specification_version: 4
159
161
  summary: CSV DSL