Skip to content

Commit

Permalink
Merge 9b1705f into 11ae60b
Browse files Browse the repository at this point in the history
  • Loading branch information
SidOfc committed Jun 21, 2018
2 parents 11ae60b + 9b1705f commit a9323b1
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 165 deletions.
7 changes: 2 additions & 5 deletions .gitignore
Expand Up @@ -6,8 +6,5 @@
/doc/
/pkg/
/spec/reports/
/tmp/
*output.csv
*input.csv
*result.csv
*transformed.csv
tmp/*
!tmp/
6 changes: 3 additions & 3 deletions .travis.yml
Expand Up @@ -2,9 +2,9 @@ sudo: false
language: ruby
cache: bundler
rvm:
- 2.0.0
- 2.1.0
- 2.2.0
- 2.2.2
- 2.3.0
- 2.3.3
before_install: gem install bundler -v 1.13.6
- 2.4.0
- 2.5.0
65 changes: 49 additions & 16 deletions README.md
Expand Up @@ -46,20 +46,20 @@ The headers will be picked up and used instead of the first line.

These are the settings that will be merged with settings passed through either `SimpleCsv#generate` or `SimpleCsv#read`

| setting | value |
|----------------------|---------------------------------------|
|`:col_sep` | `","` |
|`:row_sep` | `:auto` |
|`:quote_char` | `"\"` |
|`:field_size_limit` | `nil` |
|`:converters` | `[:all, :blank_to_nil, :null_to_nil]` |
|`:unconverted_fields` | `nil` |
|`:headers` | `true` |
|`:return_headers` | `false` |
|`:header_converters` | `nil` |
|`:skip_blanks` | `false` |
|`:force_quotes` | `true` |
|`:skip_lines` | `nil` |
| setting | value |
| ---------------------- | --------------------------------------- |
| `:col_sep` | `","` |
| `:row_sep` | `:auto` |
| `:quote_char` | `"\"` |
| `:field_size_limit` | `nil` |
| `:converters` | `[:all, :blank_to_nil, :null_to_nil]` |
| `:unconverted_fields` | `nil` |
| `:headers` | `true` |
| `:return_headers` | `false` |
| `:header_converters` | `nil` |
| `:skip_blanks` | `false` |
| `:force_quotes` | `true` |
| `:skip_lines` | `nil` |

The following settings differ from the `CSV::DEFAULT_OPTIONS`

Expand Down Expand Up @@ -110,7 +110,12 @@ SimpleCsv.generate path, options = { ... }, &block
The `SimpleCsv#generate` method takes a (required) path, an (optional) hash of options and a (required) block to start building a CSV file.
To generate a CSV file we use `SimpleCsv#generate` (using the [faker](https://github.com/stympy/faker) gem to provide fake data)

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)
This method passes any unknown method to its caller (`main Object` if none). If you need a reference to the instance of the current writer from within the block, it takes an optional argument:

```ruby
```

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).

```ruby
require 'faker'
Expand Down Expand Up @@ -156,7 +161,7 @@ end

### Reading a CSV file without headers

Last but not least, if we have a CSV file that does not contain headers we can use the following setup.
If we have a CSV file that does not contain headers we can use the following setup.
Setting `:has_headers` to `false` means we do not expect the first line to be headers.
Therefore we have to explicitly define the headers before looping the CSV.

Expand All @@ -172,6 +177,34 @@ SimpleCsv.read('headerless.csv', has_headers: false) do
end
```

### Transforming s CSV file

When you want to alter or reduce the output of a given CSV file, `SimpleCsv#transform` can be used.
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.

A transformation is defined by calling the header you wish to modify with a block that performs the modification.
In below example, a CSV with columns `:name`, `:username`, `:age` and `:interests` is assumed. The `:age` of every row
will be incremented because `age` was defined with the block. **Only** `headers` _and_ `output_headers` are supported within the transform block.

```ruby
SimpleCsv.transform('people.csv', output: 'people2.csv') do
# define specific output headers, other columns will not be added to output csv file
output_headers :name, :username, :age, :interests

# everyone got one year older, increase all ages.
age { |n| n + 1 }

# replace all names with "#{name}_old".
name { |s| "#{name}_old" }
end
```

The above example will create a file called `people2.csv` that contains the result data. The original file is **not** destroyed by default.
There is one additional option for `SimpleCsv#transform` which is the `:output` option.
When this option not set, the returned file will have the same name as the input CSV followed by a timestamp
formatted in the following format: `[input_csv]-[%d-%m-%Y-%S&7N].csv` (`[input_csv]` will have `.csv` extension stripped and reapplied).
See Ruby's [`Time#strftime`](https://ruby-doc.org/core-2.5.0/Time.html) documentation for more information.

### Batch operations

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).
Expand Down
24 changes: 14 additions & 10 deletions bin/console
Expand Up @@ -5,21 +5,25 @@ require 'pry'
require 'faker'
require 'simple_csv'

SimpleCsv.generate('sample.csv') do
headers :name, :age
# SimpleCsv.generate('sample.csv') do
# headers :name, :age

10.times do
name Faker::Name.name
age Faker::Number.between(20, 120)
end
end
# 10.times do
# name Faker::Name.name
# age Faker::Number.between(20, 120)
# end
# end

SimpleCsv.transform('sample.csv', output: 'result.csv') do
output_headers :age
SimpleCsv.transform('spec/files/result.csv', output: 'result.csv') do
output_headers 'user name'

age { |n| n * 2 }
user_name { |n| n * 2 }
end

SimpleCsv.read('result.csv') do
p headers

each_row do
p headers.map { |h| send(h) }
end
end
5 changes: 2 additions & 3 deletions lib/simple_csv/base.rb
Expand Up @@ -42,14 +42,13 @@ def first_line
@first_line ||= File.open @csv_path, &:readline
end


def headers?
@headers_set
end

def alias_to_friendly_headers
def alias_to_friendly_headers(names = @headers)
@col_map ||= {}
aliasses = headers.each_with_object({}) do |hdr, h|
aliasses = names.each_with_object({}) do |hdr, h|
n = hdr.to_s.strip.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
.gsub(/[^\w]|\s/, '_')
h[n] = hdr unless @col_map.key? n
Expand Down
13 changes: 10 additions & 3 deletions lib/simple_csv/transformer.rb
Expand Up @@ -22,13 +22,14 @@ def output_headers(*out_headers)
return @output_headers if @output_headers.any?

@output_headers = out_headers.map(&:to_s)
alias_to_friendly_headers @output_headers
@output_headers
end

private

def apply_transforms(path, **opts)
received_headers = headers
transformations = @transforms
timestamp = Time.new.strftime '%d-%m-%Y-%S%7N'
output_path = opts.delete(:output) || "#{path.split('.')[0..-2].join}-#{timestamp}.csv"
output_headers = @output_headers.any? ? @output_headers : received_headers
Expand All @@ -39,7 +40,7 @@ def apply_transforms(path, **opts)

reader.each_row do
output_headers.each do |column|
transform = transformations[column.to_sym]
transform = find_transform column
result = transform ? transform.call(reader.send(column))
: reader.send(column)

Expand All @@ -50,8 +51,14 @@ def apply_transforms(path, **opts)
end
end

def find_transform(column)
@transforms[(@col_map.key(column.to_s) || column).to_sym]
end

def method_missing(mtd, *args, &block)
if headers.include?(mtd.to_s) || @output_headers.include?(mtd.to_s)
mstr = mtd.to_s

if headers.include?(mstr) || @output_headers.include?(mstr) || @col_map.key?(mstr)
@transforms[mtd] = block || args.first unless @transforms.key? mtd
else
@caller_self.send mtd, *args, &block
Expand Down
6 changes: 3 additions & 3 deletions simple_csv.gemspec
Expand Up @@ -24,9 +24,9 @@ Gem::Specification.new do |spec|

spec.required_ruby_version = '>= 2.1'

spec.add_development_dependency 'bundler', '~> 1.13'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'rspec'
spec.add_development_dependency 'pry'
spec.add_development_dependency 'pry-byebug'
spec.add_development_dependency 'faker'
Expand Down
101 changes: 0 additions & 101 deletions spec/files/output.csv

This file was deleted.

0 comments on commit a9323b1

Please sign in to comment.