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
@@ -0,0 +1,43 @@
1
+ # Copyright (C) 2011 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 are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ Tb::Cmd.subcommands << 'tsv'
26
+
27
+ def (Tb::Cmd).op_tsv
28
+ op = OptionParser.new
29
+ op.banner = 'Usage: tb tsv [OPTS] [TABLE]'
30
+ define_common_option(op, "hNo", "--no-pager")
31
+ op
32
+ end
33
+
34
+ def (Tb::Cmd).main_tsv(argv)
35
+ op_tsv.parse!(argv)
36
+ exit_if_help('tsv')
37
+ argv = ['-'] if argv.empty?
38
+ tbl = Tb::CatReader.open(argv, Tb::Cmd.opt_N) {|creader| build_table(creader) }
39
+ with_output {|out|
40
+ tbl_generate_tsv(tbl, out)
41
+ }
42
+ end
43
+
@@ -0,0 +1,47 @@
1
+ # Copyright (C) 2011 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 are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ Tb::Cmd.subcommands << 'yaml'
26
+
27
+ def (Tb::Cmd).op_yaml
28
+ op = OptionParser.new
29
+ op.banner = 'Usage: tb yaml [OPTS] [TABLE]'
30
+ define_common_option(op, "hNo", "--no-pager")
31
+ op
32
+ end
33
+
34
+ def (Tb::Cmd).main_yaml(argv)
35
+ require 'yaml'
36
+ op_yaml.parse!(argv)
37
+ exit_if_help('yaml')
38
+ argv = ['-'] if argv.empty?
39
+ tbl = Tb::CatReader.open(argv, Tb::Cmd.opt_N) {|creader| build_table(creader) }
40
+ ary = tbl.map {|rec| rec.to_h }
41
+ with_output {|out|
42
+ YAML.dump(ary, out)
43
+ out.puts
44
+ }
45
+ end
46
+
47
+
@@ -0,0 +1,45 @@
1
+ # Copyright (C) 2011 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 are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ def (Tb::Cmd).main_body(argv)
26
+ subcommand = argv.shift
27
+ if subcommand == '-h' || subcommand == '--help'
28
+ main_help(argv)
29
+ elsif Tb::Cmd.subcommands.include?(subcommand)
30
+ self.subcommand_send("main", subcommand, argv)
31
+ elsif subcommand == nil
32
+ usage_list_subcommands
33
+ true
34
+ else
35
+ err "unexpected subcommand: #{subcommand.inspect}"
36
+ end
37
+ end
38
+
39
+ def (Tb::Cmd).main(argv)
40
+ main_body(argv)
41
+ rescue SystemExit
42
+ STDERR.puts $!.message if $!.message != 'exit'
43
+ raise
44
+ end
45
+
@@ -0,0 +1,58 @@
1
+ # Copyright (C) 2011 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 are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ require 'tb'
26
+ require 'optparse'
27
+ require 'pathname'
28
+ require 'etc'
29
+ require 'time'
30
+ require 'enumerator'
31
+ require 'tb/pager'
32
+ require 'tb/cmdutil'
33
+ require 'tb/cmd_help'
34
+ require 'tb/cmd_csv'
35
+ require 'tb/cmd_tsv'
36
+ require 'tb/cmd_pnm'
37
+ require 'tb/cmd_json'
38
+ require 'tb/cmd_yaml'
39
+ require 'tb/cmd_pp'
40
+ require 'tb/cmd_grep'
41
+ require 'tb/cmd_gsub'
42
+ require 'tb/cmd_sort'
43
+ require 'tb/cmd_cut'
44
+ require 'tb/cmd_rename'
45
+ require 'tb/cmd_newfield'
46
+ require 'tb/cmd_cat'
47
+ require 'tb/cmd_join'
48
+ require 'tb/cmd_consecutive'
49
+ require 'tb/cmd_group'
50
+ require 'tb/cmd_cross'
51
+ require 'tb/cmd_shape'
52
+ require 'tb/cmd_mheader'
53
+ require 'tb/cmd_crop'
54
+ require 'tb/cmd_ls'
55
+ require 'tb/cmd_svn_log'
56
+ require 'tb/cmdmain'
57
+
58
+ Tb::Cmd.init_option
@@ -0,0 +1,327 @@
1
+ # Copyright (C) 2011 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 are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice, this
7
+ # list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ # 3. The name of the author may not be used to endorse or promote products
12
+ # derived from this software without specific prior written permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
23
+ # OF SUCH DAMAGE.
24
+
25
+ class Tb::Cmd
26
+ @subcommands = []
27
+
28
+ @default_option = {
29
+ :opt_help => 0,
30
+ :opt_N => nil,
31
+ :opt_debug => 0,
32
+ :opt_no_pager => nil,
33
+ :opt_output => nil,
34
+ }
35
+
36
+ def self.reset_option
37
+ @default_option.each {|k, v|
38
+ instance_variable_set("@#{k}", Marshal.load(Marshal.dump(v)))
39
+ }
40
+ end
41
+
42
+ def self.init_option
43
+ class << Tb::Cmd
44
+ Tb::Cmd.default_option.each {|k, v|
45
+ attr_accessor k
46
+ }
47
+ end
48
+ reset_option
49
+ end
50
+
51
+ def self.define_common_option(op, short_opts, *long_opts)
52
+ if short_opts.include? "h"
53
+ op.def_option('-h', '--help', 'show help message (-hh for verbose help)') { Tb::Cmd.opt_help += 1 }
54
+ end
55
+ if short_opts.include? "N"
56
+ op.def_option('-N', 'use numeric field name') { Tb::Cmd.opt_N = true }
57
+ end
58
+ if short_opts.include? "o"
59
+ op.def_option('-o filename', 'output to specified filename') {|filename| Tb::Cmd.opt_output = filename }
60
+ end
61
+ if long_opts.include? "--no-pager"
62
+ op.def_option('--no-pager', 'don\'t use pager') { Tb::Cmd.opt_no_pager = true }
63
+ end
64
+ opts = []
65
+ opts << '-d' if short_opts.include?('d')
66
+ opts << '--debug' if long_opts.include?('--debug')
67
+ if !opts.empty?
68
+ op.def_option(*(opts + ['show debug message'])) { Tb::Cmd.opt_debug += 1 }
69
+ end
70
+ end
71
+
72
+ @verbose_help = {}
73
+ def self.def_vhelp(subcommand, str)
74
+ if @verbose_help[subcommand]
75
+ raise ArgumentError, "verbose_help[#{subcommand.dump}] already defined."
76
+ end
77
+ @verbose_help[subcommand] = str
78
+ end
79
+
80
+ def self.subcommand_send(prefix, subcommand, *args, &block)
81
+ self.send(prefix + "_" + subcommand.gsub(/-/, '_'), *args, &block)
82
+ end
83
+ end
84
+
85
+ class << Tb::Cmd
86
+ attr_reader :subcommands
87
+ attr_reader :default_option
88
+ attr_reader :verbose_help
89
+ end
90
+
91
+ def err(msg)
92
+ raise SystemExit.new(1, msg)
93
+ end
94
+
95
+ def smart_cmp_value(v)
96
+ case v
97
+ when nil
98
+ []
99
+ when Numeric
100
+ [0, v]
101
+ when String
102
+ if v.respond_to? :force_encoding
103
+ v = v.dup.force_encoding("ASCII-8BIT")
104
+ end
105
+ case v
106
+ when /\A\s*-?\d+\s*\z/
107
+ [0, v.to_i(10)]
108
+ when /\A\s*-?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?\s*\z/
109
+ [0, Float(v)]
110
+ else
111
+ a = []
112
+ v.scan(/(\d+)|\D+/) {
113
+ if $1
114
+ a << 0 << $1.to_i
115
+ else
116
+ a << 1 << $&
117
+ end
118
+ }
119
+ a
120
+ end
121
+ else
122
+ raise ArgumentError, "unexpected: #{v.inspect}"
123
+ end
124
+ end
125
+
126
+ def conv_to_numeric(v)
127
+ v = v.strip
128
+ if /\A-?\d+\z/ =~ v
129
+ v = v.to_i
130
+ elsif /\A-?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?\z/ =~ v
131
+ v = v.to_f
132
+ else
133
+ raise ArgumentError, "number string expected: #{v.inspect}"
134
+ end
135
+ v
136
+ end
137
+
138
+ class CountAggregator
139
+ def initialize() @result = 0 end
140
+ def update(v) @result += 1 end
141
+ def finish() @result end
142
+ end
143
+
144
+ class SumAggregator
145
+ def initialize() @result = 0 end
146
+ def update(v) @result += conv_to_numeric(v) if !(v.nil? || v == '') end
147
+ def finish() @result end
148
+ end
149
+
150
+ class AvgAggregator
151
+ def initialize() @sum = 0; @count = 0 end
152
+ def update(v) @count += 1; @sum += conv_to_numeric(v) if !(v.nil? || v == '') end
153
+ def finish() @sum / @count.to_f end
154
+ end
155
+
156
+ class MaxAggregator
157
+ def initialize() @v = nil; @cmp = nil end
158
+ def update(v)
159
+ cmp = smart_cmp_value(v)
160
+ if @cmp == nil
161
+ @v, @cmp = v, cmp
162
+ else
163
+ @v, @cmp = v, cmp if (@cmp <=> cmp) < 0
164
+ end
165
+ end
166
+ def finish() @v end
167
+ end
168
+
169
+ class MinAggregator
170
+ def initialize() @v = @cmp = nil end
171
+ def update(v)
172
+ cmp = smart_cmp_value(v)
173
+ if @cmp == nil
174
+ @v, @cmp = v, cmp
175
+ else
176
+ @v, @cmp = v, cmp if (@cmp <=> cmp) > 0
177
+ end
178
+ end
179
+ def finish() @v end
180
+ end
181
+
182
+ class ValuesAggregator
183
+ def initialize() @result = [] end
184
+ def update(v) @result << v if v end
185
+ def finish() @result.join(",") end
186
+ end
187
+
188
+ class UniqueValuesAggregator
189
+ def initialize() @result = [] end
190
+ def update(v) @result << v if v end
191
+ def finish() @result.uniq.join(",") end
192
+ end
193
+
194
+ class Selector
195
+ def initialize(i, aggregator) @i = i; @agg = aggregator end
196
+ def update(ary) @agg.update(ary[@i]) end
197
+ def finish() @agg.finish end
198
+ end
199
+
200
+ def make_aggregator(spec, fs)
201
+ case spec
202
+ when 'count'
203
+ CountAggregator.new
204
+ when /\Asum\((.*)\)\z/
205
+ field = $1
206
+ i = fs.index(field)
207
+ raise ArgumentError, "field not found: #{field.inspect}" if !i
208
+ Selector.new(i, SumAggregator.new)
209
+ when /\Aavg\((.*)\)\z/
210
+ field = $1
211
+ i = fs.index(field)
212
+ raise ArgumentError, "field not found: #{field.inspect}" if !i
213
+ Selector.new(i, AvgAggregator.new)
214
+ when /\Amax\((.*)\)\z/
215
+ field = $1
216
+ i = fs.index(field)
217
+ raise ArgumentError, "field not found: #{field.inspect}" if !i
218
+ Selector.new(i, MaxAggregator.new)
219
+ when /\Amin\((.*)\)\z/
220
+ field = $1
221
+ i = fs.index(field)
222
+ raise ArgumentError, "field not found: #{field.inspect}" if !i
223
+ Selector.new(i, MinAggregator.new)
224
+ when /\Avalues\((.*)\)\z/
225
+ field = $1
226
+ i = fs.index(field)
227
+ raise ArgumentError, "field not found: #{field.inspect}" if !i
228
+ Selector.new(i, ValuesAggregator.new)
229
+ when /\Auniquevalues\((.*)\)\z/
230
+ field = $1
231
+ i = fs.index(field)
232
+ raise ArgumentError, "field not found: #{field.inspect}" if !i
233
+ Selector.new(i, UniqueValuesAggregator.new)
234
+ else
235
+ raise ArgumentError, "unexpected aggregation spec: #{spec.inspect}"
236
+ end
237
+ end
238
+
239
+ def split_field_list_argument(arg)
240
+ split_csv_argument(arg).map {|f| f || '' }
241
+ end
242
+
243
+ def split_csv_argument(arg)
244
+ Tb.csv_stream_input(arg) {|ary| return ary }
245
+ return []
246
+ end
247
+
248
+ def build_table(tblreader)
249
+ arys = []
250
+ tblreader.each {|ary|
251
+ arys << ary
252
+ }
253
+ header = tblreader.header
254
+ tbl = Tb.new(header)
255
+ arys.each {|ary|
256
+ ary << nil while ary.length < header.length
257
+ tbl.insert_values header, ary
258
+ }
259
+ tbl
260
+ end
261
+
262
+ def load_table(filename)
263
+ tablereader_open(filename) {|tblreader|
264
+ build_table(tblreader)
265
+ }
266
+ end
267
+
268
+ def tablereader_open(filename, &b)
269
+ Tb::Reader.open(filename, {:numeric=>Tb::Cmd.opt_N}, &b)
270
+ end
271
+
272
+ def with_table_stream_output
273
+ with_output {|out|
274
+ Tb.csv_stream_output(out) {|gen|
275
+ def gen.output_header(header)
276
+ self << header if !Tb::Cmd.opt_N
277
+ end
278
+ yield gen
279
+ }
280
+ }
281
+ end
282
+
283
+ def tbl_generate_csv(tbl, out)
284
+ if Tb::Cmd.opt_N
285
+ header = tbl.list_fields
286
+ Tb.csv_stream_output(out) {|gen|
287
+ tbl.each {|rec|
288
+ gen << rec.values_at(*header)
289
+ }
290
+ }
291
+ else
292
+ tbl.generate_csv(out)
293
+ end
294
+ end
295
+
296
+ def tbl_generate_tsv(tbl, out)
297
+ if Tb::Cmd.opt_N
298
+ header = tbl.list_fields
299
+ Tb.tsv_stream_output(out) {|gen|
300
+ tbl.each {|rec|
301
+ gen << rec.values_at(*header)
302
+ }
303
+ }
304
+ else
305
+ tbl.generate_tsv(out)
306
+ end
307
+ end
308
+
309
+ def with_output
310
+ if Tb::Cmd.opt_output
311
+ tmp = Tb::Cmd.opt_output + ".part"
312
+ begin
313
+ File.open(tmp, 'w') {|f|
314
+ yield f
315
+ }
316
+ File.rename tmp, Tb::Cmd.opt_output
317
+ ensure
318
+ File.unlink tmp if File.exist? tmp
319
+ end
320
+ elsif STDOUT.tty? && !Tb::Cmd.opt_no_pager
321
+ Tb::Pager.open {|pager|
322
+ yield pager
323
+ }
324
+ else
325
+ yield STDOUT
326
+ end
327
+ end