cassava 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color -f d
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cassava.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Kurt Stephens
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.
@@ -0,0 +1,42 @@
1
+ # Cassava
2
+
3
+ A command-line CSV tool.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'cassava'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install cassava
18
+
19
+ ## Usage
20
+
21
+ # Concat mulitple CSVs, while merging columns:
22
+ $ cassava cat first.csv second.csv
23
+
24
+ # Cut columns out of result; '-' is used to pipe results to next command:
25
+ $ cassava cat first.csv second.csv - cut -c first_name,last_name
26
+
27
+ # Format as ASCII table.
28
+ $ cassava format first.csv
29
+
30
+ # Select where.
31
+ $ cassava cat *.csv - where last_name=Smith - format
32
+
33
+ # Sort by a column:
34
+ $ cassava cat *.csv - sort -by last_name,first_name - format
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ gem 'rspec'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Default => :test"
6
+ task :default => :test
7
+
8
+ desc "Run all tests"
9
+ task :test => [ :spec ]
10
+
11
+ desc "Run specs"
12
+ RSpec::Core::RakeTask.new(:spec) do |t|
13
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
14
+ # Put spec opts in a file named .rspec in root
15
+ end
16
+
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- ruby -*-
3
+ $: << File.expand_path("../../lib", __FILE__)
4
+ if ARGV[0] == '-D'
5
+ ARGV.shift
6
+ $DEBUG = true
7
+ require 'rubygems'
8
+ gem 'ruby-debug'
9
+ require 'ruby-debug'
10
+ end
11
+ require 'cassava/main'
12
+ exit(Cassava::Main.new(ARGV).run!.exit_code)
13
+
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cassava/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cassava"
8
+ gem.version = Cassava::VERSION
9
+ gem.authors = ["Kurt Stephens"]
10
+ gem.email = ["ks.github@kurtstephens.com"]
11
+ gem.description = %q{A command-line CSV tool.}
12
+ gem.summary = %q{A command-line CSV tool.}
13
+ gem.homepage = "https://github.com/kstephens/cassava"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'terminal-table', '~> 1.4.5'
21
+
22
+ gem.add_development_dependency "rake", "~> 10.0.2"
23
+ gem.add_development_dependency "rspec", "~> 2.12.0"
24
+ end
@@ -0,0 +1,6 @@
1
+ require "cassava/version"
2
+
3
+ module Cassava
4
+ end
5
+
6
+ require 'cassava/document'
@@ -0,0 +1,388 @@
1
+ require 'csv'
2
+
3
+ module Cassava
4
+ class Document
5
+ attr_accessor :name, :columns, :column_offset, :offset_column
6
+ attr_accessor :rows
7
+ attr_accessor :debug
8
+
9
+ def initialize opts = nil
10
+ @opts = opts
11
+ @name = opts[:name] or raise ArgumentError, "name not specified"
12
+ @rows = [ ]
13
+ @index = { }
14
+ self.columns = opts[:columns] || [ ]
15
+ if x = opts[:rows]
16
+ append_row! x
17
+ end
18
+ end
19
+
20
+ def columns= x
21
+ @columns = x
22
+ update_columns!
23
+ end
24
+
25
+ def update_columns!
26
+ @ncols = @columns.size
27
+ @column_offset = { }
28
+ @offset_column = [ ]
29
+ i = -1
30
+ @columns.map! do | c |
31
+ i += 1
32
+ c = c.to_s
33
+ next if c.empty?
34
+ c = c.to_sym
35
+ @column_offset[c] = i
36
+ @offset_column[i] = c
37
+ c
38
+ end
39
+ @column_types = nil
40
+ @index.keep_if { | c, h | @column_offset[c] }
41
+ self
42
+ end
43
+
44
+ def add_column! c
45
+ c = c.to_sym
46
+ unless i = @column_offset[c]
47
+ i = @columns.size
48
+ @columns << c
49
+ update_columns!
50
+ end
51
+ i
52
+ end
53
+
54
+ def to_column_names! a
55
+ a.map! do | x |
56
+ case x
57
+ when Integer
58
+ c = @columns[x]
59
+ when String
60
+ if x == (i = x.to_i).to_s
61
+ c = @columns[i]
62
+ else
63
+ c = x.to_sym
64
+ end
65
+ when Symbol
66
+ c = x
67
+ else
68
+ raise TypeError, "#{x.inspect}"
69
+ end
70
+ raise TypeError, "#{x.inspect} => #{c.inspect}" unless c
71
+ c
72
+ end
73
+ a
74
+ end
75
+
76
+ def nrows
77
+ @rows.size
78
+ end
79
+
80
+ def empty_rows!
81
+ @rows = [ ]
82
+ @index.clear
83
+ @column_types = nil
84
+ self
85
+ end
86
+
87
+ def append_rows! rows
88
+ return self unless rows
89
+ if Document === rows
90
+ rows.columns.each { | c | add_column!(c) }
91
+ rows = rows.rows
92
+ end
93
+ rows.map!{ | r | array_to_row(r) }
94
+ row_i = @rows.size
95
+ @rows.concat(rows)
96
+ rows.each do | r |
97
+ @ncols = r.size if @ncols < r.size
98
+ r[:_row_i] = row_i
99
+ row_i += 1
100
+ end
101
+ @index.clear
102
+ @column_types = nil
103
+ self
104
+ end
105
+
106
+ def parse!
107
+ # debugger if $DEBUG
108
+ csv = nil
109
+ if RUBY_VERSION =~ /^1\.8/
110
+ @rows = [ ]
111
+ csv = CSV.open(name, "rb", @opts[:col_sep]) do | r |
112
+ @rows << r
113
+ end
114
+ else
115
+ csv_opts = { }
116
+ csv_opts[:col_sep] = @opts[:col_sep] if @opts[:col_sep]
117
+ csv = CSV.open(name, "rb", csv_opts)
118
+ @rows = csv.read
119
+ end
120
+
121
+ @columns = @rows.shift if @columns.empty?
122
+ update_columns!
123
+
124
+ row_i = 0
125
+ @rows.map! do | r |
126
+ @ncols = r.size if @ncols < r.size
127
+ h = { :_row_i => (row_i += 1) }
128
+ @column_offset.each do | c, i |
129
+ h[c] = r[i] if c && i
130
+ end
131
+ h
132
+ end
133
+ # debugger
134
+ self
135
+ ensure
136
+ csv.close if csv
137
+ end
138
+
139
+ def index! c
140
+ c = @column[c] if Integer === c
141
+ unless ind = @index[c]
142
+ ind = { }
143
+ @rows.each do | r |
144
+ (ind[r[c]] ||= [ ]) << r
145
+ end
146
+ @index[c] = ind
147
+ end
148
+ ind
149
+ end
150
+
151
+ def get c, v
152
+ index!(c)[v]
153
+ end
154
+
155
+ def emit! file
156
+ out = nil
157
+ case file
158
+ when '/dev/stdout', '/dev/fd/0'
159
+ tmp_file = true
160
+ out = $stdout
161
+ when %r{^/dev/}
162
+ tmp_file = true
163
+ end
164
+ if tmp_file
165
+ tmp_file = "/tmp/cassava-#{$$}.csv"
166
+ # $stderr.puts " #{file} => #{tmp_file}"
167
+ file = tmp_file
168
+ end
169
+
170
+ _emit! file
171
+
172
+ if tmp_file
173
+ out ||= File.open(tmp, "w")
174
+ # $stderr.puts " #{tmp_file} => #{out}"
175
+ out.write(File.read(tmp_file))
176
+ end
177
+ ensure
178
+ File.unlink(tmp_file) if tmp_file
179
+ end
180
+
181
+ def _emit! file
182
+ CSV.open(file, "wb") do | out |
183
+ a = @offset_column.map do | c |
184
+ c && c.to_s
185
+ end
186
+ out << a
187
+ @rows.each do | r |
188
+ a = @offset_column.map do | i |
189
+ i && r[i]
190
+ end
191
+ out << a
192
+ end
193
+ end
194
+ self
195
+ end
196
+ alias :emit! :_emit!
197
+
198
+ def array_to_row a, columns = nil
199
+ if Array === a
200
+ columns ||= self.columns
201
+ h = { }
202
+ columns.each_with_index do | c, i |
203
+ h[c] = a[i]
204
+ end
205
+ a = h
206
+ end
207
+ a
208
+ end
209
+
210
+ def row_to_array r
211
+ unless Array === r
212
+ r = @offset_column.map do | c |
213
+ c && r[c]
214
+ end
215
+ end
216
+ r
217
+ end
218
+
219
+ def coerce_to_strings!
220
+ rows.each do | r |
221
+ r.each do | k, v |
222
+ r[k] = v.to_s unless String === v
223
+ end
224
+ end
225
+ self
226
+ end
227
+
228
+ def cast_strings rows
229
+ rows.each do | r |
230
+ r.each do | k, v |
231
+ next if v.nil?
232
+ old_v = v
233
+ v = v.to_s
234
+ if String === v
235
+ case v
236
+ when /\A[-+]?\d+\Z/
237
+ v = v.to_i
238
+ when /\A([-+]?([0-9]+\.[0-9]+|\.[0-9]+|[0-9]+\.)(e[-+]?\d+)?|[-+]?\d+e[-+]?\d+)\Z/i
239
+ v = v.to_f
240
+ end
241
+ # puts "old_v = #{old_v.inspect} => #{v.inspect}"
242
+ end
243
+ r[k] = v
244
+ end
245
+ end
246
+ rows
247
+ end
248
+
249
+ def cast_strings!
250
+ cast_strings @rows
251
+ @column_types = nil
252
+ self
253
+ end
254
+
255
+ def sort! by = nil
256
+ by ||= @columns
257
+ cast_strings!
258
+ # by = by.map { | x | column_offset[x] }
259
+ ct = { }
260
+ columns.each_with_index do | c, i |
261
+ ct[c] = column_types[i]
262
+ end
263
+ @rows.sort! do | a, b |
264
+ r = 0
265
+ by.each do | c |
266
+ av = a[c]
267
+ bv = b[c]
268
+ case
269
+ when av.nil? && bv.nil?
270
+ r = 0
271
+ when av.nil?
272
+ r = -1
273
+ when bv.nil?
274
+ r = 1
275
+ else
276
+ r = (av <=> bv rescue nil) || 0
277
+ end
278
+ break if r != 0
279
+ end
280
+ r
281
+ end
282
+ self
283
+ end
284
+
285
+ require 'pp'
286
+ def infer_column_types rows = self.rows
287
+ column_types = [ nil ] * @columns.size
288
+ ancestors_cache = { }
289
+ common_ancestor_cache = { }
290
+ rows.each do | r |
291
+ raise unless Hash === r
292
+ @columns.each_with_index do | k, i |
293
+ v = r[k]
294
+ next if v.nil?
295
+ ct = column_types[i]
296
+ vt = v.class
297
+ if ct.nil?
298
+ column_types[i] = vt
299
+ next
300
+ end
301
+ common_ancestor =
302
+ common_ancestor_cache[[ct, vt]] ||=
303
+ begin
304
+ ca =
305
+ ancestors_cache[ct] ||=
306
+ ct.ancestors.delete_if{|x| x.class == Module}
307
+ va =
308
+ ancestors_cache[vt] ||=
309
+ vt.ancestors.delete_if{|x| x.class == Module}
310
+ (ca & va).first || Object
311
+ end
312
+ if @debug && k == :float
313
+ pp [ :k, k, :v, v, :ct, ct, :vt, vt, :ca, ca, :va, va, :common_ancestor, common_ancestor ]
314
+ end
315
+ # if Value's class is not a specialization of column class.
316
+ ct = common_ancestor
317
+ column_types[i] = ct
318
+ end
319
+ end
320
+ # pp columns.zip(column_types)
321
+ column_types
322
+ end
323
+
324
+ def column_types
325
+ unless @column_types
326
+ @column_types ||= infer_column_types
327
+ end
328
+ @column_types
329
+ end
330
+
331
+ def clone_rows rows = self.rows
332
+ rows.map { | r | r.dup }
333
+ end
334
+
335
+ # Format as ASCII table.
336
+ def to_text opts = { }
337
+ gem 'terminal-table'
338
+ require 'terminal-table'
339
+
340
+ table = Terminal::Table.new() do | table |
341
+ # t.title = self.name
342
+ s = table.style
343
+ s.border_x = s.border_y = s.border_i = ''
344
+ # s.border_i = '|'
345
+ s.padding_left = 0
346
+ s.padding_right = 1
347
+
348
+ table << self.columns.map{|c| { :value => c.to_s, :alignment => :center }}
349
+
350
+ # Convert rows to Arrays and handle nil, etc.
351
+ self.rows.each do | r |
352
+ r = self.row_to_array(r)
353
+ r.map! do | c |
354
+ c = case c
355
+ when nil
356
+ ''
357
+ when Integer
358
+ thousands(c)
359
+ else
360
+ c
361
+ end
362
+ # c = "#{c} |"
363
+ end
364
+ table << r
365
+ end
366
+
367
+ # Align numeric columns to the left.
368
+ column_types = infer_column_types(cast_strings(clone_rows))
369
+ column_types.each_with_index do | type, ci |
370
+ if type && type.ancestors.include?(Numeric)
371
+ # puts " column #{ci} #{columns[ci]} #{t}"
372
+ table.align_column(ci, :right)
373
+ end
374
+ end
375
+ end
376
+
377
+
378
+ # Return formatted table.
379
+ table.to_s
380
+ end
381
+
382
+ def thousands x, sep = '_'
383
+ x && x.to_s.reverse!.gsub(/(\d{3})/, "\\1#{sep}").reverse!.sub(/^(\D|\A)#{sep}/, '')
384
+ end
385
+ end
386
+ end
387
+
388
+
@@ -0,0 +1,199 @@
1
+ require 'cassava'
2
+ require 'cassava/document'
3
+
4
+ module Cassava
5
+ class Main
6
+ attr_accessor :progname
7
+ attr_accessor :args, :cmd, :opts, :exit_code
8
+ attr_accessor :result, :output
9
+ attr_accessor :by, :columns
10
+
11
+ def initialize args = nil
12
+ @progname = File.basename($0)
13
+ @args = (args || ARGV).dup
14
+ @opts = { }
15
+ @exit_code = 0
16
+ @output = "/dev/stdout"
17
+ @debug = true
18
+ @select_where = { }
19
+ end
20
+
21
+ def run!
22
+ cmds = [ ]
23
+ last_arg = nil
24
+
25
+ cmd = [ ]
26
+ args = @args.dup
27
+ while arg = args.shift
28
+ if arg == '-'
29
+ if last_arg
30
+ cmd << last_arg
31
+ last_arg = nil
32
+ end
33
+ last_arg = '--result'
34
+ cmds << cmd
35
+ cmd = [ ]
36
+ else
37
+ cmd << arg
38
+ end
39
+ end
40
+ if last_arg
41
+ cmd << last_arg
42
+ last_arg = nil
43
+ end
44
+ cmds << cmd
45
+ # pp cmds
46
+
47
+ # Run each command:
48
+ cmds.each do | cmd |
49
+ @args = cmd.dup
50
+ next_cmd!
51
+ end
52
+
53
+ result.emit!(@output) if result
54
+ self
55
+ rescue ::Exception => exc
56
+ $stderr.puts "#{progname}: ERROR: #{exc.inspect}"
57
+ $stderr.puts " #{exc.backtrace * "\n "}" if @debug || $DEBUG
58
+ @exit_code = 1
59
+ self
60
+ end
61
+
62
+ def next_cmd!
63
+ @cmd = @args.shift
64
+ sel = :"_#{cmd}!"
65
+ raise ArgumentError, "Invalid command: #{cmd.inspect}" unless respond_to?(sel)
66
+ send(sel)
67
+ end
68
+
69
+ def next_document!
70
+ doc = nil
71
+ opts = { }
72
+ until args.empty?
73
+ case arg = args.shift
74
+ when '-cv'
75
+ k = args.shift.to_sym
76
+ v = args.shift
77
+ @select_where[k] = v
78
+ when /\A([^=]+)=(.*)\Z/
79
+ k = $1.to_sym
80
+ v = $2
81
+ @select_where[k] = v
82
+ when '--result'
83
+ doc = @result
84
+ break
85
+ when '-c'
86
+ self.columns = args.shift.split(/\s*,\s*|\s+/)
87
+ when '-by'
88
+ self.by = args.shift.split(/\s*,\s*|\s+/)
89
+ when '-o'
90
+ self.output = args.shift
91
+ when '-FS'
92
+ opts[:col_sep] = args.shift
93
+ when '-IGNORE'
94
+ opts[:ignore] = args.shift
95
+ when '-C'
96
+ opts[:columns] = args.shift.split(/\s*,\s*|\s+/)
97
+ else
98
+ opts[:name] = arg
99
+ doc = Document.new(opts)
100
+ doc.parse!
101
+ break
102
+ end
103
+ end
104
+ doc
105
+ end
106
+
107
+ def self.define_command name, doc, &blk
108
+ meth = :"_#{name}!"
109
+ define_method meth, &blk
110
+ @@commands << { :name => name, :doc => doc }
111
+ end
112
+
113
+ def _cat!
114
+ self.result = next_document!
115
+ until args.empty?
116
+ other = next_document!
117
+ result.append_rows!(other)
118
+ end
119
+ end
120
+
121
+ def _where!
122
+ @select_where = { }
123
+ self.result = next_document!
124
+ result.coerce_to_strings!
125
+ @select_where.each do | col, val |
126
+ new_result = result.dup
127
+ new_result.empty_rows!
128
+ found_rows = result.get(col, val)
129
+ # puts " #{col}=#{val} => #{found_rows.size} rows"
130
+ new_result.append_rows!(found_rows)
131
+ self.result = new_result
132
+ end
133
+ end
134
+ alias :_select! :_where!
135
+
136
+ def _cut!
137
+ self.columns = nil
138
+ self.result = next_document!
139
+ self.columns ||= [ result.columns[0] ]
140
+ # pp result.columns
141
+ result.to_column_names! columns
142
+ new_result = result.dup.empty_rows!
143
+ new_result.columns = columns & result.columns
144
+ # pp new_result.columns
145
+ # pp new_result.column_offset
146
+ rows = self.result.rows.map do | r |
147
+ r = r.dup
148
+ r.keep_if { | c | new_result.column_offset[c] }
149
+ # pp r
150
+ r
151
+ end
152
+ new_result.append_rows! rows
153
+ self.result = new_result
154
+ end
155
+
156
+ def _join!
157
+ self.result = next_document!
158
+ until args.empty?
159
+ left_key = args.shift.to_sym
160
+ right_key = args.shift.to_sym
161
+ right = next_document!
162
+ new_result = Document.new
163
+ result.columns.each do | c |
164
+ new_result.add_column!(c)
165
+ end
166
+ right.columns.each do | c |
167
+ new_result.add_column!(c)
168
+ end
169
+ result.rows.each do | lr |
170
+ rrows = right.get(right_key, lr[left_key])
171
+ unless rrows.empty?
172
+ r = lr.dup
173
+ rrows.each do | rr |
174
+ r.merge(rr)
175
+ end
176
+ new_result.rows << r
177
+ end
178
+ end
179
+ self.result = new_result
180
+ end
181
+ end
182
+
183
+ def _sort!
184
+ self.by = nil
185
+ self.result = next_document!
186
+ by = self.by || args
187
+ by.map! { | c | c.to_sym }
188
+ result.sort!(by)
189
+ end
190
+
191
+ def _format!
192
+ rows = result.to_text
193
+ # $stderr.puts rows
194
+ rows = rows.split("\n").map { | r | { :_ => r } }
195
+ # require 'pp'; pp rows
196
+ self.result = Cassava::Document.new(:name => "#{@result.name}->format", :columns => [ :_ ], :rows => rows)
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,3 @@
1
+ module Cassava
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ class Exception
4
+ alias :_message :message
5
+ def message
6
+ @message ||= _message
7
+ end
8
+ def message= msg
9
+ @message = msg
10
+ end
11
+ end
12
+
13
+ describe "Cassava::Document" do
14
+ attr_accessor :d
15
+ before do
16
+ self.d = Cassava::Document.new(:name => 'test', :columns => [ :nil, :string, :fixnum, :float, :integer, :numeric ])
17
+ rows = <<"END".split("\n").map{|r| r.strip.split(/,\s+/).map{|x| x == 'nil' ? nil : x }}
18
+ nil, nil, nil, nil, nil, nil
19
+ nil, string, nil, nil, nil, nil
20
+ nil, +, nil, nil, nil, nil
21
+ nil, -, nil, nil, nil, nil
22
+ nil, 234+, nil, nil, nil, nil
23
+ nil, 1234ws, nil, nil, nil, nil
24
+ nil, nil, 1234, nil, nil, nil
25
+ nil, nil, +1234, nil, nil, nil
26
+ nil, nil, -1234, nil, nil, nil
27
+ nil, nil, nil, .2, nil, nil
28
+ nil, nil, nil, -1.2, nil, nil
29
+ nil, nil, nil, -2., nil, nil
30
+ nil, nil, nil, 2e10, nil, nil
31
+ nil, nil, nil, 1e1, nil, nil
32
+ nil, nil, nil, 2E-2, nil, nil
33
+ nil, nil, nil, 3E+3, nil, nil
34
+ nil, nil, nil, nil, 1234, nil
35
+ nil, nil, nil, nil, 12341231234523452345345234, nil
36
+ nil, nil, nil, nil, nil, 1234
37
+ nil, nil, nil, nil, nil, 123412348102394812934
38
+ nil, nil, nil, nil, nil, 1234.2342
39
+ END
40
+ d.append_rows! rows
41
+ d.cast_strings!
42
+ end
43
+
44
+ def each_row
45
+ i = 0
46
+ d.rows.each do | r |
47
+ begin
48
+ yield r
49
+ rescue ::Exception => exc
50
+ exc.message = "in row #{i} : #{r.inspect} : #{exc.message}"
51
+ raise exc
52
+ end
53
+ i += 1
54
+ end
55
+ end
56
+
57
+ context "#cast_strings!" do
58
+ it "should not touch nil columns" do
59
+ each_row do | r |
60
+ r[0].should == nil
61
+ end
62
+ end
63
+ it "should not touch string only columns" do
64
+ each_row do | r |
65
+ next if r[1].nil?
66
+ r[1].class.should == String
67
+ end
68
+ end
69
+ it "should coerce integer columns" do
70
+ each_row do | r |
71
+ next if r[2].nil?
72
+ r[2].is_a?(Integer).should == true
73
+ end
74
+ end
75
+ it "should coerce float columns" do
76
+ each_row do | r |
77
+ next if r[3].nil?
78
+ r[3].is_a?(Float).should == true
79
+ end
80
+ end
81
+ end
82
+
83
+ context "#infer_types!" do
84
+ it "should infer nil for all nils" do
85
+ d.column_types[0].should == nil
86
+ end
87
+ it "should infer String for String" do
88
+ d.column_types[1].should == String
89
+ end
90
+ it "should infer Fixum for Fixnum" do
91
+ d.column_types[2].should == Fixnum
92
+ end
93
+ it "should infer Float for all Float" do
94
+ d.column_types[3].should == Float
95
+ end
96
+ it "should infer Integer for Fixnum and Bignum" do
97
+ d.column_types[4].should == Integer
98
+ end
99
+ it "should infer Numeric for Fixnum, Bignum, Float" do
100
+ d.column_types[5].should == Numeric
101
+ end
102
+ end
103
+ end
@@ -0,0 +1 @@
1
+ require 'cassava/main'
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassava
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kurt Stephens
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: terminal-table
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.4.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.4.5
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 10.0.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 10.0.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.12.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.12.0
62
+ description: A command-line CSV tool.
63
+ email:
64
+ - ks.github@kurtstephens.com
65
+ executables:
66
+ - cassava
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - .rspec
72
+ - Gemfile
73
+ - LICENSE.txt
74
+ - README.md
75
+ - Rakefile
76
+ - bin/cassava
77
+ - cassava.gemspec
78
+ - lib/cassava.rb
79
+ - lib/cassava/document.rb
80
+ - lib/cassava/main.rb
81
+ - lib/cassava/version.rb
82
+ - spec/cast_strings_spec.rb
83
+ - spec/spec_helper.rb
84
+ homepage: https://github.com/kstephens/cassava
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ segments:
97
+ - 0
98
+ hash: -1985077025104642675
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ segments:
106
+ - 0
107
+ hash: -1985077025104642675
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.23
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: A command-line CSV tool.
114
+ test_files:
115
+ - spec/cast_strings_spec.rb
116
+ - spec/spec_helper.rb