eccsv 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8dd728098ee2f3066326be199f2657c34b33facd
4
- data.tar.gz: 0472be8f00b3f6c9079cc20808bfd89804ff2212
3
+ metadata.gz: 6cd8161c122089744642e805673f8758e1f9eead
4
+ data.tar.gz: 2243015e40dfa275dd74b291fc1303fd533ad0bb
5
5
  SHA512:
6
- metadata.gz: 9238b550d38a2766e5c53ca152855dfc7f782cc79c98dad60030095138e4a40a68655b14f22c3e376214901294ad81ad3e835e9091c2ee9d810c302e3f744ff3
7
- data.tar.gz: 12599cfb6ae428dc134dd67c64fac344fa2a0306ba28de4e5f7a183b2754780d5c3e865ead20f6e20e2979b076e49e13ca83bbd11eb51ddf03587839d648b693
6
+ metadata.gz: ebb75489bb52a02c4c6690476bdaea6a45fe750ed3b16dfee051e511086a50e5c7921e9b0546d340342f3b04febfda9a99869104dc866305177c268aa532c295
7
+ data.tar.gz: 20b6014bc62d0138e454dec1ca2f71679058cb199104246afb2670958b615145112f58cd92c21d269efca0cca4f12fa7df28b231922be3cf8ff39802defe366c
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ lib/eccsv/parser.output
data/.projections.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "lib/eccsv/*.rb": {
3
+ "type": "source",
4
+ "alternate": "test/test_{}.rb"
5
+ },
6
+ "test/test_*.rb": {
7
+ "type": "test",
8
+ "alternate": "lib/eccsv/{}.rb"
9
+ }
10
+ }
data/README.md CHANGED
@@ -104,6 +104,40 @@ parser.warnings[0].line #=> 2
104
104
  parser.warnings[0].col #=> 4
105
105
  ```
106
106
 
107
+ ## Corrections
108
+
109
+ It is possible to provide corrections to errors by inserting and deleting.
110
+
111
+ ### Examples
112
+
113
+ #### Inserting
114
+
115
+ ```ruby
116
+ require 'eccsv'
117
+
118
+ data = <<EOF
119
+ foo",bar
120
+ EOF
121
+
122
+ parser = ECCSV::Parser.new
123
+ parser.add_correction(1, 1, :insert, '"')
124
+ parser.parse(data) #=> [["foo", "bar"]]]
125
+ ```
126
+
127
+ #### Deleting
128
+
129
+ ```ruby
130
+ require 'eccsv'
131
+
132
+ data = <<EOF
133
+ foo",bar
134
+ EOF
135
+
136
+ parser = ECCSV::Parser.new
137
+ parser.add_correction(1, 4, :delete, 1)
138
+ parser.parse(data) #=> [["foo", "bar"]]]
139
+ ```
140
+
107
141
  ## Contributing
108
142
 
109
143
  1. Fork it
data/lib/eccsv.rb CHANGED
@@ -1,5 +1,10 @@
1
+ require 'stringio'
1
2
  require 'eccsv/version'
2
3
  require 'eccsv/errors'
4
+ require 'eccsv/correction'
5
+ require 'eccsv/stream'
6
+ require 'eccsv/node'
7
+ require 'eccsv/lexer'
3
8
  require 'eccsv/parser'
4
9
 
5
10
  module ECCSV
@@ -0,0 +1,42 @@
1
+ module ECCSV
2
+ class Correction
3
+ attr_reader :line, :col
4
+
5
+ def initialize(line, col, *args)
6
+ @line = line
7
+ @col = col
8
+ end
9
+
10
+ def apply(stream)
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+
15
+ class InsertCorrection < Correction
16
+ attr_reader :string
17
+
18
+ def initialize(line, col, string)
19
+ super
20
+ @string = string
21
+ end
22
+
23
+ def length
24
+ @string.length
25
+ end
26
+
27
+ def apply(stream)
28
+ stream.insert(@string, @line, @col)
29
+ end
30
+ end
31
+
32
+ class DeleteCorrection < Correction
33
+ def initialize(line, col, amount)
34
+ super
35
+ @amount = amount
36
+ end
37
+
38
+ def apply(stream)
39
+ stream.delete(@amount, @line, @col)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module ECCSV
2
+ class Lexer
3
+ def initialize(stream)
4
+ @stream = stream
5
+ end
6
+
7
+ def next_token
8
+ unless @stream.eof?
9
+ token = nil
10
+ match = ""
11
+ line = @stream.line
12
+ col = @stream.col
13
+
14
+ until @stream.eof?
15
+ c = @stream.peek
16
+ if token.nil?
17
+ match << c
18
+ @stream.next
19
+ if c == ","
20
+ token = :COMMA
21
+ break
22
+ elsif c == '"'
23
+ token = :QUOTE
24
+ break
25
+ elsif c == "\n"
26
+ token = :NEWLINE
27
+ break
28
+ else
29
+ token = :TEXT
30
+ end
31
+ elsif c != "," && c != '"' && c != "\n"
32
+ match << c
33
+ @stream.next
34
+ else
35
+ break
36
+ end
37
+ end
38
+
39
+ if match.length == 0
40
+ raise "Stream error"
41
+ end
42
+ node = Node.new(match, token, line, col)
43
+ [token, node]
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/eccsv/node.rb ADDED
@@ -0,0 +1,101 @@
1
+ module ECCSV
2
+ class Node
3
+ attr_reader :value, :token, :line, :col
4
+
5
+ def initialize(value = "", token = nil, line = nil, col = nil)
6
+ @value = value
7
+ @token = token
8
+ @line = line
9
+ @col = col
10
+ end
11
+ end
12
+
13
+ class ParentNode < Node
14
+ def initialize(children = [], line = nil, col = nil)
15
+ last = children.last
16
+ if last && last.is_a?(Node)
17
+ line = last.line
18
+ col = last.col
19
+ end
20
+ super(nil, nil, line, col)
21
+ @children = children
22
+ end
23
+ end
24
+
25
+ class QuotedTextNode < ParentNode
26
+ def value
27
+ @value ||= @children.collect(&:value).join
28
+ end
29
+ end
30
+
31
+ class FieldNode < ParentNode
32
+ def value
33
+ @value ||=
34
+ if @children[0].token == :TEXT
35
+ @children[0].value
36
+ else
37
+ # quoted text
38
+ @children[1].value
39
+ end
40
+ end
41
+ end
42
+
43
+ class DelimFieldNode < ParentNode
44
+ def value
45
+ @value ||= @children[0].value
46
+ end
47
+ end
48
+
49
+ class DelimFieldsNode < ParentNode
50
+ def value
51
+ @value ||=
52
+ if @children.empty?
53
+ []
54
+ else
55
+ @children[0].value + [@children[1].value]
56
+ end
57
+ end
58
+ end
59
+
60
+ class RecordNode < ParentNode
61
+ def value
62
+ # TODO: 'consume' children to produce value to reduce memory footprint
63
+ @value ||= @children[0].value + [@children[1].value]
64
+ end
65
+ end
66
+
67
+ class DelimRecordNode < ParentNode
68
+ def value
69
+ @value ||= @children.length == 1 ? [] : @children[0].value
70
+ end
71
+ end
72
+
73
+ class DelimRecordsNode < ParentNode
74
+ def value
75
+ if @value.nil?
76
+ if @children.empty?
77
+ @value = []
78
+ else
79
+ @value = @children[0].value
80
+ val = @children[1].value
81
+ if !val.empty?
82
+ @value += [val]
83
+ end
84
+ end
85
+ end
86
+ @value
87
+ end
88
+ end
89
+
90
+ class RootNode < ParentNode
91
+ def value
92
+ if @value.nil?
93
+ @value = @children[0].value
94
+ if @children[1]
95
+ @value += [@children[1].value]
96
+ end
97
+ end
98
+ @value
99
+ end
100
+ end
101
+ end
data/lib/eccsv/parser.rb CHANGED
@@ -12,141 +12,46 @@ module ECCSV
12
12
  class Parser < Racc::Parser
13
13
 
14
14
  module_eval(<<'...end parser.y/module_eval...', 'parser.y', 36)
15
- class Node
16
- attr_reader :value, :token, :line, :col
17
-
18
- def initialize(value = "", token = nil, line = nil, col = nil)
19
- @value = value
20
- @token = token
21
- @line = line
22
- @col = col
23
- end
24
- end
25
-
26
- class ParentNode < Node
27
- def initialize(children = [], line = nil, col = nil)
28
- last = children.last
29
- if last && last.is_a?(Node)
30
- line = last.line
31
- col = last.col
15
+ attr_reader :error, :warnings
16
+
17
+ def initialize
18
+ @warnings = []
19
+ @corrections = []
20
+ end
21
+
22
+ def add_correction(line, col, type, *args)
23
+ klass =
24
+ case type
25
+ when :insert
26
+ InsertCorrection
27
+ when :delete
28
+ DeleteCorrection
29
+ else
30
+ raise "invalid correction type: #{type.inspect}"
32
31
  end
33
- super(nil, nil, line, col)
34
- @children = children
35
- end
36
- end
37
-
38
- class QuotedTextNode < ParentNode
39
- def value
40
- @value ||= @children.collect(&:value).join
41
- end
42
- end
43
-
44
- class FieldNode < ParentNode
45
- def value
46
- @value ||=
47
- if @children[0].token == :TEXT
48
- @children[0].value
49
- else
50
- # quoted text
51
- @children[1].value
52
- end
53
- end
54
- end
55
-
56
- class DelimFieldNode < ParentNode
57
- def value
58
- @value ||= @children[0].value
59
- end
60
- end
61
-
62
- class DelimFieldsNode < ParentNode
63
- def value
64
- @value ||=
65
- if @children.empty?
66
- []
67
- else
68
- @children[0].value + [@children[1].value]
69
- end
70
- end
71
- end
72
-
73
- class RecordNode < ParentNode
74
- def value
75
- # TODO: 'consume' children to produce value to reduce memory footprint
76
- @value ||= @children[0].value + [@children[1].value]
77
- end
32
+ correction = klass.new(line, col, *args)
33
+ @corrections << correction
78
34
  end
79
35
 
80
- class DelimRecordNode < ParentNode
81
- def value
82
- @value ||= @children.length == 1 ? [] : @children[0].value
83
- end
84
- end
85
-
86
- class DelimRecordsNode < ParentNode
87
- def value
88
- if @value.nil?
89
- if @children.empty?
90
- @value = []
91
- else
92
- @value = @children[0].value
93
- val = @children[1].value
94
- if !val.empty?
95
- @value += [val]
96
- end
97
- end
98
- end
99
- @value
36
+ def parse(str)
37
+ @stream = Stream.new(StringIO.new(str))
38
+ @corrections.each do |correction|
39
+ correction.apply(@stream)
100
40
  end
41
+ @lexer = Lexer.new(@stream)
42
+ do_parse
101
43
  end
102
44
 
103
- class RootNode < ParentNode
104
- def value
105
- if @value.nil?
106
- @value = @children[0].value
107
- if @children[1]
108
- @value += [@children[1].value]
109
- end
110
- end
111
- @value
112
- end
45
+ def curr_line
46
+ @stream.line
113
47
  end
114
48
 
115
- attr_reader :error
116
-
117
- def parse(str)
118
- @scanner = StringScanner.new(str)
119
- @line = 1
120
- @col = 1
121
- do_parse
49
+ def curr_col
50
+ @stream.col
122
51
  end
123
52
 
124
53
  def next_token
125
- until @scanner.empty?
126
- next_line = @line
127
- next_col = @col
128
- case
129
- when match = @scanner.scan(/,/)
130
- token = :COMMA
131
- when match = @scanner.scan(/"/)
132
- token = :QUOTE
133
- when match = @scanner.scan(/\n/)
134
- token = :NEWLINE
135
- next_line += 1
136
- next_col = 0
137
- when match = @scanner.scan(/[^,\n"]+/)
138
- token = :TEXT
139
- else
140
- raise "can't recognize <#{@scanner.peek(5)}>"
141
- end
142
- next_col += match.length
143
-
144
- value = node(match, token)
145
- @line = next_line
146
- @col = next_col
147
-
148
- return [token, value]
149
- end
54
+ @lexer.next_token
150
55
  end
151
56
 
152
57
  def warnings
@@ -155,27 +60,23 @@ module_eval(<<'...end parser.y/module_eval...', 'parser.y', 36)
155
60
 
156
61
  private
157
62
 
158
- def node(value = "", token = nil, line = @line, col = @col)
159
- Node.new(value, token, line, col)
160
- end
161
-
162
- def quoted_text(children = [], line = @line, col = @col)
63
+ def quoted_text(children = [], line = curr_line, col = curr_col)
163
64
  QuotedTextNode.new(children, line, col)
164
65
  end
165
66
 
166
- def field(children = [], line = @line, col = @col)
67
+ def field(children = [], line = curr_line, col = curr_col)
167
68
  FieldNode.new(children, line, col)
168
69
  end
169
70
 
170
- def delim_field(children = [], line = @line, col = @col)
71
+ def delim_field(children = [], line = curr_line, col = curr_col)
171
72
  DelimFieldNode.new(children, line, col)
172
73
  end
173
74
 
174
- def delim_fields(children = [], line = @line, col = @col)
75
+ def delim_fields(children = [], line = curr_line, col = curr_col)
175
76
  DelimFieldsNode.new(children, line, col)
176
77
  end
177
78
 
178
- def record(children = [], line = @line, col = @col)
79
+ def record(children = [], line = curr_line, col = curr_col)
179
80
  record = RecordNode.new(children, line, col)
180
81
  value = record.value
181
82
  if defined? @num_fields
@@ -196,15 +97,15 @@ module_eval(<<'...end parser.y/module_eval...', 'parser.y', 36)
196
97
  record
197
98
  end
198
99
 
199
- def delim_record(children = [], line = @line, col = @col)
100
+ def delim_record(children = [], line = curr_line, col = curr_col)
200
101
  DelimRecordNode.new(children, line, col)
201
102
  end
202
103
 
203
- def delim_records(children = [], line = @line, col = @col)
104
+ def delim_records(children = [], line = curr_line, col = curr_col)
204
105
  DelimRecordsNode.new(children, line, col)
205
106
  end
206
107
 
207
- def root(children = [], line = @line, col = @col)
108
+ def root(children = [], line = curr_line, col = curr_col)
208
109
  RootNode.new(children, line, col)
209
110
  end
210
111
 
@@ -338,7 +239,7 @@ Racc_token_to_s_table = [
338
239
  "delim_field",
339
240
  "quoted_text" ]
340
241
 
341
- Racc_debug_parser = true
242
+ Racc_debug_parser = false
342
243
 
343
244
  ##### State transition tables end #####
344
245
 
data/lib/eccsv/parser.y CHANGED
@@ -33,141 +33,46 @@ require 'strscan'
33
33
 
34
34
  module ECCSV
35
35
  ---- inner
36
- class Node
37
- attr_reader :value, :token, :line, :col
38
-
39
- def initialize(value = "", token = nil, line = nil, col = nil)
40
- @value = value
41
- @token = token
42
- @line = line
43
- @col = col
44
- end
45
- end
46
-
47
- class ParentNode < Node
48
- def initialize(children = [], line = nil, col = nil)
49
- last = children.last
50
- if last && last.is_a?(Node)
51
- line = last.line
52
- col = last.col
36
+ attr_reader :error, :warnings
37
+
38
+ def initialize
39
+ @warnings = []
40
+ @corrections = []
41
+ end
42
+
43
+ def add_correction(line, col, type, *args)
44
+ klass =
45
+ case type
46
+ when :insert
47
+ InsertCorrection
48
+ when :delete
49
+ DeleteCorrection
50
+ else
51
+ raise "invalid correction type: #{type.inspect}"
53
52
  end
54
- super(nil, nil, line, col)
55
- @children = children
56
- end
57
- end
58
-
59
- class QuotedTextNode < ParentNode
60
- def value
61
- @value ||= @children.collect(&:value).join
62
- end
63
- end
64
-
65
- class FieldNode < ParentNode
66
- def value
67
- @value ||=
68
- if @children[0].token == :TEXT
69
- @children[0].value
70
- else
71
- # quoted text
72
- @children[1].value
73
- end
74
- end
75
- end
76
-
77
- class DelimFieldNode < ParentNode
78
- def value
79
- @value ||= @children[0].value
80
- end
81
- end
82
-
83
- class DelimFieldsNode < ParentNode
84
- def value
85
- @value ||=
86
- if @children.empty?
87
- []
88
- else
89
- @children[0].value + [@children[1].value]
90
- end
91
- end
92
- end
93
-
94
- class RecordNode < ParentNode
95
- def value
96
- # TODO: 'consume' children to produce value to reduce memory footprint
97
- @value ||= @children[0].value + [@children[1].value]
98
- end
53
+ correction = klass.new(line, col, *args)
54
+ @corrections << correction
99
55
  end
100
56
 
101
- class DelimRecordNode < ParentNode
102
- def value
103
- @value ||= @children.length == 1 ? [] : @children[0].value
57
+ def parse(str)
58
+ @stream = Stream.new(StringIO.new(str))
59
+ @corrections.each do |correction|
60
+ correction.apply(@stream)
104
61
  end
62
+ @lexer = Lexer.new(@stream)
63
+ do_parse
105
64
  end
106
65
 
107
- class DelimRecordsNode < ParentNode
108
- def value
109
- if @value.nil?
110
- if @children.empty?
111
- @value = []
112
- else
113
- @value = @children[0].value
114
- val = @children[1].value
115
- if !val.empty?
116
- @value += [val]
117
- end
118
- end
119
- end
120
- @value
121
- end
66
+ def curr_line
67
+ @stream.line
122
68
  end
123
69
 
124
- class RootNode < ParentNode
125
- def value
126
- if @value.nil?
127
- @value = @children[0].value
128
- if @children[1]
129
- @value += [@children[1].value]
130
- end
131
- end
132
- @value
133
- end
134
- end
135
-
136
- attr_reader :error
137
-
138
- def parse(str)
139
- @scanner = StringScanner.new(str)
140
- @line = 1
141
- @col = 1
142
- do_parse
70
+ def curr_col
71
+ @stream.col
143
72
  end
144
73
 
145
74
  def next_token
146
- until @scanner.empty?
147
- next_line = @line
148
- next_col = @col
149
- case
150
- when match = @scanner.scan(/,/)
151
- token = :COMMA
152
- when match = @scanner.scan(/"/)
153
- token = :QUOTE
154
- when match = @scanner.scan(/\n/)
155
- token = :NEWLINE
156
- next_line += 1
157
- next_col = 0
158
- when match = @scanner.scan(/[^,\n"]+/)
159
- token = :TEXT
160
- else
161
- raise "can't recognize <#{@scanner.peek(5)}>"
162
- end
163
- next_col += match.length
164
-
165
- value = node(match, token)
166
- @line = next_line
167
- @col = next_col
168
-
169
- return [token, value]
170
- end
75
+ @lexer.next_token
171
76
  end
172
77
 
173
78
  def warnings
@@ -176,27 +81,23 @@ module ECCSV
176
81
 
177
82
  private
178
83
 
179
- def node(value = "", token = nil, line = @line, col = @col)
180
- Node.new(value, token, line, col)
181
- end
182
-
183
- def quoted_text(children = [], line = @line, col = @col)
84
+ def quoted_text(children = [], line = curr_line, col = curr_col)
184
85
  QuotedTextNode.new(children, line, col)
185
86
  end
186
87
 
187
- def field(children = [], line = @line, col = @col)
88
+ def field(children = [], line = curr_line, col = curr_col)
188
89
  FieldNode.new(children, line, col)
189
90
  end
190
91
 
191
- def delim_field(children = [], line = @line, col = @col)
92
+ def delim_field(children = [], line = curr_line, col = curr_col)
192
93
  DelimFieldNode.new(children, line, col)
193
94
  end
194
95
 
195
- def delim_fields(children = [], line = @line, col = @col)
96
+ def delim_fields(children = [], line = curr_line, col = curr_col)
196
97
  DelimFieldsNode.new(children, line, col)
197
98
  end
198
99
 
199
- def record(children = [], line = @line, col = @col)
100
+ def record(children = [], line = curr_line, col = curr_col)
200
101
  record = RecordNode.new(children, line, col)
201
102
  value = record.value
202
103
  if defined? @num_fields
@@ -217,15 +118,15 @@ module ECCSV
217
118
  record
218
119
  end
219
120
 
220
- def delim_record(children = [], line = @line, col = @col)
121
+ def delim_record(children = [], line = curr_line, col = curr_col)
221
122
  DelimRecordNode.new(children, line, col)
222
123
  end
223
124
 
224
- def delim_records(children = [], line = @line, col = @col)
125
+ def delim_records(children = [], line = curr_line, col = curr_col)
225
126
  DelimRecordsNode.new(children, line, col)
226
127
  end
227
128
 
228
- def root(children = [], line = @line, col = @col)
129
+ def root(children = [], line = curr_line, col = curr_col)
229
130
  RootNode.new(children, line, col)
230
131
  end
231
132
 
@@ -0,0 +1,77 @@
1
+ module ECCSV
2
+ class Stream
3
+ attr_reader :line, :col, :pos
4
+
5
+ def initialize(io)
6
+ @io = io
7
+ @line = 1
8
+ @col = 1
9
+ @pos = 0
10
+ @inserts = Hash.new { |h, k| h[k] = {} }
11
+ @deletions = Hash.new { |h, k| h[k] = {} }
12
+ end
13
+
14
+ def peek
15
+ unless defined? @buf
16
+ @buf = getc
17
+ end
18
+ @buf
19
+ end
20
+
21
+ def next
22
+ if defined? @buf
23
+ val = @buf
24
+ remove_instance_variable(:@buf)
25
+ else
26
+ val = getc
27
+ end
28
+
29
+ if val
30
+ if val == "\n"
31
+ @line += 1
32
+ @col = 1
33
+ else
34
+ @col += 1
35
+ end
36
+ @pos += val.bytesize
37
+ end
38
+ val
39
+ end
40
+
41
+ def eof?
42
+ peek.nil?
43
+ end
44
+
45
+ def insert(str, line, col)
46
+ i = 0
47
+ str.each_char do |c|
48
+ @inserts[line][col+i] = c
49
+ if c == "\n"
50
+ line += 1
51
+ col = 1
52
+ i = 0
53
+ else
54
+ i += 1
55
+ end
56
+ end
57
+ end
58
+
59
+ def delete(len, line, col)
60
+ @deletions[line][col] = len
61
+ end
62
+
63
+ private
64
+
65
+ def getc
66
+ if @deletions.has_key?(@line) && @deletions[@line].has_key?(@col)
67
+ @io.seek(@deletions[@line][@col], IO::SEEK_CUR)
68
+ end
69
+
70
+ if @inserts.has_key?(@line) && @inserts[@line].has_key?(@col)
71
+ @inserts[@line][@col]
72
+ else
73
+ @io.getc
74
+ end
75
+ end
76
+ end
77
+ end
data/lib/eccsv/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ECCSV
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/test.rb ADDED
@@ -0,0 +1,5 @@
1
+ require __dir__ + '/lib/eccsv'
2
+ require 'pp'
3
+
4
+ parser = ECCSV::Parser.new
5
+ pp parser.parse(%{foo,bar\nbaz,qux})
data/test/test_parser.rb CHANGED
@@ -227,4 +227,18 @@ class TestParser < Test::Unit::TestCase
227
227
  assert error
228
228
  end
229
229
  =end
230
+
231
+ test "single insertion correction" do
232
+ parser = ECCSV::Parser.new
233
+ parser.add_correction(2, 5, :insert, '"')
234
+ result = parser.parse(%{foo,bar\n"foo})
235
+ assert_equal [['foo', 'bar'], ['foo']], result
236
+ end
237
+
238
+ test "single deletion correction" do
239
+ parser = ECCSV::Parser.new
240
+ parser.add_correction(1, 1, :delete, 1)
241
+ result = parser.parse(%{"foo,bar})
242
+ assert_equal [['foo', 'bar']], result
243
+ end
230
244
  end
@@ -0,0 +1,147 @@
1
+ require 'helper'
2
+
3
+ class TestStream < Test::Unit::TestCase
4
+ test "#peek returns 1 character without advancing cursor" do
5
+ io = StringIO.new("foo")
6
+ stream = ECCSV::Stream.new(io)
7
+ assert_equal "f", stream.peek
8
+ assert_equal "f", stream.peek
9
+ assert_equal "f", stream.peek
10
+ end
11
+
12
+ test "#next advances cursor after peek" do
13
+ io = StringIO.new("foo")
14
+ stream = ECCSV::Stream.new(io)
15
+ assert_equal "f", stream.peek
16
+ stream.next
17
+ assert_equal "o", stream.peek
18
+ stream.next
19
+ assert_equal "o", stream.peek
20
+ end
21
+
22
+ test "#next advances cursor without peek" do
23
+ io = StringIO.new("foo")
24
+ stream = ECCSV::Stream.new(io)
25
+ stream.next
26
+ assert_equal "o", stream.peek
27
+ end
28
+
29
+ test "#next increases column" do
30
+ io = StringIO.new("foo")
31
+ stream = ECCSV::Stream.new(io)
32
+ assert_equal 1, stream.col
33
+ stream.next
34
+ assert_equal 2, stream.col
35
+ stream.next
36
+ assert_equal 3, stream.col
37
+ end
38
+
39
+ test "#next increases line and resets column after reaching newline" do
40
+ io = StringIO.new("f\noo")
41
+ stream = ECCSV::Stream.new(io)
42
+ assert_equal 1, stream.line
43
+ assert_equal 1, stream.col
44
+ stream.next
45
+ assert_equal 1, stream.line
46
+ assert_equal 2, stream.col
47
+ stream.next
48
+ assert_equal 2, stream.line
49
+ assert_equal 1, stream.col
50
+ end
51
+
52
+ test "#next increases pos for single-byte character" do
53
+ io = StringIO.new("foo")
54
+ stream = ECCSV::Stream.new(io)
55
+ assert_equal 0, stream.pos
56
+ stream.next
57
+ assert_equal 1, stream.pos
58
+ end
59
+
60
+ test "#next increases pos for multi-byte character" do
61
+ io = StringIO.new("♫")
62
+ stream = ECCSV::Stream.new(io)
63
+ assert_equal 0, stream.pos
64
+ stream.next
65
+ assert_equal 3, stream.pos
66
+ end
67
+
68
+ test "#eof? returns true if at end" do
69
+ io = StringIO.new("foo")
70
+ stream = ECCSV::Stream.new(io)
71
+ stream.peek
72
+ stream.next
73
+ stream.peek
74
+ stream.next
75
+ stream.peek
76
+ stream.next
77
+ assert stream.eof?
78
+ end
79
+
80
+ test "#insert at line and col" do
81
+ io = StringIO.new("foo")
82
+ stream = ECCSV::Stream.new(io)
83
+ stream.insert("x", 1, 2)
84
+ assert_equal "f", stream.next
85
+ assert_equal "x", stream.next
86
+ assert_equal "o", stream.next
87
+ assert_equal "o", stream.next
88
+ end
89
+
90
+ test "#insert at end of input" do
91
+ io = StringIO.new("foo")
92
+ stream = ECCSV::Stream.new(io)
93
+ stream.insert("x", 1, 4)
94
+ assert_equal "f", stream.next
95
+ assert_equal "o", stream.next
96
+ assert_equal "o", stream.next
97
+ assert !stream.eof?
98
+ assert_equal "x", stream.next
99
+ end
100
+
101
+ test "#insert multi-character string" do
102
+ io = StringIO.new("foo")
103
+ stream = ECCSV::Stream.new(io)
104
+ stream.insert("bar", 1, 2)
105
+ assert_equal "f", stream.next
106
+ assert_equal "b", stream.next
107
+ assert_equal "a", stream.next
108
+ assert_equal "r", stream.next
109
+ assert_equal "o", stream.next
110
+ assert_equal "o", stream.next
111
+ end
112
+
113
+ test "#insert newline" do
114
+ io = StringIO.new("foo")
115
+ stream = ECCSV::Stream.new(io)
116
+ stream.insert("bar\nbaz", 1, 2)
117
+ assert_equal "f", stream.next
118
+ assert_equal "b", stream.next
119
+ assert_equal "a", stream.next
120
+ assert_equal "r", stream.next
121
+ assert_equal "\n", stream.next
122
+ assert_equal "b", stream.next
123
+ assert_equal "a", stream.next
124
+ assert_equal "z", stream.next
125
+ assert_equal "o", stream.next
126
+ assert_equal "o", stream.next
127
+ end
128
+
129
+ test "#delete 1 character at line and col" do
130
+ io = StringIO.new("foo")
131
+ stream = ECCSV::Stream.new(io)
132
+ stream.delete(1, 1, 2)
133
+ assert_equal "f", stream.next
134
+ assert_equal "o", stream.next
135
+ assert stream.eof?
136
+ end
137
+
138
+ test "#delete 3 characters at line and col" do
139
+ io = StringIO.new("fbaroo")
140
+ stream = ECCSV::Stream.new(io)
141
+ stream.delete(3, 1, 2)
142
+ assert_equal "f", stream.next
143
+ assert_equal "o", stream.next
144
+ assert_equal "o", stream.next
145
+ assert stream.eof?
146
+ end
147
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eccsv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Stephens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-02 00:00:00.000000000 Z
11
+ date: 2014-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -74,18 +74,25 @@ extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
76
  - ".gitignore"
77
+ - ".projections.json"
77
78
  - Gemfile
78
79
  - LICENSE.txt
79
80
  - README.md
80
81
  - Rakefile
81
82
  - eccsv.gemspec
82
83
  - lib/eccsv.rb
84
+ - lib/eccsv/correction.rb
83
85
  - lib/eccsv/errors.rb
86
+ - lib/eccsv/lexer.rb
87
+ - lib/eccsv/node.rb
84
88
  - lib/eccsv/parser.rb
85
89
  - lib/eccsv/parser.y
90
+ - lib/eccsv/stream.rb
86
91
  - lib/eccsv/version.rb
92
+ - test.rb
87
93
  - test/helper.rb
88
94
  - test/test_parser.rb
95
+ - test/test_stream.rb
89
96
  homepage: https://github.com/coupler/eccsv
90
97
  licenses:
91
98
  - MIT
@@ -113,3 +120,5 @@ summary: CSV library with advanced error reporting
113
120
  test_files:
114
121
  - test/helper.rb
115
122
  - test/test_parser.rb
123
+ - test/test_stream.rb
124
+ has_rdoc: