tb 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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?