tb 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README +2 -1
  2. data/lib/tb.rb +7 -3
  3. data/lib/tb/basic.rb +1 -1
  4. data/lib/tb/cmd_cat.rb +1 -3
  5. data/lib/tb/cmd_consecutive.rb +4 -6
  6. data/lib/tb/cmd_crop.rb +5 -7
  7. data/lib/tb/cmd_cross.rb +51 -49
  8. data/lib/tb/cmd_cut.rb +2 -6
  9. data/lib/tb/cmd_git_log.rb +20 -11
  10. data/lib/tb/cmd_grep.rb +1 -3
  11. data/lib/tb/cmd_group.rb +18 -44
  12. data/lib/tb/cmd_gsub.rb +2 -4
  13. data/lib/tb/cmd_join.rb +1 -3
  14. data/lib/tb/cmd_ls.rb +8 -15
  15. data/lib/tb/cmd_mheader.rb +3 -4
  16. data/lib/tb/cmd_nest.rb +4 -9
  17. data/lib/tb/cmd_newfield.rb +1 -3
  18. data/lib/tb/cmd_rename.rb +2 -4
  19. data/lib/tb/cmd_shape.rb +2 -3
  20. data/lib/tb/cmd_sort.rb +3 -5
  21. data/lib/tb/cmd_svn_log.rb +3 -5
  22. data/lib/tb/cmd_tar_tvf.rb +2 -4
  23. data/lib/tb/cmd_to_csv.rb +1 -1
  24. data/lib/tb/cmd_unnest.rb +1 -3
  25. data/lib/tb/cmdutil.rb +57 -135
  26. data/lib/tb/csv.rb +11 -54
  27. data/lib/tb/customcmp.rb +41 -0
  28. data/lib/tb/customeq.rb +41 -0
  29. data/lib/tb/enumerable.rb +225 -435
  30. data/lib/tb/enumerator.rb +22 -14
  31. data/lib/tb/ex_enumerable.rb +659 -0
  32. data/lib/tb/ex_enumerator.rb +102 -0
  33. data/lib/tb/fileenumerator.rb +2 -2
  34. data/lib/tb/func.rb +141 -0
  35. data/lib/tb/json.rb +1 -1
  36. data/lib/tb/reader.rb +4 -4
  37. data/lib/tb/search.rb +2 -4
  38. data/lib/tb/zipper.rb +60 -0
  39. data/test/test_cmd_cat.rb +40 -0
  40. data/test/test_cmd_git_log.rb +116 -0
  41. data/test/test_cmd_ls.rb +90 -0
  42. data/test/test_cmd_svn_log.rb +87 -0
  43. data/test/test_cmd_to_csv.rb +14 -0
  44. data/test/test_cmdutil.rb +25 -10
  45. data/test/test_csv.rb +10 -0
  46. data/test/test_customcmp.rb +14 -0
  47. data/test/test_customeq.rb +20 -0
  48. data/test/{test_enumerable.rb → test_ex_enumerable.rb} +181 -3
  49. data/test/test_search.rb +2 -10
  50. data/test/test_tbenum.rb +3 -3
  51. data/test/test_zipper.rb +22 -0
  52. metadata +20 -8
  53. data/lib/tb/enum.rb +0 -294
  54. data/lib/tb/pairs.rb +0 -227
  55. 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
@@ -282,7 +282,7 @@ class Tb::FileEnumerator
282
282
  end
283
283
  end
284
284
 
285
- module Tb::Enum
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::Enum
302
+ include Tb::Enumerable
303
303
 
304
304
  class HBuilder
305
305
  def initialize(klass)
@@ -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
@@ -30,7 +30,7 @@ require 'json'
30
30
 
31
31
  class Tb
32
32
  class JSONReader
33
- include Tb::Enum
33
+ include Tb::Enumerable
34
34
 
35
35
  def initialize(string)
36
36
  @ary = JSON.parse(string)
@@ -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::Enum
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 << [f, v]
95
+ pairs[f] = v
96
96
  }
97
- yield Tb::Pairs.new(pairs)
97
+ yield pairs
98
98
  end
99
99
  }
100
100
  @reader_open.call(body)
@@ -441,8 +441,7 @@ module Tb::Search::EmptyState
441
441
  elsif !rest.empty?
442
442
  return rest[0]
443
443
  else
444
- exc = defined?(KeyError) ? KeyError : IndexError # 1.9 v.s. 1.8
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
- exc = defined?(KeyError) ? KeyError : IndexError # 1.9 v.s. 1.8
530
- raise exc, "key not found: #{k}"
528
+ raise KeyError, "key not found: #{k}"
531
529
  end
532
530
  end
533
531
 
@@ -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
@@ -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
@@ -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