tb 0.1 → 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.
- data/README +156 -5
- data/bin/tb +2 -1110
- data/lib/tb.rb +4 -2
- data/lib/tb/catreader.rb +131 -0
- data/lib/tb/cmd_cat.rb +65 -0
- data/lib/tb/cmd_consecutive.rb +79 -0
- data/lib/tb/cmd_crop.rb +105 -0
- data/lib/tb/cmd_cross.rb +119 -0
- data/lib/tb/cmd_csv.rb +42 -0
- data/lib/tb/cmd_cut.rb +77 -0
- data/lib/tb/cmd_grep.rb +76 -0
- data/lib/tb/cmd_group.rb +82 -0
- data/lib/tb/cmd_gsub.rb +77 -0
- data/lib/tb/cmd_help.rb +98 -0
- data/lib/tb/cmd_join.rb +81 -0
- data/lib/tb/cmd_json.rb +60 -0
- data/lib/tb/cmd_ls.rb +273 -0
- data/lib/tb/cmd_mheader.rb +77 -0
- data/lib/tb/cmd_newfield.rb +59 -0
- data/lib/tb/cmd_pnm.rb +43 -0
- data/lib/tb/cmd_pp.rb +70 -0
- data/lib/tb/cmd_rename.rb +58 -0
- data/lib/tb/cmd_shape.rb +67 -0
- data/lib/tb/cmd_sort.rb +58 -0
- data/lib/tb/cmd_svn_log.rb +158 -0
- data/lib/tb/cmd_tsv.rb +43 -0
- data/lib/tb/cmd_yaml.rb +47 -0
- data/lib/tb/cmdmain.rb +45 -0
- data/lib/tb/cmdtop.rb +58 -0
- data/lib/tb/cmdutil.rb +327 -0
- data/lib/tb/csv.rb +30 -6
- data/lib/tb/fieldset.rb +39 -41
- data/lib/tb/pager.rb +132 -0
- data/lib/tb/pnm.rb +357 -0
- data/lib/tb/reader.rb +18 -128
- data/lib/tb/record.rb +3 -3
- data/lib/tb/ropen.rb +70 -0
- data/lib/tb/{pathfinder.rb → search.rb} +69 -34
- data/lib/tb/tsv.rb +29 -1
- data/sample/colors.ppm +0 -0
- data/sample/gradation.pgm +0 -0
- data/sample/langs.csv +46 -0
- data/sample/tbplot +293 -0
- data/test-all-cov.rb +65 -0
- data/test-all.rb +5 -0
- data/test/test_basic.rb +99 -2
- data/test/test_catreader.rb +27 -0
- data/test/test_cmd_cat.rb +118 -0
- data/test/test_cmd_consecutive.rb +90 -0
- data/test/test_cmd_crop.rb +101 -0
- data/test/test_cmd_cross.rb +113 -0
- data/test/test_cmd_csv.rb +129 -0
- data/test/test_cmd_cut.rb +100 -0
- data/test/test_cmd_grep.rb +89 -0
- data/test/test_cmd_group.rb +181 -0
- data/test/test_cmd_gsub.rb +103 -0
- data/test/test_cmd_help.rb +190 -0
- data/test/test_cmd_join.rb +197 -0
- data/test/test_cmd_json.rb +75 -0
- data/test/test_cmd_ls.rb +203 -0
- data/test/test_cmd_mheader.rb +86 -0
- data/test/test_cmd_newfield.rb +63 -0
- data/test/test_cmd_pnm.rb +35 -0
- data/test/test_cmd_pp.rb +62 -0
- data/test/test_cmd_rename.rb +91 -0
- data/test/test_cmd_shape.rb +50 -0
- data/test/test_cmd_sort.rb +105 -0
- data/test/test_cmd_tsv.rb +67 -0
- data/test/test_cmd_yaml.rb +55 -0
- data/test/test_cmdtty.rb +154 -0
- data/test/test_cmdutil.rb +43 -0
- data/test/test_csv.rb +10 -0
- data/test/test_fieldset.rb +42 -0
- data/test/test_pager.rb +142 -0
- data/test/test_pnm.rb +374 -0
- data/test/test_reader.rb +147 -0
- data/test/test_record.rb +49 -0
- data/test/test_search.rb +575 -0
- data/test/test_tsv.rb +7 -0
- metadata +108 -5
- data/lib/tb/qtsv.rb +0 -93
data/lib/tb/reader.rb
CHANGED
@@ -24,106 +24,10 @@
|
|
24
24
|
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
25
|
# OF SUCH DAMAGE.
|
26
26
|
|
27
|
-
class Tb
|
28
|
-
def Tb.load_csv(filename, *header_fields, &block)
|
29
|
-
Tb.parse_csv(File.read(filename), *header_fields, &block)
|
30
|
-
end
|
31
|
-
|
32
|
-
def Tb.parse_csv(csv, *header_fields)
|
33
|
-
aa = []
|
34
|
-
csv_stream_input(csv) {|ary|
|
35
|
-
aa << ary
|
36
|
-
}
|
37
|
-
aa = yield aa if block_given?
|
38
|
-
if header_fields.empty?
|
39
|
-
reader = Tb::Reader.new(aa)
|
40
|
-
arys = []
|
41
|
-
reader.each {|ary|
|
42
|
-
arys << ary
|
43
|
-
}
|
44
|
-
header = reader.header
|
45
|
-
else
|
46
|
-
header = header_fields
|
47
|
-
arys = aa
|
48
|
-
end
|
49
|
-
t = Tb.new(header)
|
50
|
-
arys.each {|ary|
|
51
|
-
ary << nil while ary.length < header.length
|
52
|
-
t.insert_values header, ary
|
53
|
-
}
|
54
|
-
t
|
55
|
-
end
|
56
|
-
|
57
|
-
def Tb.load_tsv(filename, *header_fields, &block)
|
58
|
-
Tb.parse_tsv(File.read(filename), *header_fields, &block)
|
59
|
-
end
|
60
|
-
|
61
|
-
def Tb.parse_tsv(tsv, *header_fields)
|
62
|
-
aa = []
|
63
|
-
tsv_stream_input(tsv) {|ary|
|
64
|
-
aa << ary
|
65
|
-
}
|
66
|
-
aa = yield aa if block_given?
|
67
|
-
if header_fields.empty?
|
68
|
-
reader = Tb::Reader.new(aa)
|
69
|
-
arys = []
|
70
|
-
reader.each {|ary|
|
71
|
-
arys << ary
|
72
|
-
}
|
73
|
-
header = reader.header
|
74
|
-
else
|
75
|
-
header = header_fields
|
76
|
-
arys = aa
|
77
|
-
end
|
78
|
-
t = Tb.new(header)
|
79
|
-
arys.each {|ary|
|
80
|
-
ary << nil while ary.length < header.length
|
81
|
-
t.insert_values header, ary
|
82
|
-
}
|
83
|
-
t
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
27
|
class Tb::Reader
|
88
|
-
def self.open(filename, opts={})
|
89
|
-
io = nil
|
90
|
-
case filename
|
91
|
-
when /\.csv\z/
|
92
|
-
io = File.open(filename)
|
93
|
-
rawreader = Tb::CSVReader.new(io)
|
94
|
-
when /\.tsv\z/
|
95
|
-
io = File.open(filename)
|
96
|
-
rawreader = Tb::TSVReader.new(io)
|
97
|
-
when /\Acsv:/
|
98
|
-
io = File.open($')
|
99
|
-
rawreader = Tb::CSVReader.new(io)
|
100
|
-
when /\Atsv:/
|
101
|
-
io = File.open($')
|
102
|
-
rawreader = Tb::TSVReader.new(io)
|
103
|
-
else
|
104
|
-
if filename == '-'
|
105
|
-
rawreader = Tb::CSVReader.new(STDIN)
|
106
|
-
else
|
107
|
-
# guess table format?
|
108
|
-
io = File.open(filename)
|
109
|
-
rawreader = Tb::CSVReader.new(io)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
reader = self.new(rawreader, opts)
|
113
|
-
if block_given?
|
114
|
-
begin
|
115
|
-
yield reader
|
116
|
-
ensure
|
117
|
-
reader.close
|
118
|
-
end
|
119
|
-
else
|
120
|
-
reader
|
121
|
-
end
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
28
|
def initialize(rawreader, opts={})
|
126
29
|
@opt_n = opts[:numeric]
|
30
|
+
@opt_close = opts[:close]
|
127
31
|
@reader = rawreader
|
128
32
|
@fieldset = nil
|
129
33
|
end
|
@@ -146,34 +50,25 @@ class Tb::Reader
|
|
146
50
|
return @fieldset.header
|
147
51
|
end
|
148
52
|
|
53
|
+
def index_from_field_ex(f)
|
54
|
+
self.header
|
55
|
+
@fieldset.index_from_field_ex(f)
|
56
|
+
end
|
57
|
+
|
149
58
|
def index_from_field(f)
|
150
59
|
self.header
|
151
|
-
|
152
|
-
raise "numeric field start from 1: #{f.inspect}" if /\A0+\z/ =~ f
|
153
|
-
raise "numeric field name expected: #{f.inspect}" if /\A(\d+)\z/ !~ f
|
154
|
-
$1.to_i - 1
|
155
|
-
else
|
156
|
-
@fieldset.index_from_field(f)
|
157
|
-
end
|
60
|
+
@fieldset.index_from_field(f)
|
158
61
|
end
|
159
62
|
|
160
63
|
def field_from_index_ex(i)
|
161
64
|
raise ArgumentError, "negative index: #{i}" if i < 0
|
162
65
|
self.header
|
163
|
-
if @opt_n
|
164
|
-
if @fieldset.length <= i
|
165
|
-
@fieldset.add_fields(*(@fieldset.header.length..i).to_a.map {|j| "#{j+1}" })
|
166
|
-
end
|
167
|
-
end
|
168
66
|
@fieldset.field_from_index_ex(i)
|
169
67
|
end
|
170
68
|
|
171
69
|
def field_from_index(i)
|
172
70
|
raise ArgumentError, "negative index: #{i}" if i < 0
|
173
71
|
self.header
|
174
|
-
if @opt_n
|
175
|
-
return "#{i+1}"
|
176
|
-
end
|
177
72
|
@fieldset.field_from_index(i)
|
178
73
|
end
|
179
74
|
|
@@ -191,23 +86,18 @@ class Tb::Reader
|
|
191
86
|
nil
|
192
87
|
end
|
193
88
|
|
194
|
-
def
|
195
|
-
|
89
|
+
def read_all
|
90
|
+
result = []
|
91
|
+
while ary = self.shift
|
92
|
+
result << ary
|
93
|
+
end
|
94
|
+
result
|
196
95
|
end
|
197
96
|
|
198
|
-
def
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
s += "(2)" if /\(\d+\)\z/ !~ s
|
204
|
-
while h[s]
|
205
|
-
s = s.sub(/\((\d+)\)\z/) { n = $1.to_i; "(#{n+1})" }
|
206
|
-
end
|
207
|
-
s
|
208
|
-
end
|
209
|
-
h[s] = true
|
210
|
-
s
|
211
|
-
}
|
97
|
+
def close
|
98
|
+
@reader.close
|
99
|
+
if @opt_close
|
100
|
+
@opt_close.close
|
101
|
+
end
|
212
102
|
end
|
213
103
|
end
|
data/lib/tb/record.rb
CHANGED
@@ -90,7 +90,7 @@ class Tb::Record
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def to_a
|
93
|
-
a =
|
93
|
+
a = []
|
94
94
|
@table.each_field {|f|
|
95
95
|
v = @table.get_cell(@recordid, f)
|
96
96
|
a << [f, v] if !v.nil?
|
@@ -99,7 +99,7 @@ class Tb::Record
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def to_a_with_reserved
|
102
|
-
a =
|
102
|
+
a = []
|
103
103
|
@table.each_field_with_reserved {|f|
|
104
104
|
v = @table.get_cell(@recordid, f)
|
105
105
|
a << [f, v] if !v.nil?
|
@@ -116,7 +116,7 @@ class Tb::Record
|
|
116
116
|
end
|
117
117
|
|
118
118
|
def each_with_reserved
|
119
|
-
@table.
|
119
|
+
@table.each_field_with_reserved {|f|
|
120
120
|
v = @table.get_cell(@recordid, f)
|
121
121
|
yield [f, v] if !v.nil?
|
122
122
|
}
|
data/lib/tb/ropen.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# lib/tb/ropen.rb - Tb::Reader.open
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Tanaka Akira <akr@fsij.org>
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote products
|
14
|
+
# derived from this software without specific prior written permission.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
17
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
18
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
19
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
20
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
21
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
22
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
23
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
|
+
# OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
class Tb::Reader
|
28
|
+
def self.open(filename, opts={})
|
29
|
+
opts = opts.dup
|
30
|
+
case filename
|
31
|
+
when /\Acsv:/
|
32
|
+
filename = $'
|
33
|
+
rawreader_maker = lambda {|io| Tb::CSVReader.new(io) }
|
34
|
+
when /\Atsv:/
|
35
|
+
filename = $'
|
36
|
+
rawreader_maker = lambda {|io| Tb::TSVReader.new(io) }
|
37
|
+
when /\Ap[pgbn]m:/
|
38
|
+
filename = $'
|
39
|
+
rawreader_maker = lambda {|io| Tb.pnm_stream_input(io) }
|
40
|
+
when /\.csv\z/
|
41
|
+
rawreader_maker = lambda {|io| Tb::CSVReader.new(io) }
|
42
|
+
when /\.tsv\z/
|
43
|
+
rawreader_maker = lambda {|io| Tb::TSVReader.new(io) }
|
44
|
+
when /\.p[pgbn]m\z/
|
45
|
+
rawreader_maker = lambda {|io| Tb.pnm_stream_input(io) }
|
46
|
+
else
|
47
|
+
rawreader_maker = lambda {|io| Tb::CSVReader.new(io) }
|
48
|
+
end
|
49
|
+
unless filename.respond_to? :to_str
|
50
|
+
raise ArgumentError, "unexpected filename: #{filename.inspect}"
|
51
|
+
end
|
52
|
+
if filename == '-'
|
53
|
+
rawreader = rawreader_maker.call(STDIN)
|
54
|
+
else
|
55
|
+
io = File.open(filename)
|
56
|
+
opts[:close] = io
|
57
|
+
rawreader = rawreader_maker.call(io)
|
58
|
+
end
|
59
|
+
reader = self.new(rawreader, opts)
|
60
|
+
if block_given?
|
61
|
+
begin
|
62
|
+
yield reader
|
63
|
+
ensure
|
64
|
+
reader.close
|
65
|
+
end
|
66
|
+
else
|
67
|
+
reader
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# lib/tb/
|
1
|
+
# lib/tb/search.rb - pattern matcher for two-dimensional array.
|
2
2
|
#
|
3
3
|
# Copyright (C) 2011 Tanaka Akira <akr@fsij.org>
|
4
4
|
#
|
@@ -24,7 +24,7 @@
|
|
24
24
|
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
25
|
# OF SUCH DAMAGE.
|
26
26
|
|
27
|
-
module Tb::
|
27
|
+
module Tb::Search
|
28
28
|
module_function
|
29
29
|
|
30
30
|
def strary_to_aa(strary)
|
@@ -47,7 +47,7 @@ module Tb::Pathfinder
|
|
47
47
|
def each_match(pat, aa, spos=nil)
|
48
48
|
if spos
|
49
49
|
run {
|
50
|
-
try(pat, aa, Tb::
|
50
|
+
try(pat, aa, Tb::Search::EmptyState.merge(:pos => spos)) {|st2|
|
51
51
|
yield spos, st2.fetch(:pos), st2.reject {|k,v| k == :pos }
|
52
52
|
nil
|
53
53
|
}
|
@@ -57,7 +57,7 @@ module Tb::Pathfinder
|
|
57
57
|
a.each_index {|x|
|
58
58
|
spos = [x,y]
|
59
59
|
run {
|
60
|
-
try(pat, aa, Tb::
|
60
|
+
try(pat, aa, Tb::Search::EmptyState.merge(:pos => spos)) {|st2|
|
61
61
|
yield spos, st2.fetch(:pos), st2.reject {|k,v| k == :pos }
|
62
62
|
nil
|
63
63
|
}
|
@@ -93,14 +93,18 @@ module Tb::Pathfinder
|
|
93
93
|
when nil; lambda { yield st }
|
94
94
|
when String; try_lit(pat, aa, st, &b)
|
95
95
|
when Regexp; try_regexp(pat, aa, st, &b)
|
96
|
-
when :n, :north; try_rmove(
|
97
|
-
when :s, :south; try_rmove(
|
98
|
-
when :e, :east; try_rmove(
|
99
|
-
when :w, :west; try_rmove(
|
96
|
+
when :n, :north; try_rmove(0, -1, aa, st, &b)
|
97
|
+
when :s, :south; try_rmove(0, 1, aa, st, &b)
|
98
|
+
when :e, :east; try_rmove(1, 0, aa, st, &b)
|
99
|
+
when :w, :west; try_rmove(-1, 0, aa, st, &b)
|
100
|
+
when :ne, :north; try_rmove(1, -1, aa, st, &b)
|
101
|
+
when :nw, :south; try_rmove(-1, -1, aa, st, &b)
|
102
|
+
when :se, :east; try_rmove(1, 1, aa, st, &b)
|
103
|
+
when :sw, :west; try_rmove(-1, 1, aa, st, &b)
|
100
104
|
when :debug_print_state; p st; yield st
|
101
105
|
when Array
|
102
106
|
case pat[0]
|
103
|
-
when :rmove; _,
|
107
|
+
when :rmove; _, dx, dy = pat; try_rmove(dx, dy, aa, st, &b)
|
104
108
|
when :lit; _, val = pat; try_lit(val, aa, st, &b)
|
105
109
|
when :regexp; _, re = pat; try_regexp(re, aa, st, &b)
|
106
110
|
when :cat; _, *ps = pat; try_cat(ps, aa, st, &b)
|
@@ -123,24 +127,31 @@ module Tb::Pathfinder
|
|
123
127
|
when :pop_pos; _, n = pat; try_pop_pos(n, aa, st, &b)
|
124
128
|
when :update; _, pr = pat; try_update(pr, aa, st, &b)
|
125
129
|
when :assert; _, pr = pat; try_assert(pr, aa, st, &b)
|
126
|
-
else raise "unexpected: #{pat.inspect}"
|
130
|
+
else raise ArgumentError, "unexpected: #{pat.inspect}"
|
127
131
|
end
|
128
132
|
else
|
129
|
-
raise
|
133
|
+
raise ArgumentError, "unexpected pattern: #{pat.inspect}"
|
130
134
|
end
|
131
135
|
end
|
132
136
|
|
133
|
-
def try_rmove(
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
137
|
+
def try_rmove(dx, dy, aa, st)
|
138
|
+
# Basically, try_rmove restricts movement inside of _aa_ to avoid
|
139
|
+
# unintentional infinite loop such as [:rep, :e].
|
140
|
+
#
|
141
|
+
# However try_rmove permits movement to/from outside of _aa_.
|
142
|
+
# It don't permit between two position outside of _aa_, though.
|
143
|
+
# This make match [:rep, "a", :e] to %w[a a a] with three "a"s.
|
144
|
+
# If try_rmove don't permit movement to outside of _aa_,
|
145
|
+
# the last movement is forbidden.
|
146
|
+
#
|
147
|
+
x1, y1 = st.fetch(:pos)
|
148
|
+
x2 = x1 + dx
|
149
|
+
y2 = y1 + dy
|
150
|
+
if (0 <= y1 && y1 < aa.length &&
|
151
|
+
0 <= x1 && x1 < aa[y1].length) ||
|
152
|
+
(0 <= y2 && y2 < aa.length &&
|
153
|
+
0 <= x2 && x2 < aa[y2].length)
|
154
|
+
lambda { yield st.merge(:pos => [x2,y2]) }
|
144
155
|
else
|
145
156
|
nil
|
146
157
|
end
|
@@ -406,7 +417,7 @@ module Tb::Pathfinder
|
|
406
417
|
end
|
407
418
|
end
|
408
419
|
|
409
|
-
module Tb::
|
420
|
+
module Tb::Search::EmptyState
|
410
421
|
module_function
|
411
422
|
|
412
423
|
def empty?
|
@@ -416,13 +427,18 @@ module Tb::Pathfinder::EmptyState
|
|
416
427
|
def each
|
417
428
|
end
|
418
429
|
|
430
|
+
def to_h
|
431
|
+
{}
|
432
|
+
end
|
433
|
+
|
419
434
|
def fetch(k, *rest)
|
420
435
|
if block_given?
|
421
436
|
yield k
|
422
437
|
elsif !rest.empty?
|
423
438
|
return rest[0]
|
424
439
|
else
|
425
|
-
|
440
|
+
exc = defined?(KeyError) ? KeyError : IndexError # 1.9 v.s. 1.8
|
441
|
+
raise exc, "key not found: #{k}"
|
426
442
|
end
|
427
443
|
end
|
428
444
|
|
@@ -441,7 +457,7 @@ module Tb::Pathfinder::EmptyState
|
|
441
457
|
def merge(h)
|
442
458
|
pairs = self
|
443
459
|
h.reverse_each {|k, v|
|
444
|
-
pairs = Tb::
|
460
|
+
pairs = Tb::Search::State.new(k, v, pairs)
|
445
461
|
}
|
446
462
|
pairs
|
447
463
|
end
|
@@ -451,11 +467,15 @@ module Tb::Pathfinder::EmptyState
|
|
451
467
|
end
|
452
468
|
|
453
469
|
def inspect
|
454
|
-
"\#<Tb::
|
470
|
+
"\#<Tb::Search::EmptyState>"
|
455
471
|
end
|
456
472
|
end
|
457
473
|
|
458
|
-
class Tb::
|
474
|
+
class Tb::Search::State
|
475
|
+
def self.make(hash)
|
476
|
+
Tb::Search::EmptyState.merge(hash)
|
477
|
+
end
|
478
|
+
|
459
479
|
def initialize(key, val, tail=nil)
|
460
480
|
@key = key
|
461
481
|
@val = val
|
@@ -476,6 +496,21 @@ class Tb::Pathfinder::State
|
|
476
496
|
nil
|
477
497
|
end
|
478
498
|
|
499
|
+
def to_h
|
500
|
+
res = {}
|
501
|
+
each {|k, v|
|
502
|
+
if !res.include? k
|
503
|
+
res[k] = v
|
504
|
+
end
|
505
|
+
}
|
506
|
+
res
|
507
|
+
end
|
508
|
+
|
509
|
+
def ==(other)
|
510
|
+
other.kind_of?(Tb::Search::State) &&
|
511
|
+
self.to_h == other.to_h
|
512
|
+
end
|
513
|
+
|
479
514
|
def fetch(k, *rest)
|
480
515
|
pairs = self
|
481
516
|
while !pairs.empty?
|
@@ -487,7 +522,8 @@ class Tb::Pathfinder::State
|
|
487
522
|
elsif !rest.empty?
|
488
523
|
return rest[0]
|
489
524
|
else
|
490
|
-
|
525
|
+
exc = defined?(KeyError) ? KeyError : IndexError # 1.9 v.s. 1.8
|
526
|
+
raise exc, "key not found: #{k}"
|
491
527
|
end
|
492
528
|
end
|
493
529
|
|
@@ -529,10 +565,10 @@ class Tb::Pathfinder::State
|
|
529
565
|
end
|
530
566
|
(needs_copy-1).downto(0) {|i|
|
531
567
|
pairs = ary[i]
|
532
|
-
result = Tb::
|
568
|
+
result = Tb::Search::State.new(pairs.key, pairs.val, result)
|
533
569
|
}
|
534
570
|
h.reverse_each {|k, v|
|
535
|
-
result = Tb::
|
571
|
+
result = Tb::Search::State.new(k, v, result)
|
536
572
|
}
|
537
573
|
result
|
538
574
|
end
|
@@ -546,15 +582,14 @@ class Tb::Pathfinder::State
|
|
546
582
|
end
|
547
583
|
pairs = pairs.tail
|
548
584
|
end
|
549
|
-
result = Tb::
|
550
|
-
ary.reverse_each {|
|
551
|
-
result = Tb::
|
585
|
+
result = Tb::Search::EmptyState
|
586
|
+
ary.reverse_each {|pairs2|
|
587
|
+
result = Tb::Search::State.new(pairs2.key, pairs2.val, result)
|
552
588
|
}
|
553
589
|
result
|
554
590
|
end
|
555
591
|
|
556
592
|
def inspect
|
557
|
-
h = {}
|
558
593
|
pairs = self
|
559
594
|
str = ''
|
560
595
|
while !pairs.empty?
|