Skip to content

Commit c8f2220

Browse files
committed
Parse progressive input incrementally
- Replace raw input accumulation with row updates - Match DSV header validation - Add progressive data tests
1 parent e4fb965 commit c8f2220

2 files changed

Lines changed: 188 additions & 13 deletions

File tree

‎lib/youplot/command.rb‎

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,25 +112,95 @@ def main(input)
112112
def main_progressive(input)
113113
output_data(input)
114114

115-
# FIXME
116-
# Worked around the problem of not being able to draw
117-
# plots when there is only one header line.
118-
if @raw_data.nil?
119-
@raw_data = String.new
120-
if options[:headers]
121-
@raw_data << input
122-
return
123-
end
124-
end
125-
@raw_data << input
115+
row = parse_progressive_row(input)
116+
return 0 if row.nil?
126117

127-
# FIXME
128-
@data = parse_dsv(@raw_data)
118+
@data = progressive_update_data(row)
119+
return 0 if @data.nil?
129120

130121
plot = create_plot
131122
output_plot_progressive(plot)
132123
end
133124

125+
def parse_progressive_row(input)
126+
line = normalize_input_encoding!(input)
127+
128+
begin
129+
row = CSV.parse_line(line, col_sep: options[:delimiter])
130+
rescue CSV::MalformedCSVError => e
131+
warn 'Failed to parse the text. '
132+
warn 'Please try to set the correct character encoding with --encoding option.'
133+
warn e.backtrace.grep(/youplot/).first
134+
exit 1
135+
rescue ArgumentError => e
136+
warn 'Failed to parse the text. '
137+
warn e.backtrace.grep(/youplot/).first
138+
exit 1
139+
end
140+
141+
return nil if row.nil? || row.empty? || row.all?(&:nil?)
142+
143+
row
144+
end
145+
146+
def progressive_update_data(row)
147+
init_progressive_state
148+
149+
return nil if consume_progressive_header?(row)
150+
151+
append_progressive_row(row)
152+
progressive_data
153+
end
154+
155+
def init_progressive_state
156+
return if @progressive_initialized
157+
158+
@progressive_initialized = true
159+
@progressive_headers = options[:headers] ? [] : nil
160+
@progressive_series = []
161+
@progressive_header_consumed = false
162+
@progressive_row_count = 0
163+
end
164+
165+
def consume_progressive_header?(row)
166+
return false unless options[:headers]
167+
return false if options[:transpose]
168+
return false if @progressive_header_consumed
169+
170+
@progressive_headers = row
171+
@progressive_header_consumed = true
172+
true
173+
end
174+
175+
def append_progressive_row(row)
176+
if options[:headers] && options[:transpose]
177+
@progressive_headers << row[0]
178+
@progressive_series << row[1..-1]
179+
elsif options[:transpose]
180+
@progressive_series << row
181+
else
182+
append_progressive_columns(row)
183+
end
184+
end
185+
186+
def progressive_data
187+
DSV.build_data(@progressive_headers, @progressive_series)
188+
end
189+
190+
def append_progressive_columns(row)
191+
if row.size > @progressive_series.size
192+
(@progressive_series.size...row.size).each do |i|
193+
@progressive_series[i] = Array.new(@progressive_row_count, nil)
194+
end
195+
end
196+
197+
0.upto(@progressive_series.size - 1) do |i|
198+
@progressive_series[i] << row[i]
199+
end
200+
201+
@progressive_row_count += 1
202+
end
203+
134204
def parse_dsv(input)
135205
# If encoding is specified, convert to UTF-8
136206
normalize_input_encoding!(input)

‎test/youplot/progressive_test.rb‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# frozen_string_literal: true
2+
3+
require 'stringio'
4+
require_relative '../test_helper'
5+
6+
class YouPlotProgressiveTest < Test::Unit::TestCase
7+
def build_options(overrides = {})
8+
out = StringIO.new
9+
defaults = YouPlot::Options::DEFAULTS.merge(output: out, pass: false)
10+
options_hash = defaults.merge(overrides)
11+
YouPlot::Options.new(*YouPlot::Options.members.map { |k| options_hash[k] })
12+
end
13+
14+
def build_data_with_progressive(command, lines)
15+
data = nil
16+
lines.each do |line|
17+
row = command.send(:parse_progressive_row, line)
18+
next if row.nil?
19+
20+
data = command.send(:progressive_update_data, row)
21+
end
22+
data
23+
end
24+
25+
def suppress_stderr
26+
stderr = $stderr
27+
$stderr = StringIO.new
28+
yield
29+
ensure
30+
$stderr = stderr
31+
end
32+
33+
test 'progressive incremental parsing without headers matches DSV.parse' do
34+
command = YouPlot::Command.new([])
35+
command.options = build_options(delimiter: "\t", headers: false, transpose: false)
36+
37+
lines = %W[1\t2\n 3\n \n]
38+
actual = build_data_with_progressive(command, lines)
39+
expected = YouPlot::DSV.parse(lines.join, "\t", false, false)
40+
41+
assert_equal expected.headers, actual.headers
42+
assert_equal expected.series, actual.series
43+
assert_nil command.instance_variable_get(:@raw_data)
44+
end
45+
46+
test 'progressive incremental parsing with headers waits for first data row' do
47+
command = YouPlot::Command.new([])
48+
command.options = build_options(delimiter: "\t", headers: true, transpose: false)
49+
50+
header_row = command.send(:parse_progressive_row, "h1\th2\n")
51+
first = command.send(:progressive_update_data, header_row)
52+
assert_nil first
53+
54+
data_row = command.send(:parse_progressive_row, "1\t2\n")
55+
actual = command.send(:progressive_update_data, data_row)
56+
expected = YouPlot::DSV.parse("h1\th2\n1\t2\n", "\t", true, false)
57+
58+
assert_equal expected.headers, actual.headers
59+
assert_equal expected.series, actual.series
60+
end
61+
62+
test 'progressive incremental parsing with headers and transpose matches DSV.parse' do
63+
command = YouPlot::Command.new([])
64+
command.options = build_options(delimiter: "\t", headers: true, transpose: true)
65+
66+
lines = %W[h1\t1\t2\n h2\t3\t4\n]
67+
actual = build_data_with_progressive(command, lines)
68+
expected = YouPlot::DSV.parse(lines.join, "\t", true, true)
69+
70+
assert_equal expected.headers, actual.headers
71+
assert_equal expected.series, actual.series
72+
end
73+
74+
test 'progressive with headers returns nil when headers are fewer than series' do
75+
command = YouPlot::Command.new([])
76+
command.options = build_options(delimiter: "\t", headers: true, transpose: false)
77+
78+
lines = %W[h1\n 1\t2\n]
79+
actual = nil
80+
expected = nil
81+
suppress_stderr do
82+
actual = build_data_with_progressive(command, lines)
83+
expected = YouPlot::DSV.parse(lines.join, "\t", true, false)
84+
end
85+
86+
assert_nil expected
87+
assert_nil actual
88+
end
89+
90+
test 'progressive with headers returns nil when headers are greater than series' do
91+
command = YouPlot::Command.new([])
92+
command.options = build_options(delimiter: "\t", headers: true, transpose: false)
93+
94+
lines = %W[h1\th2\n 1\n]
95+
actual = nil
96+
expected = nil
97+
suppress_stderr do
98+
actual = build_data_with_progressive(command, lines)
99+
expected = YouPlot::DSV.parse(lines.join, "\t", true, false)
100+
end
101+
102+
assert_nil expected
103+
assert_nil actual
104+
end
105+
end

0 commit comments

Comments
 (0)