csv_piper 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +174 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +5 -0
- data/csv_piper.gemspec +25 -0
- data/lib/csv_piper/builder.rb +40 -0
- data/lib/csv_piper/errors/row.rb +19 -0
- data/lib/csv_piper/piper.rb +73 -0
- data/lib/csv_piper/pre_processors/remove_extra_columns.rb +10 -0
- data/lib/csv_piper/processors/error_collector.rb +15 -0
- data/lib/csv_piper/processors/output_collector.rb +16 -0
- data/lib/csv_piper/version.rb +3 -0
- data/lib/csv_piper.rb +6 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ea1cbd12bae76e95d785d1c5b0f1e5b5226d6e15
|
4
|
+
data.tar.gz: f93996e0438a347161ced80ad8eee12875099029
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b1812adb041655a4952e44ddba1b0cd8d90c97aeb1bc83dc09547a0805930da9dcaf2b02cab390e2adac9e0f823043ff5cf138972b2973cf6362a8560bf1f2fc
|
7
|
+
data.tar.gz: 220b7d3a231c03ba2c5196eaf590d9ea11f18b62ff3b2d1b6f1bfd192cd1c05f106db2d56b6614a0d074740ce49da2ebb7327507f42b532fa825aa492b2c601b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jarrod
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
# CsvPiper
|
2
|
+
|
3
|
+
A simple wrapper to create a pipeline style csv processor that makes your transforms easily testable.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'csv_piper'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install csv_piper
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
CsvPiper handles CSV reading row by row passing each row through a series of processors.
|
24
|
+
|
25
|
+
#### Requirements
|
26
|
+
* Currently source csv must have headers
|
27
|
+
|
28
|
+
#### Basic Usage
|
29
|
+
```ruby
|
30
|
+
File.open("my/file/path", "r") do |io_stream|
|
31
|
+
CsvPiper::Builder.new.from(io_stream).with_processors([your_processors]).build.process
|
32
|
+
end
|
33
|
+
```
|
34
|
+
`io_stream` can be any subclass of [IO](ruby-doc.org/core/IO.html).
|
35
|
+
|
36
|
+
`build` returns an instance of `CsvPiper::Piper` but you will only need this object to call `process` unless you are utilising the `requires_headers()` method _(see builder options below)_
|
37
|
+
|
38
|
+
#### Basic Usage with Processors
|
39
|
+
_Extracted from `spec/end_to_end_spec.rb`_
|
40
|
+
```ruby
|
41
|
+
# Build some processors beforehand so we can access them later
|
42
|
+
output_collector = ProcessedEquationCollector.new
|
43
|
+
error_collector = CsvPiper::Processors::ErrorCollector.new
|
44
|
+
|
45
|
+
# Open the csv file to get our io source
|
46
|
+
File.open(File.join(File.dirname(__FILE__),"/data/csv_1.csv")) do |file|
|
47
|
+
|
48
|
+
# Build piper
|
49
|
+
csv_piper = CsvPiper::Builder.new.from(file)
|
50
|
+
.requires_headers(required_headers)
|
51
|
+
.with_processors([
|
52
|
+
BuildEquation.new, EvaluateEquation.new, output_collector, error_collector
|
53
|
+
])
|
54
|
+
.build
|
55
|
+
|
56
|
+
# Process csv
|
57
|
+
csv_piper.process if csv_piper.has_required_headers?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Grab some output we wanted to collect (You don't have to do this, espicially when processing lots of data)
|
61
|
+
output = output_collector.output
|
62
|
+
errors = error_collector.errors
|
63
|
+
|
64
|
+
|
65
|
+
class BuildEquation
|
66
|
+
def process(source, transformed, errors)
|
67
|
+
transformed[:equation] = [ source['Input 1'], source['Process'], source['Input 2'], '==', source['Result'] ].join(' ')
|
68
|
+
[transformed, errors]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class EvaluateEquation
|
73
|
+
def process(source, transformed, errors)
|
74
|
+
begin
|
75
|
+
transformed[:valid] = eval(transformed[:equation]) == true
|
76
|
+
rescue Exception
|
77
|
+
errors.add(:msg, transformed[:equation] + ' is not a valid equation')
|
78
|
+
end
|
79
|
+
[transformed, errors]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class ProcessedEquationCollector
|
84
|
+
attr_reader :output
|
85
|
+
def initialize
|
86
|
+
@output = []
|
87
|
+
end
|
88
|
+
|
89
|
+
def process(source, transformed, errors)
|
90
|
+
@output << {row: errors.row_index}.merge(transformed) if errors.empty?
|
91
|
+
[transformed, errors]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
#### Processors
|
97
|
+
Each processor can do whatever it wants, transformation, logging, saving to a database etc.
|
98
|
+
|
99
|
+
Here is an example of a processor that passes the values from the csv straight along to the transformed output:
|
100
|
+
```ruby
|
101
|
+
class PassThrough
|
102
|
+
def process(source, transformed, errors)
|
103
|
+
[transformed.merge(source), errors]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
* `source` is a frozen hash representing the row data out of the csv (with headers as keys)
|
109
|
+
* `transformed` is whatever has been passed on by the previous processor. The first processor will receive an empty hash.
|
110
|
+
* `errors` is an instance of `CsvPiper::Errors::Row`
|
111
|
+
|
112
|
+
_Return value_ is what will be passed into _transformed_ and _errors_ of the next processor
|
113
|
+
|
114
|
+
#### Pre-Processors
|
115
|
+
|
116
|
+
Pre-processors work the same as processors except that their purpose is to modify the source row data that will be passed into all processors. It's useful for doing things like converting strings to primitives, removing columns etc.
|
117
|
+
|
118
|
+
They are also allowed to add errors against the row.
|
119
|
+
|
120
|
+
Here is an example of a pre-processor that converts all values to uppercase:
|
121
|
+
```ruby
|
122
|
+
class UpCase
|
123
|
+
def process(source, errors)
|
124
|
+
transformed = source.each_with_object({}) { |(key, value), memo| memo[key] = value.upcase }
|
125
|
+
[transformed, errors]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
* `source` is a hash representing the row data out of the csv which may have been modified by a previous pre-processor
|
130
|
+
* `errors` is an instance of `CsvPiper::Errors::Row`
|
131
|
+
|
132
|
+
_Return value_ is what will be passed into _source_ and _errors_ of the next pre-processor (and processors). Final pre-processor value of _source_ will be passed to each processor as a frozen hash. Final pre-processor value of _errors_ will be passed to the first processor.
|
133
|
+
|
134
|
+
#### Row Errors
|
135
|
+
This object is passed into each processor (which must pass it on) and is used to accumulate any and all errors for the particular row. You can access the row number through `row_index`.
|
136
|
+
|
137
|
+
Add errors using `errors.add(error_key, error)`.
|
138
|
+
|
139
|
+
#### Builder Options
|
140
|
+
All builder options utilise the _fluent interface pattern_ and should be followed by a call to `build` to get the piper instance and then `process` to process the csv.
|
141
|
+
|
142
|
+
Eg. `CsvPiper::Builder.new.from(io).with_processors(processors).build.process`
|
143
|
+
|
144
|
+
* `from(io_stream)`: Specifies the **open** io stream to read csv data from
|
145
|
+
* `with_pre_processors(pre_processors)`: Takes an array of pre-processors which will transform each row before it is handled by processors
|
146
|
+
* `with_processors(processors)`: Takes an array of processors which do all the interesting domain based work
|
147
|
+
* `with_csv_options(options)`: Takes an options hash which is passed to `CSV.new` to set any options on the CSV library
|
148
|
+
* `requires_headers(headers)`: Takes an array of strings representing the headers that must be present in the CSV. If this build option is used and `process` is called on a io source missing a header then a exception is thrown. Before calling `process` you should make use of the `has_required_headers?` check and then retrieve the missing headers through `missing_headers` if necessary.
|
149
|
+
|
150
|
+
## Pre-made Processors
|
151
|
+
Over time we will collect a bunch of general purpose processors that anyone can use. They can be found in the `lib/processors` folder but here are a couple:
|
152
|
+
|
153
|
+
* `OutputCollector`: Collects the transformed object of every row that is passed through it
|
154
|
+
* `ErrorCollector`: Collects the `RowError` object of every row that is passed through it
|
155
|
+
|
156
|
+
By using `OutputCollector` and to a lesser extent `ErrorCollector` you will start to build up objects in memory. For very large csv files you might not want to use these convenience processors and rather create a new processor that does whatever you need with the row (Ie. log, write to db) which will then be discarded rather than collected.
|
157
|
+
|
158
|
+
Require them explicitly if you want to use them.
|
159
|
+
|
160
|
+
Eg. `require 'csv_piper/processors/output_collector'`
|
161
|
+
|
162
|
+
|
163
|
+
## Development
|
164
|
+
|
165
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
166
|
+
|
167
|
+
## Contributing
|
168
|
+
|
169
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/csv_piper.
|
170
|
+
|
171
|
+
## License
|
172
|
+
|
173
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
174
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "csv_piper"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/csv_piper.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'csv_piper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "csv_piper"
|
8
|
+
spec.version = CsvPiper::VERSION
|
9
|
+
spec.authors = ["Jarrod Sibbison"]
|
10
|
+
spec.email = [""]
|
11
|
+
|
12
|
+
spec.summary = %q{CSV processing pipeline}
|
13
|
+
spec.description = %q{Simple wrapper to process csv's with a pipeline of testable processors.}
|
14
|
+
spec.homepage = "https://github.com/jazzarati/csv_piper"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3"
|
25
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CsvPiper
|
2
|
+
class Builder
|
3
|
+
def initialize
|
4
|
+
@pre_processors = []
|
5
|
+
@processors = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def from(io_stream)
|
9
|
+
@io = io_stream
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_pre_processors(pre_processors)
|
14
|
+
@pre_processors += pre_processors
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_processors(processors)
|
19
|
+
@processors += processors
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_csv_options(options)
|
24
|
+
@csv_options = options
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def requires_headers(headers)
|
29
|
+
@required_headers = headers
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def build
|
34
|
+
build_options = { io_stream: @io, pre_processors: @pre_processors, processors: @processors }
|
35
|
+
build_options[:csv_options] = @csv_options if @csv_options
|
36
|
+
build_options[:required_headers] = @required_headers if @required_headers
|
37
|
+
Piper.new(build_options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CsvPiper
|
2
|
+
module Errors
|
3
|
+
class Row
|
4
|
+
extend Forwardable
|
5
|
+
delegate :empty? => :errors
|
6
|
+
|
7
|
+
attr_reader :row_index, :errors
|
8
|
+
|
9
|
+
def initialize(row_index)
|
10
|
+
@row_index = row_index
|
11
|
+
@errors = Hash.new { [].freeze }
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(key, error)
|
15
|
+
@errors[key] += [error]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module CsvPiper
|
2
|
+
class Piper
|
3
|
+
HEADER_LINE_INDEX = 1
|
4
|
+
FIRST_DATA_LINE_INDEX = 2
|
5
|
+
CSV_HEADER_OPTIONS = { headers: true, return_headers: true, skip_blanks: true, skip_lines: /^(\s*,)*$/ }
|
6
|
+
|
7
|
+
def initialize(io_stream:, pre_processors: [], processors: [], csv_options: {}, required_headers: [])
|
8
|
+
@pre_processors = [PreProcessors::RemoveExtraColumns.new] + pre_processors
|
9
|
+
@processors = processors
|
10
|
+
@required_headers = required_headers
|
11
|
+
@csv_options = csv_options.merge(CSV_HEADER_OPTIONS)
|
12
|
+
@csv_options = @csv_options.merge(skip_lines: "^(\s*#{@csv_options[:col_sep]})*$") if @csv_options[:col_sep]
|
13
|
+
@io = io_stream
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_required_headers?
|
17
|
+
missing_headers.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def missing_headers
|
21
|
+
headers = csv.headers
|
22
|
+
required_headers.reject { |header| headers.include?(header) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def process
|
26
|
+
validate_process_configuration!
|
27
|
+
validate_headers!
|
28
|
+
|
29
|
+
process_csv_body
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :io, :pre_processors, :processors, :required_headers, :csv_options
|
37
|
+
|
38
|
+
def process_csv_body
|
39
|
+
csv.each.with_index(FIRST_DATA_LINE_INDEX) do |row, index|
|
40
|
+
processed_data, row_errors = process_row(index, row.to_hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_row(row_index, row)
|
45
|
+
pre_processed_row, row_errors = pre_processors.reduce([row, Errors::Row.new(row_index)]) do |memo, processor|
|
46
|
+
processor.process(*memo)
|
47
|
+
end
|
48
|
+
|
49
|
+
frozen_row = pre_processed_row.freeze
|
50
|
+
|
51
|
+
processed_data = {}
|
52
|
+
processed_data, row_errors = processors.reduce([processed_data, row_errors]) do |memo, processor|
|
53
|
+
processor.process(frozen_row, *memo)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def csv
|
58
|
+
@csv ||= begin
|
59
|
+
csv_object = CSV.new(io, csv_options)
|
60
|
+
csv_object.readline # Read headers through
|
61
|
+
csv_object
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_process_configuration!
|
66
|
+
raise 'Requires an IO object to process from' if io.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_headers!
|
70
|
+
raise "Missing required headers: #{missing_headers.join(', ')}" unless has_required_headers?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CsvPiper
|
2
|
+
module Processors
|
3
|
+
class ErrorCollector
|
4
|
+
attr_reader :errors
|
5
|
+
def initialize
|
6
|
+
@errors = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def process(source, transformed, errors)
|
10
|
+
@errors[errors.row_index] = errors unless errors.empty?
|
11
|
+
[transformed, errors]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CsvPiper
|
2
|
+
module Processors
|
3
|
+
class OutputCollector
|
4
|
+
attr_reader :output
|
5
|
+
def initialize(collect_when_invalid: true)
|
6
|
+
@output = []
|
7
|
+
@collect_when_invalid = collect_when_invalid
|
8
|
+
end
|
9
|
+
|
10
|
+
def process(source, transformed, errors)
|
11
|
+
@output << transformed if @collect_when_invalid || errors.empty?
|
12
|
+
[transformed, errors]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/csv_piper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: csv_piper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jarrod Sibbison
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
description: Simple wrapper to process csv's with a pipeline of testable processors.
|
56
|
+
email:
|
57
|
+
- ''
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- csv_piper.gemspec
|
71
|
+
- lib/csv_piper.rb
|
72
|
+
- lib/csv_piper/builder.rb
|
73
|
+
- lib/csv_piper/errors/row.rb
|
74
|
+
- lib/csv_piper/piper.rb
|
75
|
+
- lib/csv_piper/pre_processors/remove_extra_columns.rb
|
76
|
+
- lib/csv_piper/processors/error_collector.rb
|
77
|
+
- lib/csv_piper/processors/output_collector.rb
|
78
|
+
- lib/csv_piper/version.rb
|
79
|
+
homepage: https://github.com/jazzarati/csv_piper
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.4.5
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: CSV processing pipeline
|
103
|
+
test_files: []
|