csv_monster 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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