motion-csv 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,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWEyYmZjNDU5YzI4ZGFmODBjZmQxODc3OWMyMTEyM2VmZmNjMTUyMw==
5
+ data.tar.gz: !binary |-
6
+ ZmY5N2E0MzM0YjZlZDBkMmNiZjZiM2YwZDRjZDljNjg4Nzg3Y2E4OA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Y2E1NmIyM2E4NDFlM2FlMmMwYmQ3ZDEwODEyN2RmMDhiMGQyYzJhMTYyZDA1
10
+ M2U3MTMwNTUxM2I4ZDViNzFjZWY0MDYyNDU1ZGQ2YmY4MGRiN2EyYzBjMDk0
11
+ ZWJlMTVjYWJiMjViNzE5NGJjZmQwMGZlNjhjNmMyN2NmN2RjYzY=
12
+ data.tar.gz: !binary |-
13
+ YjczNjRhM2NjMTJlMTQ1YWFkMzAwZmRlNmJhZjc1ZjU3NjlhYzBjYjBhNTE1
14
+ NmMzMDA4MGNiZWIzZTUzMjExNDc0MTY0MDdlOTk5ZmExNTE4Njk2MWI4ZGZk
15
+ OTQyYTU3Yzk5MTFmM2E0MDhiOGVkNTQ1MDM5MThlMWZjODRiZjc=
@@ -0,0 +1,41 @@
1
+ # motion-csv
2
+
3
+ This is a RubyMotion friendly port of fasterer-csv by Mason: http://rubygems.org/gems/fasterer-csv
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'motion-csv'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install motion-csv
18
+
19
+ ## Usage
20
+
21
+ Check out the `specs` dorectory for usage examples, but here's a brief example:
22
+
23
+ ```ruby
24
+ csv_string = "a,b,c,d
25
+ 1,2,3,4
26
+ 5,6,7,whatever"
27
+
28
+ csv = MotionCSV.parse(csv_string)
29
+
30
+ puts csv.headers # [:a, :b, :c, :d]
31
+ puts csv.first[:b] # 2
32
+ puts csv.last[:d] # "whatever"
33
+ ```
34
+
35
+ ## Contributing
36
+
37
+ 1. Fork it
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
40
+ 4. Push to the branch (`git push origin my-new-feature`)
41
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "This file must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
6
+ Motion::Project::App.setup do |app|
7
+ app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion-csv/**/*.rb")))
8
+ end
@@ -0,0 +1,427 @@
1
+ module MotionCSV
2
+
3
+ class Table < Array
4
+
5
+ class << self
6
+ def format_headers(unformatted)
7
+ unformatted.map { |header| Row.to_key(header) }
8
+ end
9
+ end
10
+
11
+ attr_reader :headers, :lines, :line_block
12
+
13
+ def initialize(headers, fail_on_malformed_columns = true, &line_block)
14
+ @headers = Table.format_headers(headers)
15
+ @fail_on_malformed_columns = fail_on_malformed_columns
16
+ @line_block = line_block
17
+ @lines = 0
18
+ @indexes = {}
19
+ end
20
+
21
+ def <<(row)
22
+ @lines += 1
23
+ if !row.is_a?(Row)
24
+ row = Row.new(self, row, @lines)
25
+ end
26
+ if @headers.length != row.length
27
+ error = "*** WARNING - COLUMN COUNT MISMATCH - WARNING ***\n*** ROW #{size} : EXPECTED #{@headers.length} : FOUND #{row.length}\n\n"
28
+ len = 0
29
+ headers.each do |header|
30
+ len = header.to_s.length if header.to_s.length > len
31
+ end
32
+ headers.each_with_index do |header, i|
33
+ error << sprintf("%-32s : %s\n", header, row[i])
34
+ end
35
+ puts error
36
+ raise error if @fail_on_malformed_columns
37
+ end
38
+ if line_block
39
+ line_block.call(row)
40
+ else
41
+ super(row)
42
+ end
43
+ end
44
+ alias_method :push, :<<
45
+
46
+ def merge(*tables)
47
+
48
+ tables.each do |table|
49
+ matching = self.headers & table.headers
50
+
51
+ key = {}
52
+
53
+ table.each do |row|
54
+ matching.each do |match|
55
+ key[match] = row[match]
56
+ end
57
+
58
+ self.lookup(key) { |r| r.merge(row) }
59
+ end
60
+ end
61
+
62
+ self
63
+
64
+ end
65
+
66
+ def index(columns, reindex = false)
67
+ columns = columns.compact.uniq.sort { |a, b| a.to_s <=> b.to_s }.map { |column| Row.to_key(column) }
68
+
69
+ key = columns.join('|#|')
70
+
71
+ @indexes[key] ||= {}
72
+
73
+ index = @indexes[key]
74
+
75
+ if reindex || index.empty?
76
+
77
+ self.each do |row|
78
+ vkey = columns.map { |column| row[column] }
79
+ index[vkey] ||= []
80
+ index[vkey] << row
81
+ end
82
+ end
83
+ index
84
+ end
85
+
86
+ def lookup(key)
87
+
88
+ values = []
89
+ columns = key.keys.compact.uniq.sort { |a, b| a.to_s <=> b.to_s }.map do |column|
90
+ values << key[column]
91
+ Row.to_key(column)
92
+ end
93
+
94
+ rows = index(columns)[values]
95
+ if rows && block_given?
96
+ rows.each do |row|
97
+ yield(row)
98
+ end
99
+ end
100
+
101
+ rows
102
+ end
103
+
104
+ def write(file, quot = '"', sep = ',')
105
+ MotionCSV.write(file, quot, sep) do |out|
106
+ out << headers
107
+ each do |row|
108
+ out << row
109
+ end
110
+ end
111
+ end
112
+
113
+ alias_method :rows, :to_a
114
+ alias_method :merge!, :merge
115
+
116
+ end
117
+
118
+ class Row < Array
119
+
120
+ class << self
121
+ def to_key(key)
122
+ key = "#{key}".downcase.gsub(/\s+/, '_')
123
+ key.empty? ? :_ : key.to_sym
124
+ end
125
+ end
126
+
127
+ def headers
128
+ @headers ||= @table.headers.dup
129
+ end
130
+
131
+ attr_reader :line
132
+
133
+ def initialize(table, array, line=-1)
134
+ @table = table
135
+ @line = line
136
+ super(array)
137
+ end
138
+
139
+ def [](*is)
140
+ is.each do |i|
141
+ val = if i.is_a? Fixnum
142
+ super
143
+ else
144
+ found = headers.index(Row::to_key(i))
145
+ found ? super(found) : nil
146
+ end
147
+ return val unless val.nil?
148
+ end
149
+ nil
150
+ end
151
+
152
+ def []=(key, val)
153
+ if key.is_a? Fixnum
154
+ super
155
+ else
156
+ key = Row::to_key(key)
157
+ headers << key unless headers.include? key
158
+ found = headers.index(key)
159
+ super(found, val)
160
+ end
161
+ end
162
+
163
+ def pull(*columns)
164
+ columns.map do |column|
165
+ column = [nil] if column.nil?
166
+ self[*column]
167
+ end
168
+ end
169
+
170
+ def merge(row)
171
+ if row.is_a? Row
172
+ row.headers.each do |header|
173
+ self[header] = row[header]
174
+ end
175
+ else
176
+ row.each do |key, value|
177
+ self[key] = value
178
+ end
179
+ end
180
+ self
181
+ end
182
+
183
+ def to_hash
184
+ headers.inject({}) do |memo, h|
185
+ memo[h] = self[h]
186
+ memo
187
+ end
188
+ end
189
+
190
+ def key?(key)
191
+ keys.include?(Row.to_key(key))
192
+ end
193
+
194
+ def value?(value)
195
+ values.include?(value)
196
+ end
197
+
198
+ def method_missing(method, *args, &block)
199
+ to_hash.send(method, *args, &block)
200
+ end
201
+
202
+ alias_method :keys, :headers
203
+ alias_method :values, :to_a
204
+
205
+ alias_method :has_key?, :key?
206
+ alias_method :member?, :key?
207
+ alias_method :include?, :key?
208
+
209
+ alias_method :has_value?, :value?
210
+ alias_method :merge!, :merge
211
+
212
+ end
213
+
214
+ class NumericConversion < Array
215
+
216
+ def initialize
217
+ @int = @float = true
218
+ @dot = false
219
+ end
220
+
221
+ def clear
222
+ @int = @float = true
223
+ @dot = false
224
+ super
225
+ end
226
+
227
+ def <<(ch)
228
+ if ch == ?-.ord
229
+ @float = @int = size == 0
230
+ elsif (ch > ?9.ord || ch < ?0.ord) && ch != ?..ord
231
+ @int = @float = false
232
+ elsif ch == ?..ord && @dot
233
+ @int = @float = false
234
+ elsif ch == ?..ord
235
+ @int = false
236
+ @dot = true
237
+ end
238
+
239
+ super(ch.chr)
240
+ end
241
+
242
+ def convert(as_string = false)
243
+ if as_string
244
+ join
245
+ elsif empty?
246
+ nil
247
+ elsif @int
248
+ join.to_i
249
+ elsif @float
250
+ join.to_f
251
+ else
252
+ join
253
+ end
254
+ end
255
+
256
+ end
257
+
258
+ class NoConversion < Array
259
+
260
+ def <<(ch)
261
+ super(ch.chr)
262
+ end
263
+
264
+ def convert(as_string = false)
265
+ if as_string
266
+ join
267
+ elsif empty?
268
+ nil
269
+ else
270
+ join
271
+ end
272
+ end
273
+
274
+ end
275
+
276
+ class IOWriter
277
+ def initialize(file, quot = '"', sep = ',', quotenum = false)
278
+ @first = true; @io = file; @quot = quot; @sep = sep; @quotenum = quotenum
279
+ end
280
+
281
+ def <<(row)
282
+ raise "can only write arrays! #{row.class} #{row.inspect}" unless row.is_a? Array
283
+ if @first && row.is_a?(Row)
284
+ self.<<(row.headers)
285
+ end
286
+ @first = false
287
+ @io.syswrite MotionCSV::quot_row(row, @quot, @sep, @quotenum)
288
+ row
289
+ end
290
+ end
291
+
292
+ class << self
293
+
294
+ def headers(file, quot = '"', sep = ',', fail_on_malformed = true, column = NoConversion.new, &block)
295
+ parse_headers(File.open(file, 'r') { |io| io.gets }, quot, sep, fail_on_malformed, column, &block)
296
+ end
297
+
298
+ def read(file, quot = '"', sep = ',', fail_on_malformed = true, column = NoConversion.new, &block)
299
+ File.open(file, 'r') do |io|
300
+ parse(io, quot, sep, fail_on_malformed, column, &block)
301
+ end
302
+ end
303
+
304
+ def convread(file, quot = '"', sep = ',', fail_on_malformed = true, column = NumericConversion.new, &block)
305
+ File.open(file, 'r') do |io|
306
+ parse(io, quot, sep, fail_on_malformed, column, &block)
307
+ end
308
+ end
309
+
310
+ def parse_headers(data, quot = '"', sep = ',', fail_on_malformed = true, column = NoConversion.new, &block)
311
+ parse(data, quot, sep, fail_on_malformed, column, &block).headers
312
+ end
313
+
314
+ def parse(io, quot = '"', sep = ',', fail_on_malformed = true, column = NoConversion.new, &block)
315
+ q, s, row, inquot, clean, maybe, table, field, endline = quot.ord, sep.ord, [], false, true, false, nil, true, false
316
+
317
+ io.each_byte do |c|
318
+ next if c == ?\r.ord
319
+
320
+ if maybe && c == s
321
+ row << column.convert(true)
322
+ column.clear
323
+ clean, inquot, maybe, field, endline = true, false, false, true, false
324
+ elsif maybe && c == ?\n.ord && table.nil?
325
+ row << column.convert(true) unless (column.empty? && endline)
326
+ column.clear
327
+ table = Table.new(row, fail_on_malformed, &block) unless row.empty?
328
+ row, clean, inquot, maybe, field, endline = [], true, false, false, false, true
329
+ elsif maybe && c == ?\n.ord
330
+ row << column.convert(true) unless (column.empty? && endline)
331
+ column.clear
332
+ table << row unless row.empty?
333
+ row, clean, inquot, maybe, field, endline = [], true, false, false, false, true
334
+ elsif clean && c == q
335
+ inquot, clean, endline = true, false, false
336
+ elsif maybe && c == q
337
+ column << c
338
+ clean, maybe, endline = false, false, false
339
+ elsif c == q
340
+ maybe, endline = true, false
341
+ elsif inquot
342
+ column << c
343
+ clean, endline = false, false
344
+ elsif c == s
345
+ row << column.convert(false)
346
+ column.clear
347
+ clean, field, endline = true, true, false
348
+ elsif c == ?\n.ord && table.nil?
349
+
350
+ row << column.convert(false) unless column.empty? && endline
351
+
352
+ column.clear
353
+ table = Table.new(row, fail_on_malformed, &block) unless row.empty?
354
+ row, clean, inquot, field, endline = [], true, false, false, true
355
+ elsif c == ?\n.ord
356
+
357
+ row << column.convert(false) unless column.empty? && endline
358
+
359
+ column.clear
360
+ table << row unless row.empty?
361
+ row, clean, inquot, field, endline = [], true, false, false, true
362
+ else
363
+ column << c
364
+ clean, endline = false, false
365
+ end
366
+ end
367
+
368
+ if !clean
369
+ row << column.convert(maybe)
370
+ if table
371
+ table << row unless row.empty?
372
+ else
373
+ table = Table.new(row, fail_on_malformed, &block) unless row.empty?
374
+ end
375
+ elsif field
376
+ row << column.convert(maybe)
377
+ end
378
+
379
+ table
380
+ end
381
+
382
+ def quot_row(row, q = '"', s = ',', numquot = false)
383
+ num_quot = /(?:[#{q}#{s}\n]|^\d+$)/
384
+ need_quot = /[#{q}#{s}\n]/
385
+ row.map do |val|
386
+ if val.nil?
387
+ ""
388
+ elsif val.is_a? Numeric
389
+ val.to_s
390
+ else
391
+ quot = (val.is_a?(Symbol) || !numquot) ? need_quot : num_quot
392
+ val = String(val)
393
+ if val.length == 0
394
+ q * 2
395
+ else
396
+ val[quot] ? q + val.gsub(q, q * 2) + q : val
397
+ end
398
+ end
399
+ end.join(s) + "\n"
400
+ end
401
+
402
+ def generate(quot = '"', sep = ',', &block)
403
+ builder = StringIO.new
404
+ write(builder, quot, sep, &block)
405
+ builder.string
406
+ end
407
+
408
+ def write(data, quot = '"', sep = ',', quotenum = false, &block)
409
+ out(data, 'w', quot, sep, quotenum, &block)
410
+ end
411
+
412
+ def append(data, quot = '"', sep = ',', quotenum = false, &block)
413
+ out(data, 'a', quot, sep, quotenum, &block)
414
+ end
415
+
416
+ def out(data, mode = 'w', quot = '"', sep = ',', quotenum = false, &block)
417
+ if data.class == String
418
+ File.open(data, mode) do |io|
419
+ out(io, mode, quot, sep, quotenum, &block)
420
+ end
421
+ else
422
+ yield(IOWriter.new(data, quot, sep, quotenum))
423
+ end
424
+ end
425
+
426
+ end
427
+ end
@@ -0,0 +1,3 @@
1
+ module MotionCSV
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-csv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mark Rickert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ prerelease: false
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ! '>='
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ type: :development
27
+ description: ! 'This is a RubyMotion friendly port of fasterer-csv by Mason: http://rubygems.org/gems/fasterer-csv'
28
+ email:
29
+ - mjar81@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/motion-csv/motion-csv.rb
36
+ - lib/motion-csv/version.rb
37
+ - lib/motion-csv.rb
38
+ homepage:
39
+ licenses:
40
+ - MIT
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.0.7
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: ! 'This is a RubyMotion friendly port of fasterer-csv by Mason: http://rubygems.org/gems/fasterer-csv'
62
+ test_files: []