tb 0.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +2 -1
- data/lib/tb.rb +7 -3
- data/lib/tb/basic.rb +1 -1
- data/lib/tb/cmd_cat.rb +1 -3
- data/lib/tb/cmd_consecutive.rb +4 -6
- data/lib/tb/cmd_crop.rb +5 -7
- data/lib/tb/cmd_cross.rb +51 -49
- data/lib/tb/cmd_cut.rb +2 -6
- data/lib/tb/cmd_git_log.rb +20 -11
- data/lib/tb/cmd_grep.rb +1 -3
- data/lib/tb/cmd_group.rb +18 -44
- data/lib/tb/cmd_gsub.rb +2 -4
- data/lib/tb/cmd_join.rb +1 -3
- data/lib/tb/cmd_ls.rb +8 -15
- data/lib/tb/cmd_mheader.rb +3 -4
- data/lib/tb/cmd_nest.rb +4 -9
- data/lib/tb/cmd_newfield.rb +1 -3
- data/lib/tb/cmd_rename.rb +2 -4
- data/lib/tb/cmd_shape.rb +2 -3
- data/lib/tb/cmd_sort.rb +3 -5
- data/lib/tb/cmd_svn_log.rb +3 -5
- data/lib/tb/cmd_tar_tvf.rb +2 -4
- data/lib/tb/cmd_to_csv.rb +1 -1
- data/lib/tb/cmd_unnest.rb +1 -3
- data/lib/tb/cmdutil.rb +57 -135
- data/lib/tb/csv.rb +11 -54
- data/lib/tb/customcmp.rb +41 -0
- data/lib/tb/customeq.rb +41 -0
- data/lib/tb/enumerable.rb +225 -435
- data/lib/tb/enumerator.rb +22 -14
- data/lib/tb/ex_enumerable.rb +659 -0
- data/lib/tb/ex_enumerator.rb +102 -0
- data/lib/tb/fileenumerator.rb +2 -2
- data/lib/tb/func.rb +141 -0
- data/lib/tb/json.rb +1 -1
- data/lib/tb/reader.rb +4 -4
- data/lib/tb/search.rb +2 -4
- data/lib/tb/zipper.rb +60 -0
- data/test/test_cmd_cat.rb +40 -0
- data/test/test_cmd_git_log.rb +116 -0
- data/test/test_cmd_ls.rb +90 -0
- data/test/test_cmd_svn_log.rb +87 -0
- data/test/test_cmd_to_csv.rb +14 -0
- data/test/test_cmdutil.rb +25 -10
- data/test/test_csv.rb +10 -0
- data/test/test_customcmp.rb +14 -0
- data/test/test_customeq.rb +20 -0
- data/test/{test_enumerable.rb → test_ex_enumerable.rb} +181 -3
- data/test/test_search.rb +2 -10
- data/test/test_tbenum.rb +3 -3
- data/test/test_zipper.rb +22 -0
- metadata +20 -8
- data/lib/tb/enum.rb +0 -294
- data/lib/tb/pairs.rb +0 -227
- data/test/test_pairs.rb +0 -122
data/lib/tb/cmd_join.rb
CHANGED
@@ -71,8 +71,6 @@ def (Tb::Cmd).main_join(argv)
|
|
71
71
|
$stderr.puts "shared keys: #{(result.list_fields & tbl.list_fields).inspect}" if 1 <= Tb::Cmd.opt_debug
|
72
72
|
result = result.natjoin2_outer(tbl, Tb::Cmd.opt_join_outer_missing, retain_left, retain_right)
|
73
73
|
}
|
74
|
-
|
75
|
-
result.write_to_csv(out, !Tb::Cmd.opt_N)
|
76
|
-
}
|
74
|
+
output_tbenum(result)
|
77
75
|
end
|
78
76
|
|
data/lib/tb/cmd_ls.rb
CHANGED
@@ -63,9 +63,7 @@ def (Tb::Cmd).main_ls(argv)
|
|
63
63
|
ls.ls_run(Pathname(ls.real_pathname_string(arg)))
|
64
64
|
}
|
65
65
|
}
|
66
|
-
|
67
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
68
|
-
}
|
66
|
+
output_tbenum(er)
|
69
67
|
if ls.fail
|
70
68
|
exit false
|
71
69
|
end
|
@@ -106,7 +104,7 @@ class Tb::Cmd::Ls
|
|
106
104
|
return
|
107
105
|
end
|
108
106
|
entries.map! {|filename| real_pathname_string(filename) }
|
109
|
-
entries = entries.sort_by {|filename| smart_cmp_value(filename) }
|
107
|
+
entries = entries.sort_by {|filename| Tb::Func.smart_cmp_value(filename) }
|
110
108
|
if @opts[:a] || @opts[:A]
|
111
109
|
entries1, entries2 = entries.partition {|filename| /\A\./ =~ filename }
|
112
110
|
entries0, entries1 = entries1.partition {|filename| filename == '.' || filename == '..' }
|
@@ -155,7 +153,7 @@ class Tb::Cmd::Ls
|
|
155
153
|
end
|
156
154
|
@y.yield ls_long_info(path, st)
|
157
155
|
else
|
158
|
-
@y.yield
|
156
|
+
@y.yield({'filename' => path.to_s})
|
159
157
|
end
|
160
158
|
end
|
161
159
|
|
@@ -179,9 +177,9 @@ class Tb::Cmd::Ls
|
|
179
177
|
end
|
180
178
|
|
181
179
|
def ls_long_info(path, st)
|
182
|
-
|
180
|
+
Hash[ls_long_header.map {|info_type|
|
183
181
|
[info_type, self.send("ls_info_#{info_type}", path, st)]
|
184
|
-
}
|
182
|
+
}]
|
185
183
|
end
|
186
184
|
|
187
185
|
def ls_info_dev(path, st) sprintf("0x%x", st.dev) end
|
@@ -234,7 +232,7 @@ class Tb::Cmd::Ls
|
|
234
232
|
if pw
|
235
233
|
pw.name
|
236
234
|
else
|
237
|
-
uid
|
235
|
+
uid.to_s
|
238
236
|
end
|
239
237
|
end
|
240
238
|
|
@@ -247,7 +245,7 @@ class Tb::Cmd::Ls
|
|
247
245
|
if gr
|
248
246
|
gr.name
|
249
247
|
else
|
250
|
-
gid
|
248
|
+
gid.to_s
|
251
249
|
end
|
252
250
|
end
|
253
251
|
|
@@ -291,11 +289,6 @@ class Tb::Cmd::Ls
|
|
291
289
|
end
|
292
290
|
|
293
291
|
def real_pathname_string(str)
|
294
|
-
|
295
|
-
# pathname is a sequence of bytes on Unix.
|
296
|
-
str.dup.force_encoding("ASCII-8BIT")
|
297
|
-
else
|
298
|
-
str
|
299
|
-
end
|
292
|
+
str.dup.force_encoding("ASCII-8BIT")
|
300
293
|
end
|
301
294
|
end
|
data/lib/tb/cmd_mheader.rb
CHANGED
@@ -68,7 +68,7 @@ def (Tb::Cmd).main_mheader(argv)
|
|
68
68
|
}
|
69
69
|
h2 = header_end_p.call
|
70
70
|
if h2
|
71
|
-
pairs2 =
|
71
|
+
pairs2 = Hash[h2.map.with_index {|v, i| ["#{i+1}", v] }]
|
72
72
|
y.yield pairs2
|
73
73
|
header = nil
|
74
74
|
end
|
@@ -77,9 +77,8 @@ def (Tb::Cmd).main_mheader(argv)
|
|
77
77
|
end
|
78
78
|
}
|
79
79
|
}
|
80
|
-
|
81
|
-
|
82
|
-
}
|
80
|
+
Tb::Cmd.opt_N = true
|
81
|
+
output_tbenum(er)
|
83
82
|
if header
|
84
83
|
warn "unique header fields not recognized."
|
85
84
|
end
|
data/lib/tb/cmd_nest.rb
CHANGED
@@ -58,15 +58,12 @@ def (Tb::Cmd).main_nest(argv)
|
|
58
58
|
cv = pairs.reject {|f, v|
|
59
59
|
oldfields_hash[f]
|
60
60
|
}.map {|f, v|
|
61
|
-
[smart_cmp_value(f), smart_cmp_value(v)]
|
61
|
+
[Tb::Func.smart_cmp_value(f), Tb::Func.smart_cmp_value(v)]
|
62
62
|
}.sort
|
63
63
|
[cv, pairs]
|
64
64
|
}
|
65
65
|
|
66
66
|
nested = nil
|
67
|
-
boundary_p = lambda {|(cv1, _), (cv2, _)|
|
68
|
-
cv1 != cv2
|
69
|
-
}
|
70
67
|
before_group = lambda {|(_, _)|
|
71
68
|
nested = []
|
72
69
|
}
|
@@ -82,13 +79,11 @@ def (Tb::Cmd).main_nest(argv)
|
|
82
79
|
}
|
83
80
|
assoc = last_pairs.reject {|f, v| oldfields_hash[f] }.to_a
|
84
81
|
assoc << [newfield, nested_csv]
|
85
|
-
pairs =
|
82
|
+
pairs = Hash[assoc]
|
86
83
|
y.yield pairs
|
87
84
|
}
|
88
|
-
sorted.
|
89
|
-
}
|
90
|
-
with_output {|out|
|
91
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
85
|
+
sorted.detect_group_by(before_group, after_group) {|cv,| cv }.each(&body)
|
92
86
|
}
|
87
|
+
output_tbenum(er)
|
93
88
|
end
|
94
89
|
|
data/lib/tb/cmd_newfield.rb
CHANGED
@@ -47,9 +47,7 @@ def (Tb::Cmd).main_newfield(argv)
|
|
47
47
|
argv = ['-'] if argv.empty?
|
48
48
|
creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
|
49
49
|
er = creader.newfield(field) {|pairs| pr.call(pairs) }
|
50
|
-
|
51
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
52
|
-
}
|
50
|
+
output_tbenum(er)
|
53
51
|
end
|
54
52
|
|
55
53
|
|
data/lib/tb/cmd_rename.rb
CHANGED
@@ -56,11 +56,9 @@ def (Tb::Cmd).main_rename(argv)
|
|
56
56
|
}
|
57
57
|
y.set_header header.map {|f| h.fetch(f, f) }
|
58
58
|
}.each {|pairs|
|
59
|
-
y.yield
|
59
|
+
y.yield Hash[pairs.map {|f, v| [h.fetch(f, f), v] }]
|
60
60
|
}
|
61
61
|
}
|
62
|
-
|
63
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
64
|
-
}
|
62
|
+
output_tbenum(er)
|
65
63
|
end
|
66
64
|
|
data/lib/tb/cmd_shape.rb
CHANGED
data/lib/tb/cmd_sort.rb
CHANGED
@@ -53,9 +53,9 @@ def (Tb::Cmd).main_sort(argv)
|
|
53
53
|
creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
|
54
54
|
header = []
|
55
55
|
if fs
|
56
|
-
blk = lambda {|pairs| fs.map {|f| smart_cmp_value(pairs[f]) } }
|
56
|
+
blk = lambda {|pairs| fs.map {|f| Tb::Func.smart_cmp_value(pairs[f]) } }
|
57
57
|
else
|
58
|
-
blk = lambda {|pairs| header.map {|f| smart_cmp_value(pairs[f]) } }
|
58
|
+
blk = lambda {|pairs| header.map {|f| Tb::Func.smart_cmp_value(pairs[f]) } }
|
59
59
|
end
|
60
60
|
if Tb::Cmd.opt_sort_r
|
61
61
|
blk1 = blk
|
@@ -71,9 +71,7 @@ def (Tb::Cmd).main_sort(argv)
|
|
71
71
|
y.yield pairs
|
72
72
|
}
|
73
73
|
}.extsort_by(&blk)
|
74
|
-
|
75
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
76
|
-
}
|
74
|
+
output_tbenum(er)
|
77
75
|
end
|
78
76
|
|
79
77
|
|
data/lib/tb/cmd_svn_log.rb
CHANGED
@@ -101,11 +101,11 @@ class Tb::Cmd::SVNLOGListener
|
|
101
101
|
@log['paths'].each {|h|
|
102
102
|
assoc = @log.to_a.reject {|f, v| !%w[rev author date msg].include?(f) }
|
103
103
|
assoc += h.to_a.reject {|f, v| !%w[kind action path].include?(f) }
|
104
|
-
@y.yield
|
104
|
+
@y.yield Hash[assoc]
|
105
105
|
}
|
106
106
|
else
|
107
107
|
assoc = @log.to_a.reject {|f, v| !%w[rev author date msg].include?(f) }
|
108
|
-
@y.yield
|
108
|
+
@y.yield Hash[assoc]
|
109
109
|
end
|
110
110
|
@log = nil
|
111
111
|
end
|
@@ -170,8 +170,6 @@ def (Tb::Cmd).main_svn_log(argv)
|
|
170
170
|
REXML::Parsers::StreamParser.new(f, listener).parse
|
171
171
|
}
|
172
172
|
}
|
173
|
-
|
174
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
175
|
-
}
|
173
|
+
output_tbenum(er)
|
176
174
|
end
|
177
175
|
|
data/lib/tb/cmd_tar_tvf.rb
CHANGED
@@ -436,12 +436,10 @@ def (Tb::Cmd).main_tar_tvf(argv)
|
|
436
436
|
formatted["tar_typeflag"] = h[:typeflag]
|
437
437
|
formatted["tar_magic"] = h[:magic]
|
438
438
|
formatted["tar_version"] = h[:version]
|
439
|
-
y.yield
|
439
|
+
y.yield Hash[header.map {|f2| [f2, formatted[f2]] }]
|
440
440
|
}
|
441
441
|
}
|
442
442
|
}
|
443
443
|
}
|
444
|
-
|
445
|
-
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
446
|
-
}
|
444
|
+
output_tbenum(er)
|
447
445
|
end
|
data/lib/tb/cmd_to_csv.rb
CHANGED
data/lib/tb/cmd_unnest.rb
CHANGED
data/lib/tb/cmdutil.rb
CHANGED
@@ -96,150 +96,36 @@ def err(msg)
|
|
96
96
|
raise SystemExit.new(1, msg)
|
97
97
|
end
|
98
98
|
|
99
|
-
def
|
100
|
-
case v
|
101
|
-
when nil
|
102
|
-
[]
|
103
|
-
when Numeric
|
104
|
-
[0, v]
|
105
|
-
when String
|
106
|
-
if v.respond_to? :force_encoding
|
107
|
-
v = v.dup.force_encoding("ASCII-8BIT")
|
108
|
-
end
|
109
|
-
case v
|
110
|
-
when /\A\s*-?\d+\s*\z/
|
111
|
-
[0, v.to_i(10)]
|
112
|
-
when /\A\s*-?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?\s*\z/
|
113
|
-
[0, Float(v)]
|
114
|
-
else
|
115
|
-
a = []
|
116
|
-
v.scan(/(\d+)|\D+/) {
|
117
|
-
if $1
|
118
|
-
a << 0 << $1.to_i
|
119
|
-
else
|
120
|
-
a << 1 << $&
|
121
|
-
end
|
122
|
-
}
|
123
|
-
a
|
124
|
-
end
|
125
|
-
else
|
126
|
-
raise ArgumentError, "unexpected: #{v.inspect}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def conv_to_numeric(v)
|
131
|
-
v = v.strip
|
132
|
-
if /\A-?\d+\z/ =~ v
|
133
|
-
v = v.to_i
|
134
|
-
elsif /\A-?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?\z/ =~ v
|
135
|
-
v = v.to_f
|
136
|
-
else
|
137
|
-
raise ArgumentError, "number string expected: #{v.inspect}"
|
138
|
-
end
|
139
|
-
v
|
140
|
-
end
|
141
|
-
|
142
|
-
class CountAggregator
|
143
|
-
def initialize() @result = 0 end
|
144
|
-
def update(v) @result += 1 end
|
145
|
-
def finish() @result end
|
146
|
-
end
|
147
|
-
|
148
|
-
class SumAggregator
|
149
|
-
def initialize() @result = 0 end
|
150
|
-
def update(v) @result += conv_to_numeric(v) if !(v.nil? || v == '') end
|
151
|
-
def finish() @result end
|
152
|
-
end
|
153
|
-
|
154
|
-
class AvgAggregator
|
155
|
-
def initialize() @sum = 0; @count = 0 end
|
156
|
-
def update(v) @count += 1; @sum += conv_to_numeric(v) if !(v.nil? || v == '') end
|
157
|
-
def finish() @sum / @count.to_f end
|
158
|
-
end
|
159
|
-
|
160
|
-
class MaxAggregator
|
161
|
-
def initialize() @v = nil; @cmp = nil end
|
162
|
-
def update(v)
|
163
|
-
cmp = smart_cmp_value(v)
|
164
|
-
if @cmp == nil
|
165
|
-
@v, @cmp = v, cmp
|
166
|
-
else
|
167
|
-
@v, @cmp = v, cmp if (@cmp <=> cmp) < 0
|
168
|
-
end
|
169
|
-
end
|
170
|
-
def finish() @v end
|
171
|
-
end
|
172
|
-
|
173
|
-
class MinAggregator
|
174
|
-
def initialize() @v = @cmp = nil end
|
175
|
-
def update(v)
|
176
|
-
cmp = smart_cmp_value(v)
|
177
|
-
if @cmp == nil
|
178
|
-
@v, @cmp = v, cmp
|
179
|
-
else
|
180
|
-
@v, @cmp = v, cmp if (@cmp <=> cmp) > 0
|
181
|
-
end
|
182
|
-
end
|
183
|
-
def finish() @v end
|
184
|
-
end
|
185
|
-
|
186
|
-
class ValuesAggregator
|
187
|
-
def initialize() @result = [] end
|
188
|
-
def update(v) @result << v if v end
|
189
|
-
def finish() @result.join(",") end
|
190
|
-
end
|
191
|
-
|
192
|
-
class UniqueValuesAggregator
|
193
|
-
def initialize() @result = [] end
|
194
|
-
def update(v) @result << v if v end
|
195
|
-
def finish() @result.uniq.join(",") end
|
196
|
-
end
|
197
|
-
|
198
|
-
class Selector
|
199
|
-
def initialize(i, aggregator) @i = i; @agg = aggregator end
|
200
|
-
def update(ary) @agg.update(ary[@i]) end
|
201
|
-
def finish() @agg.finish end
|
202
|
-
end
|
203
|
-
|
204
|
-
def make_aggregator(spec, fs)
|
99
|
+
def parse_aggregator_spec(spec)
|
205
100
|
case spec
|
206
101
|
when 'count'
|
207
|
-
|
102
|
+
['count', nil]
|
208
103
|
when /\Asum\((.*)\)\z/
|
209
|
-
|
210
|
-
i = fs.index(field)
|
211
|
-
raise ArgumentError, "field not found: #{field.inspect}" if !i
|
212
|
-
Selector.new(i, SumAggregator.new)
|
104
|
+
['sum', $1]
|
213
105
|
when /\Aavg\((.*)\)\z/
|
214
|
-
|
215
|
-
i = fs.index(field)
|
216
|
-
raise ArgumentError, "field not found: #{field.inspect}" if !i
|
217
|
-
Selector.new(i, AvgAggregator.new)
|
106
|
+
['avg', $1]
|
218
107
|
when /\Amax\((.*)\)\z/
|
219
|
-
|
220
|
-
i = fs.index(field)
|
221
|
-
raise ArgumentError, "field not found: #{field.inspect}" if !i
|
222
|
-
Selector.new(i, MaxAggregator.new)
|
108
|
+
['max', $1]
|
223
109
|
when /\Amin\((.*)\)\z/
|
224
|
-
|
225
|
-
i = fs.index(field)
|
226
|
-
raise ArgumentError, "field not found: #{field.inspect}" if !i
|
227
|
-
Selector.new(i, MinAggregator.new)
|
110
|
+
['min', $1]
|
228
111
|
when /\Avalues\((.*)\)\z/
|
229
|
-
|
230
|
-
i = fs.index(field)
|
231
|
-
raise ArgumentError, "field not found: #{field.inspect}" if !i
|
232
|
-
Selector.new(i, ValuesAggregator.new)
|
112
|
+
['values', $1]
|
233
113
|
when /\Auniquevalues\((.*)\)\z/
|
234
|
-
|
235
|
-
i = fs.index(field)
|
236
|
-
raise ArgumentError, "field not found: #{field.inspect}" if !i
|
237
|
-
Selector.new(i, UniqueValuesAggregator.new)
|
114
|
+
['uniquevalues', $1]
|
238
115
|
else
|
239
116
|
raise ArgumentError, "unexpected aggregation spec: #{spec.inspect}"
|
240
117
|
end
|
241
118
|
end
|
242
119
|
|
120
|
+
def parse_aggregator_spec2(spec)
|
121
|
+
name, field = parse_aggregator_spec(spec)
|
122
|
+
func = Tb::Func::AggregationFunctions[name]
|
123
|
+
if !func
|
124
|
+
raise ArgumentError, "unexpected aggregation spec: #{spec.inspect}"
|
125
|
+
end
|
126
|
+
[func, field]
|
127
|
+
end
|
128
|
+
|
243
129
|
def split_field_list_argument(arg)
|
244
130
|
split_csv_argument(arg).map {|f| f || '' }
|
245
131
|
end
|
@@ -266,14 +152,18 @@ def tbl_generate_tsv(tbl, out)
|
|
266
152
|
end
|
267
153
|
end
|
268
154
|
|
269
|
-
def with_output
|
270
|
-
if
|
271
|
-
tmp =
|
155
|
+
def with_output(filename=Tb::Cmd.opt_output)
|
156
|
+
if filename && filename != '-'
|
157
|
+
tmp = filename + ".part"
|
272
158
|
begin
|
273
159
|
File.open(tmp, 'w') {|f|
|
274
160
|
yield f
|
275
161
|
}
|
276
|
-
File.
|
162
|
+
if File.exist?(filename) && FileUtils.compare_file(filename, tmp)
|
163
|
+
File.unlink tmp
|
164
|
+
else
|
165
|
+
File.rename tmp, filename
|
166
|
+
end
|
277
167
|
ensure
|
278
168
|
File.unlink tmp if File.exist? tmp
|
279
169
|
end
|
@@ -285,3 +175,35 @@ def with_output
|
|
285
175
|
yield $stdout
|
286
176
|
end
|
287
177
|
end
|
178
|
+
|
179
|
+
def output_tbenum(te)
|
180
|
+
filename = Tb::Cmd.opt_output
|
181
|
+
if /\A([a-z0-9]{2,}):/ =~ filename
|
182
|
+
fmt = $1
|
183
|
+
filename = $'
|
184
|
+
else
|
185
|
+
fmt = nil
|
186
|
+
end
|
187
|
+
if !fmt
|
188
|
+
case filename
|
189
|
+
when /\.csv\z/
|
190
|
+
fmt = 'csv'
|
191
|
+
when /\.json\z/
|
192
|
+
fmt = 'json'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
if fmt
|
196
|
+
case fmt
|
197
|
+
when 'csv'
|
198
|
+
write_proc = lambda {|out| te.write_to_csv(out, !Tb::Cmd.opt_N) }
|
199
|
+
when 'json'
|
200
|
+
write_proc = lambda {|out| te.write_to_json(out) }
|
201
|
+
else
|
202
|
+
err("unexpected format: #{fmt.inspect}")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
write_proc ||= lambda {|out| te.write_to_csv(out, !Tb::Cmd.opt_N) }
|
206
|
+
with_output(filename) {|out|
|
207
|
+
write_proc.call(out)
|
208
|
+
}
|
209
|
+
end
|