csv_monster 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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test do
4
+ gem 'rspec'
5
+ end
6
+
7
+ # Specify your gem's dependencies in csv_monster.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ csv_monster (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.4)
10
+ rake (10.0.4)
11
+ rspec (2.13.0)
12
+ rspec-core (~> 2.13.0)
13
+ rspec-expectations (~> 2.13.0)
14
+ rspec-mocks (~> 2.13.0)
15
+ rspec-core (2.13.1)
16
+ rspec-expectations (2.13.0)
17
+ diff-lcs (>= 1.1.3, < 2.0)
18
+ rspec-mocks (2.13.1)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ csv_monster!
25
+ rake
26
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jeff Iacono, Joe Prang & Square Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # CSVMonster
2
+
3
+ ```ruby
4
+ require './lib/csv_monster'
5
+
6
+ first = CSVMonster.new 'first.csv'
7
+ second = CSVMonster.new 'second.csv'
8
+ parts = CSVMonster.new ['first.csv', 'second.csv']
9
+ whole = CSVMonster.new 'first_and_second_already_combined.csv'
10
+
11
+ # using the + operator
12
+ puts (whole == (first + second)) ? "matchy!" : "no matchy!"
13
+
14
+ # using the initializer
15
+ puts "whole contains #{whole.content_length} records"
16
+ puts "parts contain #{parts.content_length} records"
17
+ puts whole == parts ? "matchy!" : "no matchy!"
18
+ ```
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.rspec_opts = '-b'
8
+ end
9
+
10
+ task default: :spec
11
+ rescue LoadError
12
+ $stderr.puts "rspec not available, spec task not provided"
13
+ end
data/TODO.md ADDED
@@ -0,0 +1,17 @@
1
+ # TODO
2
+
3
+ ### add split method
4
+ Add in a #split method that takes an integer and splits its content into that
5
+ many equal parts and writes each.
6
+
7
+ Example:
8
+
9
+ ```ruby
10
+ e = ExtendedCSV.new 'somefile.csv' # 1 header + 20 rows
11
+ e.split(4)
12
+ #=> wrote 4 different files:
13
+ # - somefile_1.csv # 1 header + 5 rows
14
+ # - somefile_2.csv # 1 header + 5 rows
15
+ # - somefile_3.csv # 1 header + 5 rows
16
+ # - somefile_4.csv # 1 header + 5 rows
17
+ ```
@@ -0,0 +1,19 @@
1
+ require File.expand_path('../lib/csv_monster/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Jeff Iacono", "Joe Prang"]
5
+ gem.email = ["jeff.iacono@gmail.com", "joseph.prang@gmail.com"]
6
+ gem.summary = "A monster of a CSV util"
7
+ gem.description = "A set of utils for working with CSV files"
8
+ gem.homepage = ''
9
+ gem.license = 'MIT'
10
+ gem.name = 'csv_monster'
11
+ gem.date = '2013-06-19'
12
+ gem.version = CSVMonster::VERSION
13
+ gem.require_paths = ["lib"]
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+
17
+ gem.add_development_dependency "rake"
18
+ gem.add_development_dependency "rspec", [">= 2"]
19
+ end
@@ -0,0 +1,3 @@
1
+ class CSVMonster
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,75 @@
1
+ require 'csv'
2
+ require 'csv_monster/version'
3
+
4
+ class CSVMonster
5
+ attr_reader :filepaths
6
+
7
+ def initialize filepaths = nil
8
+ self.filepaths = filepaths
9
+ end
10
+
11
+ def filepaths= filepaths
12
+ @filepaths = [*filepaths]
13
+ end
14
+
15
+ def + other_csv_monster
16
+ self.class.new self.filepaths + other_csv_monster.filepaths
17
+ end
18
+
19
+ def == other_csv_monster
20
+ content == other_csv_monster.content
21
+ end
22
+
23
+ def content
24
+ return @content if defined? @content
25
+
26
+ @content ||= []
27
+ @filepaths.each_with_index do |csv_filepath, i|
28
+ csv = CSV.parse(File.read(csv_filepath))
29
+ csv.shift unless i == 0
30
+
31
+ csv.each do |row|
32
+ @content << row
33
+ end
34
+ end
35
+
36
+ @content
37
+ end
38
+
39
+ def content_length
40
+ content.length
41
+ end
42
+
43
+ def split split_count
44
+ header, *tail = self.content
45
+ splits = []
46
+
47
+ tail.each_with_index do |row, i|
48
+ if (i % (tail.length / split_count)).zero? && split_count > splits.length
49
+ splits << self.class.new
50
+ splits.last.content << header
51
+ end
52
+
53
+ splits.last.content << row
54
+ end
55
+
56
+ splits
57
+ end
58
+
59
+ def write! outfile = nil
60
+ outfile ||= default_outfile_name
61
+ CSV.open(outfile, "wb") do |csv|
62
+ content.each do |row|
63
+ csv << row
64
+ end
65
+ end
66
+
67
+ puts "wrote #{content.length} rows to #{outfile}"
68
+ end
69
+
70
+ private
71
+
72
+ def default_outfile_name
73
+ "#{Time.now.strftime("%Y%m%d%H%M%S")}_#{@filepaths.length}_files.csv"
74
+ end
75
+ end
@@ -0,0 +1,214 @@
1
+ require './lib/csv_monster'
2
+
3
+ def sample_csv_filepath
4
+ File.expand_path(File.join("..", "support", "sample.csv"), __FILE__)
5
+ end
6
+
7
+ def another_sample_csv_filepath
8
+ File.expand_path(File.join("..", "support", "another_sample.csv"), __FILE__)
9
+ end
10
+
11
+ def odd_number_of_records_csv_filepath
12
+ File.expand_path(File.join("..", "support", "odd_number_of_records.csv"), __FILE__)
13
+ end
14
+
15
+ def safely_rm file
16
+ FileUtils.rm file if File.exists? file
17
+ end
18
+
19
+ describe CSVMonster do
20
+ describe "#+" do
21
+ let(:csv_monster) { described_class.new sample_csv_filepath }
22
+ let(:another_csv_monster) { described_class.new another_sample_csv_filepath }
23
+
24
+ subject { csv_monster + another_csv_monster }
25
+
26
+ it "yields a new instance with the combination of the two's content" do
27
+ expect(csv_monster.content).to eq [["header_1", "header_2"],
28
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
29
+ ["row_2_column_1_entry", "row_2_column_2_entry"]]
30
+
31
+ expect(another_csv_monster.content).to eq [["header_1", "header_2"],
32
+ ["row_1_column_1_entry_diff_content", "row_1_column_2_entry_diff_content"],
33
+ ["row_2_column_1_entry_diff_content", "row_2_column_2_entry_diff_content"]]
34
+
35
+ result = subject
36
+
37
+ expect(csv_monster.content).to eq [["header_1", "header_2"],
38
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
39
+ ["row_2_column_1_entry", "row_2_column_2_entry"]]
40
+
41
+ expect(another_csv_monster.content).to eq [["header_1", "header_2"],
42
+ ["row_1_column_1_entry_diff_content", "row_1_column_2_entry_diff_content"],
43
+ ["row_2_column_1_entry_diff_content", "row_2_column_2_entry_diff_content"]]
44
+
45
+ expect(result.content).to eq [["header_1", "header_2"],
46
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
47
+ ["row_2_column_1_entry", "row_2_column_2_entry"],
48
+ ["row_1_column_1_entry_diff_content", "row_1_column_2_entry_diff_content"],
49
+ ["row_2_column_1_entry_diff_content", "row_2_column_2_entry_diff_content"]]
50
+ end
51
+ end
52
+
53
+ describe "#==" do
54
+ let(:csv_monster) { described_class.new }
55
+ let(:another_csv_monster) { described_class.new }
56
+
57
+ context "when the content is the same" do
58
+ before do
59
+ csv_monster.filepaths = sample_csv_filepath
60
+ another_csv_monster.filepaths = sample_csv_filepath
61
+ end
62
+
63
+ subject { csv_monster == another_csv_monster }
64
+ it { expect(subject).to be_true }
65
+ end
66
+
67
+ context "when the content is different" do
68
+ before do
69
+ csv_monster.filepaths = sample_csv_filepath
70
+ another_csv_monster.filepaths = another_sample_csv_filepath
71
+ end
72
+
73
+ subject { csv_monster == another_csv_monster }
74
+ it { expect(subject).to be_false }
75
+ end
76
+ end
77
+
78
+ describe "#content" do
79
+ let(:csv_monster) { described_class.new }
80
+
81
+ context "with a single csv file" do
82
+ before { csv_monster.filepaths = sample_csv_filepath }
83
+ subject { csv_monster.content }
84
+ it { expect(subject).to eq [["header_1", "header_2"],
85
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
86
+ ["row_2_column_1_entry", "row_2_column_2_entry"]] }
87
+ end
88
+
89
+ context "with multiple csv files" do
90
+ before { csv_monster.filepaths = [sample_csv_filepath, another_sample_csv_filepath] }
91
+ subject { csv_monster.content }
92
+
93
+ it "combines the content, removing headers from all but the first" do
94
+ expect(subject).to eq [["header_1", "header_2"],
95
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
96
+ ["row_2_column_1_entry", "row_2_column_2_entry"],
97
+ ["row_1_column_1_entry_diff_content", "row_1_column_2_entry_diff_content"],
98
+ ["row_2_column_1_entry_diff_content", "row_2_column_2_entry_diff_content"]]
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "#content_length" do
104
+ let(:csv_monster) { described_class.new sample_csv_filepath }
105
+ subject { csv_monster.content_length }
106
+
107
+ it "equals the number of entries" do
108
+ expect(subject).to eq 3
109
+ end
110
+ end
111
+
112
+ describe "#split" do
113
+ let(:number_of_splits) { 2 }
114
+
115
+ context "with an even number of records (excluding header)" do
116
+ let(:csv_monster) { described_class.new sample_csv_filepath }
117
+
118
+ subject { csv_monster.split(number_of_splits) }
119
+
120
+ it "leaves the original instance unchanged" do
121
+ expect(csv_monster.content).to eq [["header_1", "header_2"],
122
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
123
+ ["row_2_column_1_entry", "row_2_column_2_entry"]]
124
+
125
+ subject
126
+
127
+ expect(csv_monster.content).to eq [["header_1", "header_2"],
128
+ ["row_1_column_1_entry", "row_1_column_2_entry"],
129
+ ["row_2_column_1_entry", "row_2_column_2_entry"]]
130
+ end
131
+
132
+ it "returns the specified number of objects of the same type" do
133
+ result = subject
134
+
135
+ expect(result.length).to eq number_of_splits
136
+
137
+ expect(result[0]).to be_an_instance_of described_class
138
+ expect(result[1]).to be_an_instance_of described_class
139
+ end
140
+
141
+
142
+ it "splits the content amongst the parts evenly" do
143
+ result = subject
144
+
145
+ expect(result[0].content).to eq [["header_1", "header_2"],
146
+ ["row_1_column_1_entry", "row_1_column_2_entry"]]
147
+
148
+ expect(result[1].content).to eq [["header_1", "header_2"],
149
+ ["row_2_column_1_entry", "row_2_column_2_entry"]]
150
+ end
151
+ end
152
+
153
+ context "with an odd number of records (excluding header)" do
154
+ let(:csv_monster) { described_class.new odd_number_of_records_csv_filepath }
155
+
156
+ subject { csv_monster.split(number_of_splits) }
157
+
158
+ it "leaves the original instance unchanged" do
159
+ expect(csv_monster.content).to eq [["header_1", "header_2"],
160
+ ["odd_row_1_column_1_entry", "odd_row_1_column_2_entry"],
161
+ ["odd_row_2_column_1_entry", "odd_row_2_column_2_entry"],
162
+ ["odd_row_3_column_1_entry", "odd_row_3_column_2_entry"],
163
+ ["odd_row_4_column_1_entry", "odd_row_4_column_2_entry"],
164
+ ["odd_row_5_column_1_entry", "odd_row_5_column_2_entry"]]
165
+
166
+ subject
167
+
168
+ expect(csv_monster.content).to eq [["header_1", "header_2"],
169
+ ["odd_row_1_column_1_entry", "odd_row_1_column_2_entry"],
170
+ ["odd_row_2_column_1_entry", "odd_row_2_column_2_entry"],
171
+ ["odd_row_3_column_1_entry", "odd_row_3_column_2_entry"],
172
+ ["odd_row_4_column_1_entry", "odd_row_4_column_2_entry"],
173
+ ["odd_row_5_column_1_entry", "odd_row_5_column_2_entry"]]
174
+ end
175
+
176
+ it "returns the specified number of objects of the same type" do
177
+ result = subject
178
+ expect(result.length).to eq number_of_splits
179
+ expect(result[0]).to be_an_instance_of described_class
180
+ expect(result[1]).to be_an_instance_of described_class
181
+ end
182
+
183
+ it "splits the content amongst the parts approximately evenly" do
184
+ result = subject
185
+
186
+ expect(result[0].content).to eq [["header_1", "header_2"],
187
+ ["odd_row_1_column_1_entry", "odd_row_1_column_2_entry"],
188
+ ["odd_row_2_column_1_entry", "odd_row_2_column_2_entry"]]
189
+
190
+ expect(result[1].content).to eq [["header_1", "header_2"],
191
+ ["odd_row_3_column_1_entry", "odd_row_3_column_2_entry"],
192
+ ["odd_row_4_column_1_entry", "odd_row_4_column_2_entry"],
193
+ ["odd_row_5_column_1_entry", "odd_row_5_column_2_entry"]]
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "#write!" do
199
+ let(:infile) { sample_csv_filepath }
200
+ let(:outfile) { File.expand_path(File.join("..", "support", "test", "write_sample.csv"), __FILE__) }
201
+ let(:csv_monster) { described_class.new infile }
202
+
203
+ before { safely_rm outfile }
204
+ after { safely_rm outfile }
205
+
206
+ subject { csv_monster.write! outfile }
207
+
208
+ it "writes the file to the specified location" do
209
+ expect(File.exists? outfile).to be_false
210
+ subject
211
+ expect(FileUtils.identical? infile, outfile).to be_true
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,3 @@
1
+ header_1,header_2
2
+ row_1_column_1_entry_diff_content,row_1_column_2_entry_diff_content
3
+ row_2_column_1_entry_diff_content,row_2_column_2_entry_diff_content
@@ -0,0 +1,6 @@
1
+ header_1,header_2
2
+ odd_row_1_column_1_entry,odd_row_1_column_2_entry
3
+ odd_row_2_column_1_entry,odd_row_2_column_2_entry
4
+ odd_row_3_column_1_entry,odd_row_3_column_2_entry
5
+ odd_row_4_column_1_entry,odd_row_4_column_2_entry
6
+ odd_row_5_column_1_entry,odd_row_5_column_2_entry
@@ -0,0 +1,3 @@
1
+ header_1,header_2
2
+ row_1_column_1_entry,row_1_column_2_entry
3
+ row_2_column_1_entry,row_2_column_2_entry
File without changes
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csv_monster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeff Iacono
9
+ - Joe Prang
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-06-19 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rspec
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '2'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '2'
47
+ description: A set of utils for working with CSV files
48
+ email:
49
+ - jeff.iacono@gmail.com
50
+ - joseph.prang@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - Gemfile.lock
58
+ - LICENSE
59
+ - README.md
60
+ - Rakefile
61
+ - TODO.md
62
+ - csv_monster.gemspec
63
+ - lib/csv_monster.rb
64
+ - lib/csv_monster/version.rb
65
+ - spec/csv_monster_spec.rb
66
+ - spec/support/another_sample.csv
67
+ - spec/support/odd_number_of_records.csv
68
+ - spec/support/sample.csv
69
+ - spec/support/test/.gitkeep
70
+ homepage: ''
71
+ licenses:
72
+ - MIT
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 1.8.21
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: A monster of a CSV util
95
+ test_files:
96
+ - spec/csv_monster_spec.rb
97
+ - spec/support/another_sample.csv
98
+ - spec/support/odd_number_of_records.csv
99
+ - spec/support/sample.csv
100
+ - spec/support/test/.gitkeep