tb 0.3 → 0.4
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 +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
@@ -0,0 +1,102 @@
|
|
1
|
+
# Copyright (C) 2012 Tanaka Akira <akr@fsij.org>
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions
|
5
|
+
# are met:
|
6
|
+
#
|
7
|
+
# 1. Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following
|
11
|
+
# disclaimer in the documentation and/or other materials provided
|
12
|
+
# with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote
|
14
|
+
# products derived from this software without specific prior
|
15
|
+
# written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
18
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
23
|
+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
26
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
module Tb::ExEnumerator
|
30
|
+
# :call-seq:
|
31
|
+
#
|
32
|
+
# Tb::ExEnumerator.merge_sorted(enumerator1, ...) {|key, enumerator1_or_nil, ...| ... }
|
33
|
+
#
|
34
|
+
# iterates over enumerators specified by arguments.
|
35
|
+
#
|
36
|
+
# The enumerators should yield an array which first element is a comparable key.
|
37
|
+
# The enumerators should be sorted by the key in ascending order.
|
38
|
+
#
|
39
|
+
# Tb::Enumerator.merge_sorted iterates keys in all of the enumerators.
|
40
|
+
# It yields an array which contains a key and enumerators which has the key.
|
41
|
+
# The array contains nil if corresponding enumerator don't have the key.
|
42
|
+
#
|
43
|
+
# The block may or may not use +next+ method to advance enumerators.
|
44
|
+
# Anyway Tb::Enumerator.merge_sorted advance enumerators until peeked key is
|
45
|
+
# greater than the yielded key.
|
46
|
+
#
|
47
|
+
# If a enumerator has multiple elements with same key,
|
48
|
+
# the block can read them using +next+ method.
|
49
|
+
# +peek+ method should also be used to determine the key of the next element has
|
50
|
+
# the current key or not.
|
51
|
+
# Be careful to not consume extra elements with different key.
|
52
|
+
#
|
53
|
+
def (Tb::ExEnumerator).merge_sorted(*enumerators) # :yields: [key, enumerator1_or_nil, ...]
|
54
|
+
while true
|
55
|
+
has_min = false
|
56
|
+
min = nil
|
57
|
+
min_enumerators = []
|
58
|
+
enumerators.each {|kpe|
|
59
|
+
begin
|
60
|
+
key, = kpe.peek
|
61
|
+
rescue StopIteration
|
62
|
+
min_enumerators << nil
|
63
|
+
next
|
64
|
+
end
|
65
|
+
if !has_min
|
66
|
+
has_min = true
|
67
|
+
min = key
|
68
|
+
min_enumerators << kpe
|
69
|
+
else
|
70
|
+
cmp = key <=> min
|
71
|
+
if cmp < 0
|
72
|
+
min_enumerators.fill(nil)
|
73
|
+
min_enumerators << kpe
|
74
|
+
min = key
|
75
|
+
elsif cmp == 0
|
76
|
+
min_enumerators << kpe
|
77
|
+
else
|
78
|
+
min_enumerators << nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
}
|
82
|
+
if !has_min
|
83
|
+
return
|
84
|
+
end
|
85
|
+
yield [min, *min_enumerators]
|
86
|
+
min_enumerators.each {|kpe|
|
87
|
+
next if !kpe
|
88
|
+
while true
|
89
|
+
begin
|
90
|
+
key, = kpe.peek
|
91
|
+
rescue StopIteration
|
92
|
+
break
|
93
|
+
end
|
94
|
+
if (min <=> key) < 0
|
95
|
+
break
|
96
|
+
end
|
97
|
+
kpe.next
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/tb/fileenumerator.rb
CHANGED
@@ -282,7 +282,7 @@ class Tb::FileEnumerator
|
|
282
282
|
end
|
283
283
|
end
|
284
284
|
|
285
|
-
module Tb::
|
285
|
+
module Tb::Enumerable
|
286
286
|
# creates a Tb::FileHeaderEnumerator object.
|
287
287
|
#
|
288
288
|
def to_fileenumerator
|
@@ -299,7 +299,7 @@ module Tb::Enum
|
|
299
299
|
end
|
300
300
|
|
301
301
|
class Tb::FileHeaderEnumerator < Tb::FileEnumerator
|
302
|
-
include Tb::
|
302
|
+
include Tb::Enumerable
|
303
303
|
|
304
304
|
class HBuilder
|
305
305
|
def initialize(klass)
|
data/lib/tb/func.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# Copyright (C) 2012 Tanaka Akira <akr@fsij.org>
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions
|
5
|
+
# are met:
|
6
|
+
#
|
7
|
+
# 1. Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following
|
11
|
+
# disclaimer in the documentation and/or other materials provided
|
12
|
+
# with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote
|
14
|
+
# products derived from this software without specific prior
|
15
|
+
# written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
18
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
23
|
+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
26
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
module Tb::Func
|
30
|
+
def self.smart_cmp_value(v)
|
31
|
+
case v
|
32
|
+
when nil
|
33
|
+
[]
|
34
|
+
when Numeric
|
35
|
+
[0, v]
|
36
|
+
when String
|
37
|
+
if v.respond_to? :force_encoding
|
38
|
+
v = v.dup.force_encoding("ASCII-8BIT")
|
39
|
+
end
|
40
|
+
case v
|
41
|
+
when /\A\s*-?\d+\s*\z/
|
42
|
+
[0, v.to_i(10)]
|
43
|
+
when /\A\s*-?(\d+(\.\d+)?)([eE][-+]?\d+)?\s*\z/
|
44
|
+
[0, Float(v)]
|
45
|
+
else
|
46
|
+
a = []
|
47
|
+
v.scan(/(\d+)|\D+/) {
|
48
|
+
if $1
|
49
|
+
a << 0 << $1.to_i
|
50
|
+
else
|
51
|
+
a << 1 << $&
|
52
|
+
end
|
53
|
+
}
|
54
|
+
a
|
55
|
+
end
|
56
|
+
else
|
57
|
+
raise ArgumentError, "unexpected: #{v.inspect}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.smart_numerize(v)
|
62
|
+
return v if v.kind_of? Numeric
|
63
|
+
v = v.strip
|
64
|
+
if /\A-?\d+\z/ =~ v
|
65
|
+
v = v.to_i
|
66
|
+
elsif /\A-?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?\z/ =~ v
|
67
|
+
v = v.to_f
|
68
|
+
else
|
69
|
+
raise ArgumentError, "number string expected: #{v.inspect}"
|
70
|
+
end
|
71
|
+
v
|
72
|
+
end
|
73
|
+
|
74
|
+
module Count; end
|
75
|
+
def Count.start(value) 1 end
|
76
|
+
def Count.call(v1, v2) v1 + v2 end
|
77
|
+
def Count.aggregate(count) count end
|
78
|
+
|
79
|
+
module Sum; end
|
80
|
+
def Sum.start(value) Tb::Func.smart_numerize(value) end
|
81
|
+
def Sum.call(v1, v2) v1 + v2 end
|
82
|
+
def Sum.aggregate(sum) sum end
|
83
|
+
|
84
|
+
module Min; end
|
85
|
+
def Min.start(value) [value, Tb::Func.smart_cmp_value(value)] end
|
86
|
+
def Min.call(vc1, vc2) (vc1.last <=> vc2.last) <= 0 ? vc1 : vc2 end
|
87
|
+
def Min.aggregate(vc) vc.first end
|
88
|
+
|
89
|
+
module Max; end
|
90
|
+
def Max.start(value) [value, Tb::Func.smart_cmp_value(value)] end
|
91
|
+
def Max.call(vc1, vc2) (vc1.last <=> vc2.last) >= 0 ? vc1 : vc2 end
|
92
|
+
def Max.aggregate(vc) vc.first end
|
93
|
+
|
94
|
+
module Avg; end
|
95
|
+
def Avg.start(value) [Tb::Func.smart_numerize(value), 1] end
|
96
|
+
def Avg.call(v1, v2) [v1[0] + v2[0], v1[1] + v2[1]] end
|
97
|
+
def Avg.aggregate(sum_count) sum_count[0] / sum_count[1].to_f end
|
98
|
+
|
99
|
+
module First; end
|
100
|
+
def First.start(value) value end
|
101
|
+
def First.call(v1, v2) v1 end
|
102
|
+
def First.aggregate(value) value end
|
103
|
+
|
104
|
+
module Last; end
|
105
|
+
def Last.start(value) value end
|
106
|
+
def Last.call(v1, v2) v2 end
|
107
|
+
def Last.aggregate(value) value end
|
108
|
+
|
109
|
+
module Values; end
|
110
|
+
def Values.start(value) [value] end
|
111
|
+
def Values.call(a1, a2) a1.concat a2 end
|
112
|
+
def Values.aggregate(ary) ary.join(',') end
|
113
|
+
|
114
|
+
module UniqueValues; end
|
115
|
+
def UniqueValues.start(value) {value => true} end
|
116
|
+
def UniqueValues.call(h1, h2) h1.update h2 end
|
117
|
+
def UniqueValues.aggregate(hash) hash.keys.join(',') end
|
118
|
+
|
119
|
+
class FirstN
|
120
|
+
def initialize(n) @n = n end
|
121
|
+
def start(value) [value] end
|
122
|
+
def call(a1, a2) a1.length == @n ? a1 : (a1+a2).first(@n) end
|
123
|
+
def aggregate(ary) ary end
|
124
|
+
end
|
125
|
+
|
126
|
+
class LastN
|
127
|
+
def initialize(n) @n = n end
|
128
|
+
def start(value) [value] end
|
129
|
+
def call(a1, a2) a2.length == @n ? a2 : (a1+a2).last(@n) end
|
130
|
+
def aggregate(ary) ary end
|
131
|
+
end
|
132
|
+
|
133
|
+
AggregationFunctions = {}
|
134
|
+
Tb::Func.constants.each {|c|
|
135
|
+
v = Tb::Func.const_get(c)
|
136
|
+
if v.respond_to? :aggregate
|
137
|
+
AggregationFunctions[c.to_s.downcase] = v
|
138
|
+
end
|
139
|
+
}
|
140
|
+
|
141
|
+
end
|
data/lib/tb/json.rb
CHANGED
data/lib/tb/reader.rb
CHANGED
@@ -29,7 +29,7 @@
|
|
29
29
|
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
30
|
|
31
31
|
class Tb::Reader
|
32
|
-
include Tb::
|
32
|
+
include Tb::Enumerable
|
33
33
|
|
34
34
|
def initialize(opts={}, &rawreader_open)
|
35
35
|
@opt_n = opts[:numeric]
|
@@ -89,12 +89,12 @@ class Tb::Reader
|
|
89
89
|
h = self.internal_header(rawreader)
|
90
90
|
header_proc.call(h) if header_proc
|
91
91
|
while ary = self.internal_shift(rawreader)
|
92
|
-
pairs =
|
92
|
+
pairs = {}
|
93
93
|
ary.each_with_index {|v, i|
|
94
94
|
f = field_from_index_ex(i)
|
95
|
-
pairs
|
95
|
+
pairs[f] = v
|
96
96
|
}
|
97
|
-
yield
|
97
|
+
yield pairs
|
98
98
|
end
|
99
99
|
}
|
100
100
|
@reader_open.call(body)
|
data/lib/tb/search.rb
CHANGED
@@ -441,8 +441,7 @@ module Tb::Search::EmptyState
|
|
441
441
|
elsif !rest.empty?
|
442
442
|
return rest[0]
|
443
443
|
else
|
444
|
-
|
445
|
-
raise exc, "key not found: #{k}"
|
444
|
+
raise KeyError, "key not found: #{k}"
|
446
445
|
end
|
447
446
|
end
|
448
447
|
|
@@ -526,8 +525,7 @@ class Tb::Search::State
|
|
526
525
|
elsif !rest.empty?
|
527
526
|
return rest[0]
|
528
527
|
else
|
529
|
-
|
530
|
-
raise exc, "key not found: #{k}"
|
528
|
+
raise KeyError, "key not found: #{k}"
|
531
529
|
end
|
532
530
|
end
|
533
531
|
|
data/lib/tb/zipper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Copyright (C) 2012 Tanaka Akira <akr@fsij.org>
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions
|
5
|
+
# are met:
|
6
|
+
#
|
7
|
+
# 1. Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following
|
11
|
+
# disclaimer in the documentation and/or other materials provided
|
12
|
+
# with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote
|
14
|
+
# products derived from this software without specific prior
|
15
|
+
# written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
18
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
23
|
+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
26
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
class Tb::Zipper
|
30
|
+
def initialize(ops)
|
31
|
+
@ops = ops
|
32
|
+
end
|
33
|
+
|
34
|
+
def start(ary)
|
35
|
+
if ary.length != @ops.length
|
36
|
+
raise ArgumentError, "expect an array which lengths are #{@ops.length}"
|
37
|
+
end
|
38
|
+
@ops.map.with_index {|op, i|
|
39
|
+
op.start(ary[i])
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(ary1, ary2)
|
44
|
+
if ary1.length != @ops.length || ary2.length != @ops.length
|
45
|
+
raise ArgumentError, "expect an array of arrays which lengths are #{@ops.length}"
|
46
|
+
end
|
47
|
+
@ops.zip(ary1, ary2).map {|op, v1, v2|
|
48
|
+
op.call(v1, v2)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def aggregate(ary)
|
53
|
+
if ary.length != @ops.length
|
54
|
+
raise ArgumentError, "expect an array which lengths are #{@ops.length}"
|
55
|
+
end
|
56
|
+
@ops.map.with_index {|op, i|
|
57
|
+
op.aggregate(ary[i])
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
data/test/test_cmd_cat.rb
CHANGED
@@ -147,4 +147,44 @@ class TestTbCmdCat < Test::Unit::TestCase
|
|
147
147
|
End
|
148
148
|
end
|
149
149
|
|
150
|
+
def test_json_output
|
151
|
+
File.open(i1="i1.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
|
152
|
+
a,b,c
|
153
|
+
1,2,3
|
154
|
+
4,5,6
|
155
|
+
End
|
156
|
+
Tb::Cmd.main_cat(['-o', o="o.json", i1])
|
157
|
+
assert_equal(<<-"End".gsub(/\s+/, ''), File.read(o).gsub(/\s+/, ''))
|
158
|
+
[
|
159
|
+
{"a":"1", "b":"2", "c": "3"},
|
160
|
+
{"a":"4", "b":"5", "c": "6"}
|
161
|
+
]
|
162
|
+
End
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_json_output2
|
166
|
+
File.open(i1="i1.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
|
167
|
+
a,b,c
|
168
|
+
1,2,3
|
169
|
+
4,5,6
|
170
|
+
End
|
171
|
+
Tb::Cmd.main_cat(['-o', "json:" + (o="o.csv"), i1])
|
172
|
+
assert_equal(<<-"End".gsub(/\s+/, ''), File.read(o).gsub(/\s+/, ''))
|
173
|
+
[
|
174
|
+
{"a":"1", "b":"2", "c": "3"},
|
175
|
+
{"a":"4", "b":"5", "c": "6"}
|
176
|
+
]
|
177
|
+
End
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_invalid_output_format
|
181
|
+
File.open(i1="i1.csv", "w") {|f| f << <<-"End".gsub(/^[ \t]+/, '') }
|
182
|
+
a,b,c
|
183
|
+
1,2,3
|
184
|
+
4,5,6
|
185
|
+
End
|
186
|
+
exc = assert_raise(SystemExit) { Tb::Cmd.main_cat(['-o', "xson:o.csv", i1]) }
|
187
|
+
assert(!exc.success?)
|
188
|
+
end
|
189
|
+
|
150
190
|
end
|
data/test/test_cmd_git_log.rb
CHANGED
@@ -15,6 +15,16 @@ class TestTbCmdGitLog < Test::Unit::TestCase
|
|
15
15
|
FileUtils.rmtree @tmpdir
|
16
16
|
end
|
17
17
|
|
18
|
+
def with_stderr(io)
|
19
|
+
save = $stderr
|
20
|
+
$stderr = io
|
21
|
+
begin
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
$stderr = save
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
18
28
|
def test_basic
|
19
29
|
system("git init -q")
|
20
30
|
File.open("foo", "w") {|f| f.puts "bar" }
|
@@ -43,4 +53,110 @@ class TestTbCmdGitLog < Test::Unit::TestCase
|
|
43
53
|
assert_equal(filename, ftb.get_record(0)["filename"])
|
44
54
|
end
|
45
55
|
|
56
|
+
def test_debug_git_log_output_input
|
57
|
+
system("git init -q")
|
58
|
+
File.open("foo", "w") {|f| f.puts "bar" }
|
59
|
+
system("git add foo")
|
60
|
+
system("git commit -q -m msg foo")
|
61
|
+
Tb::Cmd.main_git_log(['-o', o="o.csv", '--debug-git-log-output', g='gitlog'])
|
62
|
+
result = File.read(o)
|
63
|
+
tb = Tb.parse_csv(result)
|
64
|
+
assert_equal(1, tb.size)
|
65
|
+
assert_match(/,A,foo\n/, tb.get_record(0)["files"])
|
66
|
+
gresult = File.read(g)
|
67
|
+
assert(!gresult.empty?)
|
68
|
+
FileUtils.rmtree('.git')
|
69
|
+
Tb::Cmd.main_git_log(['-o', o="o.csv", '--debug-git-log-input', g])
|
70
|
+
result = File.read(o)
|
71
|
+
tb = Tb.parse_csv(result)
|
72
|
+
assert_equal(1, tb.size)
|
73
|
+
assert_match(/,A,foo\n/, tb.get_record(0)["files"])
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_warn1
|
77
|
+
system("git init -q")
|
78
|
+
File.open("foo", "w") {|f| f.puts "bar" }
|
79
|
+
system("git add foo")
|
80
|
+
system("git commit -q -m msg foo")
|
81
|
+
Tb::Cmd.main_git_log(['-o', o="o.csv", '--debug-git-log-output', g='gitlog'])
|
82
|
+
result = File.read(o)
|
83
|
+
tb = Tb.parse_csv(result)
|
84
|
+
assert_equal(1, tb.size)
|
85
|
+
assert_match(/,A,foo\n/, tb.get_record(0)["files"])
|
86
|
+
gresult = File.binread(g)
|
87
|
+
FileUtils.rmtree('.git')
|
88
|
+
###
|
89
|
+
gresult.sub!(/^:.*foo$/, ':hoge')
|
90
|
+
File.open(g, 'w') {|f| f.print gresult }
|
91
|
+
o2 = 'o2.csv'
|
92
|
+
File.open('log', 'w') {|log|
|
93
|
+
with_stderr(log) {
|
94
|
+
Tb::Cmd.main_git_log(['-o', o2, '--debug-git-log-input', g])
|
95
|
+
}
|
96
|
+
}
|
97
|
+
result = File.read(o2)
|
98
|
+
tb = Tb.parse_csv(result)
|
99
|
+
assert_equal(1, tb.size)
|
100
|
+
assert_not_match(/,A,foo\n/, tb.get_record(0)["files"])
|
101
|
+
log = File.read('log')
|
102
|
+
assert(!log.empty?)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_warn2
|
106
|
+
system("git init -q")
|
107
|
+
File.open("foo", "w") {|f| f.puts "bar" }
|
108
|
+
system("git add foo")
|
109
|
+
system("git commit -q -m msg foo")
|
110
|
+
Tb::Cmd.main_git_log(['-o', o="o.csv", '--debug-git-log-output', g='gitlog'])
|
111
|
+
result = File.read(o)
|
112
|
+
tb = Tb.parse_csv(result)
|
113
|
+
assert_equal(1, tb.size)
|
114
|
+
assert_match(/,A,foo\n/, tb.get_record(0)["files"])
|
115
|
+
gresult = File.binread(g)
|
116
|
+
FileUtils.rmtree('.git')
|
117
|
+
###
|
118
|
+
gresult.sub!(/^author-name:/, 'author-name ')
|
119
|
+
File.open(g, 'w') {|f| f.print gresult }
|
120
|
+
o2 = 'o2.csv'
|
121
|
+
File.open('log', 'w') {|log|
|
122
|
+
with_stderr(log) {
|
123
|
+
Tb::Cmd.main_git_log(['-o', o2, '--debug-git-log-input', g])
|
124
|
+
}
|
125
|
+
}
|
126
|
+
result = File.read(o2)
|
127
|
+
tb = Tb.parse_csv(result)
|
128
|
+
assert_equal(1, tb.size)
|
129
|
+
assert_match(/,A,foo\n/, tb.get_record(0)["files"])
|
130
|
+
log = File.read('log')
|
131
|
+
assert(!log.empty?)
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_warn3
|
135
|
+
system("git init -q")
|
136
|
+
File.open("foo", "w") {|f| f.puts "bar" }
|
137
|
+
system("git add foo")
|
138
|
+
system("git commit -q -m msg foo")
|
139
|
+
Tb::Cmd.main_git_log(['-o', o="o.csv", '--debug-git-log-output', g='gitlog'])
|
140
|
+
result = File.read(o)
|
141
|
+
tb = Tb.parse_csv(result)
|
142
|
+
assert_equal(1, tb.size)
|
143
|
+
assert_match(/,A,foo\n/, tb.get_record(0)["files"])
|
144
|
+
gresult = File.binread(g)
|
145
|
+
FileUtils.rmtree('.git')
|
146
|
+
###
|
147
|
+
gresult.sub!(/end-commit/, 'endcommit')
|
148
|
+
File.open(g, 'w') {|f| f.print gresult }
|
149
|
+
o2 = 'o2.csv'
|
150
|
+
File.open('log', 'w') {|log|
|
151
|
+
with_stderr(log) {
|
152
|
+
Tb::Cmd.main_git_log(['-o', o2, '--debug-git-log-input', g])
|
153
|
+
}
|
154
|
+
}
|
155
|
+
result = File.read(o2)
|
156
|
+
tb = Tb.parse_csv(result)
|
157
|
+
assert_equal(0, tb.size)
|
158
|
+
log = File.read('log')
|
159
|
+
assert(!log.empty?)
|
160
|
+
end
|
161
|
+
|
46
162
|
end
|