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