fasterer-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.
- data/README +0 -0
- data/lib/fasterer_csv.rb +223 -0
- metadata +63 -0
data/README
ADDED
|
File without changes
|
data/lib/fasterer_csv.rb
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
|
|
4
|
+
module FastererCSV
|
|
5
|
+
|
|
6
|
+
class Table < Array
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def format_headers(unformatted)
|
|
10
|
+
unformatted.map { |header| Row.to_key(header) }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :headers
|
|
15
|
+
|
|
16
|
+
def initialize(headers, fail_on_malformed_columns = true)
|
|
17
|
+
@headers = Table.format_headers(headers)
|
|
18
|
+
@fail_on_malformed_columns = fail_on_malformed_columns
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def <<(row)
|
|
22
|
+
if row.class != Row
|
|
23
|
+
row = Row.new(self, row)
|
|
24
|
+
end
|
|
25
|
+
if @headers.length != row.length
|
|
26
|
+
error = "*** WARNING - COLUMN COUNT MISMATCH - WARNING ***\n*** ROW #{size} : EXPECTED #{@headers.length} : FOUND #{row.length}\n\n"
|
|
27
|
+
len = 0
|
|
28
|
+
headers.each do |header|
|
|
29
|
+
len = header.to_s.length if header.to_s.length > len
|
|
30
|
+
end
|
|
31
|
+
headers.each_with_index do |header, i|
|
|
32
|
+
error << sprintf("%-32s : %s\n", header, row[i])
|
|
33
|
+
end
|
|
34
|
+
puts error
|
|
35
|
+
raise error if @fail_on_malformed_columns
|
|
36
|
+
end
|
|
37
|
+
super(row)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Row < Array
|
|
43
|
+
|
|
44
|
+
class << self
|
|
45
|
+
def to_key(key)
|
|
46
|
+
key = "#{key}".downcase.gsub(/\s+/, '_')
|
|
47
|
+
key.empty? ? :_ : key.to_sym
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def headers
|
|
52
|
+
@table.headers
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def initialize(table, array)
|
|
56
|
+
@table = table
|
|
57
|
+
super(array)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def [](i)
|
|
61
|
+
if i.class == Fixnum
|
|
62
|
+
super
|
|
63
|
+
else
|
|
64
|
+
found = headers.index(Row::to_key(i))
|
|
65
|
+
found ? super(found) : nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def []=(key, val)
|
|
70
|
+
if key.class == Fixnum
|
|
71
|
+
super
|
|
72
|
+
else
|
|
73
|
+
key = Row::to_key(key)
|
|
74
|
+
headers << key unless headers.include? key
|
|
75
|
+
found = headers.index(key)
|
|
76
|
+
super(found, val)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def in_order(columns)
|
|
81
|
+
columns.map do |column_name|
|
|
82
|
+
self[column_name].nil? ? '\N' : self[column_name]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def merge(hsh)
|
|
87
|
+
hsh.each do |key, value|
|
|
88
|
+
self[key] = value
|
|
89
|
+
end
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def to_hash
|
|
94
|
+
headers.inject({}) do |memo, h|
|
|
95
|
+
memo[h] = self[h]
|
|
96
|
+
memo
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class << self
|
|
102
|
+
|
|
103
|
+
def headers(file, quot = '~', sep = ',', &block)
|
|
104
|
+
parse_headers(File.open(file, 'r') {|io| io.gets }, quot, sep, &block)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def read(file, quot = '~', sep = ',', &block)
|
|
108
|
+
parse(File.open(file, 'r') { |io| io.sysread(File.size(file)) }, quot, sep, &block)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def parse_headers(data, quot = '~', sep = ',', &block)
|
|
112
|
+
parse(data, quot, sep, &block).headers
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def parse(data, quot = '~', sep = ',')
|
|
116
|
+
q, s, row, column, inquot, clean, maybe, table, field = quot[0], sep[0], [], [], false, true, false, nil, true
|
|
117
|
+
|
|
118
|
+
data.each_byte do |c|
|
|
119
|
+
next if c == ?\r
|
|
120
|
+
|
|
121
|
+
if maybe && c == s
|
|
122
|
+
row << column.join
|
|
123
|
+
column.clear
|
|
124
|
+
clean, inquot, maybe, field = true, false, false, true
|
|
125
|
+
elsif maybe && c == ?\n && table.nil?
|
|
126
|
+
row << column.join
|
|
127
|
+
column.clear
|
|
128
|
+
table = Table.new(row)
|
|
129
|
+
row, clean, inquot, maybe, field = [], true, false, false, false
|
|
130
|
+
elsif maybe && c == ?\n
|
|
131
|
+
row << column.join
|
|
132
|
+
column.clear
|
|
133
|
+
table << row
|
|
134
|
+
row, clean, inquot, maybe, field = [], true, false, false, false
|
|
135
|
+
elsif clean && c == q
|
|
136
|
+
inquot, clean = true, false
|
|
137
|
+
elsif maybe && c == q
|
|
138
|
+
column << c.chr
|
|
139
|
+
clean, maybe = false, false
|
|
140
|
+
elsif c == q
|
|
141
|
+
maybe = true
|
|
142
|
+
elsif inquot
|
|
143
|
+
column << c.chr
|
|
144
|
+
clean = false
|
|
145
|
+
elsif c == s
|
|
146
|
+
row << (column.empty? ? nil : column.join)
|
|
147
|
+
column.clear
|
|
148
|
+
clean, field = true, true
|
|
149
|
+
elsif c == ?\n && table.nil?
|
|
150
|
+
row << (column.empty? ? nil : column.join)
|
|
151
|
+
column.clear
|
|
152
|
+
table = Table.new(row)
|
|
153
|
+
row, clean, inquot, field = [], true, false, false
|
|
154
|
+
elsif c == ?\n
|
|
155
|
+
row << (column.empty? ? nil : column.join)
|
|
156
|
+
column.clear
|
|
157
|
+
table << row
|
|
158
|
+
row, clean, inquot, field = [], true, false, false
|
|
159
|
+
else
|
|
160
|
+
column << c.chr
|
|
161
|
+
clean = false
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if !clean
|
|
166
|
+
if maybe
|
|
167
|
+
row << column.join
|
|
168
|
+
else
|
|
169
|
+
row << (column.empty? ? nil :column.join)
|
|
170
|
+
end
|
|
171
|
+
table << row
|
|
172
|
+
elsif field
|
|
173
|
+
row << (column.empty? ? nil : column.join)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
table.each do |line|
|
|
177
|
+
yield(line)
|
|
178
|
+
end if block_given?
|
|
179
|
+
|
|
180
|
+
table
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def quot_row(row, q = '~', s = ',')
|
|
184
|
+
row.map do |val|
|
|
185
|
+
if val.nil?
|
|
186
|
+
""
|
|
187
|
+
else
|
|
188
|
+
val = String(val)
|
|
189
|
+
if val.length == 0
|
|
190
|
+
q * 2
|
|
191
|
+
else
|
|
192
|
+
val[/[#{q}#{s}\n]/] ? q + val.gsub(q, q * 2) + q : val
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end.join(s) + "\n"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
class IOWriter
|
|
199
|
+
def initialize(file, quot = '~', sep = ',') @io = file; @quot = quot; @sep = sep end
|
|
200
|
+
def <<(row)
|
|
201
|
+
raise "can only write arrays! #{row.class} #{row.inspect}" unless row.class == Array || row.class == Row
|
|
202
|
+
@io.syswrite FastererCSV::quot_row(row, @quot, @sep);
|
|
203
|
+
row
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def generate(quot = '~', sep = ',', &block)
|
|
208
|
+
builder = StringIO.new
|
|
209
|
+
write(builder, quot, sep, &block)
|
|
210
|
+
builder.string
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def write(out, quot = '~', sep = ',', &block)
|
|
214
|
+
if out.class == String
|
|
215
|
+
File.open(out, "w") do |io|
|
|
216
|
+
write(io, quot, sep, &block)
|
|
217
|
+
end
|
|
218
|
+
else
|
|
219
|
+
yield(IOWriter.new(out, quot, sep))
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fasterer-csv
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 0
|
|
8
|
+
- 1
|
|
9
|
+
version: 0.0.1
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Mason
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-05-03 00:00:00 -07:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: CSV parsing awesomeness
|
|
22
|
+
email: mason@honk.com
|
|
23
|
+
executables: []
|
|
24
|
+
|
|
25
|
+
extensions: []
|
|
26
|
+
|
|
27
|
+
extra_rdoc_files:
|
|
28
|
+
- README
|
|
29
|
+
files:
|
|
30
|
+
- README
|
|
31
|
+
- lib/fasterer_csv.rb
|
|
32
|
+
has_rdoc: true
|
|
33
|
+
homepage: http://github.com/gnovos/fasterer-csv
|
|
34
|
+
licenses: []
|
|
35
|
+
|
|
36
|
+
post_install_message:
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
segments:
|
|
46
|
+
- 0
|
|
47
|
+
version: "0"
|
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
segments:
|
|
53
|
+
- 0
|
|
54
|
+
version: "0"
|
|
55
|
+
requirements: []
|
|
56
|
+
|
|
57
|
+
rubyforge_project:
|
|
58
|
+
rubygems_version: 1.3.6
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 3
|
|
61
|
+
summary: A faster implementation fo a CSV parser than FasterCSV
|
|
62
|
+
test_files: []
|
|
63
|
+
|