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.
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