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.
Files changed (81) hide show
  1. data/README +156 -5
  2. data/bin/tb +2 -1110
  3. data/lib/tb.rb +4 -2
  4. data/lib/tb/catreader.rb +131 -0
  5. data/lib/tb/cmd_cat.rb +65 -0
  6. data/lib/tb/cmd_consecutive.rb +79 -0
  7. data/lib/tb/cmd_crop.rb +105 -0
  8. data/lib/tb/cmd_cross.rb +119 -0
  9. data/lib/tb/cmd_csv.rb +42 -0
  10. data/lib/tb/cmd_cut.rb +77 -0
  11. data/lib/tb/cmd_grep.rb +76 -0
  12. data/lib/tb/cmd_group.rb +82 -0
  13. data/lib/tb/cmd_gsub.rb +77 -0
  14. data/lib/tb/cmd_help.rb +98 -0
  15. data/lib/tb/cmd_join.rb +81 -0
  16. data/lib/tb/cmd_json.rb +60 -0
  17. data/lib/tb/cmd_ls.rb +273 -0
  18. data/lib/tb/cmd_mheader.rb +77 -0
  19. data/lib/tb/cmd_newfield.rb +59 -0
  20. data/lib/tb/cmd_pnm.rb +43 -0
  21. data/lib/tb/cmd_pp.rb +70 -0
  22. data/lib/tb/cmd_rename.rb +58 -0
  23. data/lib/tb/cmd_shape.rb +67 -0
  24. data/lib/tb/cmd_sort.rb +58 -0
  25. data/lib/tb/cmd_svn_log.rb +158 -0
  26. data/lib/tb/cmd_tsv.rb +43 -0
  27. data/lib/tb/cmd_yaml.rb +47 -0
  28. data/lib/tb/cmdmain.rb +45 -0
  29. data/lib/tb/cmdtop.rb +58 -0
  30. data/lib/tb/cmdutil.rb +327 -0
  31. data/lib/tb/csv.rb +30 -6
  32. data/lib/tb/fieldset.rb +39 -41
  33. data/lib/tb/pager.rb +132 -0
  34. data/lib/tb/pnm.rb +357 -0
  35. data/lib/tb/reader.rb +18 -128
  36. data/lib/tb/record.rb +3 -3
  37. data/lib/tb/ropen.rb +70 -0
  38. data/lib/tb/{pathfinder.rb → search.rb} +69 -34
  39. data/lib/tb/tsv.rb +29 -1
  40. data/sample/colors.ppm +0 -0
  41. data/sample/gradation.pgm +0 -0
  42. data/sample/langs.csv +46 -0
  43. data/sample/tbplot +293 -0
  44. data/test-all-cov.rb +65 -0
  45. data/test-all.rb +5 -0
  46. data/test/test_basic.rb +99 -2
  47. data/test/test_catreader.rb +27 -0
  48. data/test/test_cmd_cat.rb +118 -0
  49. data/test/test_cmd_consecutive.rb +90 -0
  50. data/test/test_cmd_crop.rb +101 -0
  51. data/test/test_cmd_cross.rb +113 -0
  52. data/test/test_cmd_csv.rb +129 -0
  53. data/test/test_cmd_cut.rb +100 -0
  54. data/test/test_cmd_grep.rb +89 -0
  55. data/test/test_cmd_group.rb +181 -0
  56. data/test/test_cmd_gsub.rb +103 -0
  57. data/test/test_cmd_help.rb +190 -0
  58. data/test/test_cmd_join.rb +197 -0
  59. data/test/test_cmd_json.rb +75 -0
  60. data/test/test_cmd_ls.rb +203 -0
  61. data/test/test_cmd_mheader.rb +86 -0
  62. data/test/test_cmd_newfield.rb +63 -0
  63. data/test/test_cmd_pnm.rb +35 -0
  64. data/test/test_cmd_pp.rb +62 -0
  65. data/test/test_cmd_rename.rb +91 -0
  66. data/test/test_cmd_shape.rb +50 -0
  67. data/test/test_cmd_sort.rb +105 -0
  68. data/test/test_cmd_tsv.rb +67 -0
  69. data/test/test_cmd_yaml.rb +55 -0
  70. data/test/test_cmdtty.rb +154 -0
  71. data/test/test_cmdutil.rb +43 -0
  72. data/test/test_csv.rb +10 -0
  73. data/test/test_fieldset.rb +42 -0
  74. data/test/test_pager.rb +142 -0
  75. data/test/test_pnm.rb +374 -0
  76. data/test/test_reader.rb +147 -0
  77. data/test/test_record.rb +49 -0
  78. data/test/test_search.rb +575 -0
  79. data/test/test_tsv.rb +7 -0
  80. metadata +108 -5
  81. data/lib/tb/qtsv.rb +0 -93
@@ -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
- if @opt_n
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 close
195
- @reader.close
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 fix_header(header)
199
- h = {}
200
- header.map {|s|
201
- s ||= ''
202
- if h[s]
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
@@ -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.each_field_reserved {|f|
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
  }
@@ -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/pathfinder.rb - pattern matcher for two-dimensional array.
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::Pathfinder
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::Pathfinder::EmptyState.merge(:pos => spos)) {|st2|
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::Pathfinder::EmptyState.merge(:pos => spos)) {|st2|
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(:north, aa, st, &b)
97
- when :s, :south; try_rmove(:south, aa, st, &b)
98
- when :e, :east; try_rmove(:east, aa, st, &b)
99
- when :w, :west; try_rmove(:west, aa, st, &b)
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; _, dir = pat; try_rmove(dir, aa, st, &b)
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 TypeError, "unexpected pattern: #{pat.inspect}"
133
+ raise ArgumentError, "unexpected pattern: #{pat.inspect}"
130
134
  end
131
135
  end
132
136
 
133
- def try_rmove(dir, aa, st)
134
- x, y = st.fetch(:pos)
135
- case dir
136
- when :east then x += 1
137
- when :west then x -= 1
138
- when :north then y -= 1
139
- when :south then y += 1
140
- end
141
- if 0 <= y && y < aa.length &&
142
- 0 <= x && x < aa[y].length
143
- lambda { yield st.merge(:pos => [x,y]) }
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::Pathfinder::EmptyState
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
- raise KeyError, "key not found: #{k}"
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::Pathfinder::State.new(k, v, pairs)
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::Pathfinder::EmptyState>"
470
+ "\#<Tb::Search::EmptyState>"
455
471
  end
456
472
  end
457
473
 
458
- class Tb::Pathfinder::State
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
- raise KeyError, "key not found: #{k}"
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::Pathfinder::State.new(pairs.key, pairs.val, result)
568
+ result = Tb::Search::State.new(pairs.key, pairs.val, result)
533
569
  }
534
570
  h.reverse_each {|k, v|
535
- result = Tb::Pathfinder::State.new(k, v, result)
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::Pathfinder::EmptyState
550
- ary.reverse_each {|pairs|
551
- result = Tb::Pathfinder::State.new(pairs.key, pairs.val, result)
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?