cassava 0.0.1

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