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,81 @@
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 << 'join'
26
+
27
+ Tb::Cmd.default_option[:opt_join_outer_missing] = nil
28
+ Tb::Cmd.default_option[:opt_join_retain_left] = nil
29
+ Tb::Cmd.default_option[:opt_join_retain_right] = nil
30
+
31
+ def (Tb::Cmd).op_join
32
+ op = OptionParser.new
33
+ op.banner = 'Usage: tb join [OPTS] [TABLE1 TABLE2 ...]'
34
+ define_common_option(op, 'hNod', '--no-pager', '--debug')
35
+ op.def_option('--outer', 'outer join') {
36
+ Tb::Cmd.opt_join_retain_left = true
37
+ Tb::Cmd.opt_join_retain_right = true
38
+ }
39
+ op.def_option('--left', 'left outer join') {
40
+ Tb::Cmd.opt_join_retain_left = true
41
+ Tb::Cmd.opt_join_retain_right = false
42
+ }
43
+ op.def_option('--right', 'right outer join') {
44
+ Tb::Cmd.opt_join_retain_left = false
45
+ Tb::Cmd.opt_join_retain_right = true
46
+ }
47
+ op.def_option('--outer-missing=DEFAULT', 'missing value for outer join') {|missing|
48
+ if Tb::Cmd.opt_join_retain_left == nil
49
+ Tb::Cmd.opt_join_retain_left = true
50
+ Tb::Cmd.opt_join_retain_right = true
51
+ end
52
+ Tb::Cmd.opt_join_outer_missing = missing
53
+ }
54
+ op
55
+ end
56
+
57
+ def (Tb::Cmd).main_join(argv)
58
+ op_join.parse!(argv)
59
+ exit_if_help('join')
60
+ retain_left = Tb::Cmd.opt_join_retain_left
61
+ retain_right = Tb::Cmd.opt_join_retain_right
62
+ err('two tables required at least.') if argv.length < 2
63
+ result = load_table(argv.shift)
64
+ if retain_left || retain_right
65
+ argv.each {|filename|
66
+ tbl = load_table(filename)
67
+ STDERR.puts "shared keys: #{(result.list_fields & tbl.list_fields).inspect}" if 1 <= Tb::Cmd.opt_debug
68
+ result = result.natjoin2_outer(tbl, Tb::Cmd.opt_join_outer_missing, retain_left, retain_right)
69
+ }
70
+ else
71
+ argv.each {|filename|
72
+ tbl = load_table(filename)
73
+ STDERR.puts "shared keys: #{(result.list_fields & tbl.list_fields).inspect}" if 1 <= Tb::Cmd.opt_debug
74
+ result = result.natjoin2(tbl)
75
+ }
76
+ end
77
+ with_output {|out|
78
+ tbl_generate_csv(result, out)
79
+ }
80
+ end
81
+
@@ -0,0 +1,60 @@
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 << 'json'
26
+
27
+ def (Tb::Cmd).op_json
28
+ op = OptionParser.new
29
+ op.banner = 'Usage: tb json [OPTS] [TABLE]'
30
+ define_common_option(op, "hNo", "--no-pager")
31
+ op
32
+ end
33
+
34
+ def (Tb::Cmd).main_json(argv)
35
+ require 'json'
36
+ op_json.parse!(argv)
37
+ exit_if_help('json')
38
+ argv = ['-'] if argv.empty?
39
+ with_output {|out|
40
+ out.print "["
41
+ sep = nil
42
+ argv.each {|filename|
43
+ sep = ",\n\n" if sep
44
+ tablereader_open(filename) {|tblreader|
45
+ tblreader.each {|ary|
46
+ out.print sep if sep
47
+ header = tblreader.header
48
+ h = {}
49
+ ary.each_with_index {|e, i|
50
+ h[header[i]] = e if !e.nil?
51
+ }
52
+ out.print JSON.pretty_generate(h)
53
+ sep = ",\n"
54
+ }
55
+ }
56
+ }
57
+ out.puts "]"
58
+ }
59
+ end
60
+
@@ -0,0 +1,273 @@
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 << 'ls'
26
+
27
+ Tb::Cmd.default_option[:opt_ls_a] = nil
28
+ Tb::Cmd.default_option[:opt_ls_A] = nil
29
+ Tb::Cmd.default_option[:opt_ls_l] = 0
30
+ Tb::Cmd.default_option[:opt_ls_R] = nil
31
+
32
+ def (Tb::Cmd).op_ls
33
+ op = OptionParser.new
34
+ op.banner = 'Usage: tb ls [OPTS] [FILE ...]'
35
+ define_common_option(op, "hNo", "--no-pager")
36
+ op.def_option('-a', 'don\'t ignore filenames beginning with a period.') {|fs| Tb::Cmd.opt_ls_a = true }
37
+ op.def_option('-A', 'don\'t ignore filenames beginning with a period, except "." and "..".') {|fs| Tb::Cmd.opt_ls_A = true }
38
+ op.def_option('-l', 'show attributes. -ll for more attributes.') {|fs| Tb::Cmd.opt_ls_l += 1 }
39
+ op.def_option('-R', 'recursive.') {|fs| Tb::Cmd.opt_ls_R = true }
40
+ op
41
+ end
42
+
43
+ def (Tb::Cmd).main_ls(argv)
44
+ op_ls.parse!(argv)
45
+ exit_if_help('ls')
46
+ argv = ['.'] if argv.empty?
47
+ @fail = false
48
+ with_table_stream_output {|gen|
49
+ if Tb::Cmd.opt_ls_l == 0
50
+ gen.output_header ['filename'] # don't generate the header when -N.
51
+ else
52
+ gen.output_header(ls_long_header()) # don't generate the header when -N.
53
+ end
54
+ argv.each {|arg|
55
+ ls_run(gen, Pathname(real_pathname_string(arg)))
56
+ }
57
+ }
58
+ if @fail
59
+ exit false
60
+ end
61
+ end
62
+
63
+ def (Tb::Cmd).ls_run(gen, path)
64
+ st = ls_get_stat(path)
65
+ return if !st
66
+ if st.directory?
67
+ ls_dir(gen, path, st)
68
+ else
69
+ ls_file(gen, path, st)
70
+ end
71
+ end
72
+
73
+ def (Tb::Cmd).ls_dir(gen, dir, st)
74
+ begin
75
+ entries = Dir.entries(dir)
76
+ rescue SystemCallError
77
+ @fail = true
78
+ warn "tb: #{$!}: #{dir}"
79
+ return
80
+ end
81
+ entries.map! {|filename| real_pathname_string(filename) }
82
+ entries = entries.sort_by {|filename| smart_cmp_value(filename) }
83
+ if Tb::Cmd.opt_ls_a || Tb::Cmd.opt_ls_A
84
+ entries1, entries2 = entries.partition {|filename| /\A\./ =~ filename }
85
+ entries0, entries1 = entries1.partition {|filename| filename == '.' || filename == '..' }
86
+ entries0.sort!
87
+ if Tb::Cmd.opt_ls_A
88
+ entries = entries1 + entries2
89
+ else
90
+ entries = entries0 + entries1 + entries2
91
+ end
92
+ else
93
+ entries.reject! {|filename| /\A\./ =~ filename }
94
+ end
95
+ if !Tb::Cmd.opt_ls_R
96
+ entries.each {|filename|
97
+ ls_file(gen, dir + filename, nil)
98
+ }
99
+ else
100
+ dirs = []
101
+ entries.each {|filename|
102
+ path = dir + filename
103
+ st2 = ls_get_stat(path)
104
+ next if !st2
105
+ if filename == '.' || filename == '..'
106
+ if dir.to_s != '.'
107
+ path = Pathname(dir.to_s + "/" + filename)
108
+ end
109
+ ls_file(gen, path, st2)
110
+ elsif st2.directory?
111
+ dirs << [path, st2]
112
+ else
113
+ ls_file(gen, path, st2)
114
+ end
115
+ }
116
+ dirs.each {|path, st2|
117
+ ls_file(gen, path, st2)
118
+ ls_dir(gen, path, st2)
119
+ }
120
+ end
121
+ end
122
+
123
+ def (Tb::Cmd).ls_file(gen, path, st)
124
+ if 0 < Tb::Cmd.opt_ls_l
125
+ if !st
126
+ st = ls_get_stat(path)
127
+ return if !st
128
+ end
129
+ gen << ls_long_info(path, st)
130
+ else
131
+ gen << [path.to_s]
132
+ end
133
+ end
134
+
135
+ def (Tb::Cmd).ls_get_stat(path)
136
+ begin
137
+ st = path.lstat
138
+ rescue SystemCallError
139
+ @fail = true
140
+ warn "tb: #{$!}: #{path}"
141
+ return nil
142
+ end
143
+ st
144
+ end
145
+
146
+ def (Tb::Cmd).ls_long_header
147
+ if 1 < Tb::Cmd.opt_ls_l
148
+ %w[dev ino mode filemode nlink uid user gid group rdev size blksize blocks atime mtime ctime filename symlink]
149
+ else
150
+ %w[filemode nlink user group size mtime filename symlink]
151
+ end
152
+ end
153
+
154
+ def (Tb::Cmd).ls_long_info(path, st)
155
+ ls_long_header.map {|info_type|
156
+ self.send("ls_info_#{info_type}", path, st)
157
+ }
158
+ end
159
+
160
+ def (Tb::Cmd).ls_info_dev(path, st) sprintf("0x%x", st.dev) end
161
+ def (Tb::Cmd).ls_info_ino(path, st) st.ino end
162
+ def (Tb::Cmd).ls_info_mode(path, st) sprintf("0%o", st.mode) end
163
+ def (Tb::Cmd).ls_info_nlink(path, st) st.nlink end
164
+ def (Tb::Cmd).ls_info_uid(path, st) st.uid end
165
+ def (Tb::Cmd).ls_info_gid(path, st) st.gid end
166
+ def (Tb::Cmd).ls_info_rdev(path, st) sprintf("0x%x", st.rdev) end
167
+ def (Tb::Cmd).ls_info_size(path, st) st.size end
168
+ def (Tb::Cmd).ls_info_blksize(path, st) st.blksize end
169
+ def (Tb::Cmd).ls_info_blocks(path, st) st.blocks end
170
+
171
+ def (Tb::Cmd).ls_info_filemode(path, st)
172
+ entry_type =
173
+ case st.ftype
174
+ when "file" then '-'
175
+ when "directory" then 'd'
176
+ when "characterSpecial" then 'c'
177
+ when "blockSpecial" then 'b'
178
+ when "fifo" then 'p'
179
+ when "link" then 'l'
180
+ when "socket" then 's'
181
+ when "unknown" then '?'
182
+ else '?'
183
+ end
184
+ m = st.mode
185
+ sprintf("%s%c%c%c%c%c%c%c%c%c",
186
+ entry_type,
187
+ (m & 0400 == 0 ? ?- : ?r),
188
+ (m & 0200 == 0 ? ?- : ?w),
189
+ (m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
190
+ (m & 04000 == 0 ? ?x : ?s)),
191
+ (m & 0040 == 0 ? ?- : ?r),
192
+ (m & 0020 == 0 ? ?- : ?w),
193
+ (m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
194
+ (m & 02000 == 0 ? ?x : ?s)),
195
+ (m & 0004 == 0 ? ?- : ?r),
196
+ (m & 0002 == 0 ? ?- : ?w),
197
+ (m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
198
+ (m & 01000 == 0 ? ?x : ?t)))
199
+ end
200
+
201
+ def (Tb::Cmd).ls_info_user(path, st)
202
+ uid = st.uid
203
+ begin
204
+ pw = Etc.getpwuid(uid)
205
+ rescue ArgumentError
206
+ end
207
+ if pw
208
+ pw.name
209
+ else
210
+ uid
211
+ end
212
+ end
213
+
214
+ def (Tb::Cmd).ls_info_group(path, st)
215
+ gid = st.gid
216
+ begin
217
+ gr = Etc.getgrgid(gid)
218
+ rescue ArgumentError
219
+ end
220
+ if gr
221
+ gr.name
222
+ else
223
+ gid
224
+ end
225
+ end
226
+
227
+ def (Tb::Cmd).ls_info_atime(path, st)
228
+ if 1 < Tb::Cmd.opt_ls_l
229
+ st.atime.iso8601(9)
230
+ else
231
+ st.atime.iso8601
232
+ end
233
+ end
234
+
235
+ def (Tb::Cmd).ls_info_mtime(path, st)
236
+ if 1 < Tb::Cmd.opt_ls_l
237
+ st.mtime.iso8601(9)
238
+ else
239
+ st.mtime.iso8601
240
+ end
241
+ end
242
+
243
+ def (Tb::Cmd).ls_info_ctime(path, st)
244
+ if 1 < Tb::Cmd.opt_ls_l
245
+ st.ctime.iso8601(9)
246
+ else
247
+ st.ctime.iso8601
248
+ end
249
+ end
250
+
251
+ def (Tb::Cmd).ls_info_filename(path, st)
252
+ path
253
+ end
254
+
255
+ def (Tb::Cmd).ls_info_symlink(path, st)
256
+ return nil if !st.symlink?
257
+ begin
258
+ File.readlink(path)
259
+ rescue SystemCallError
260
+ @fail = true
261
+ warn "tb: #{$!}: #{path}"
262
+ return nil
263
+ end
264
+ end
265
+
266
+ def (Tb::Cmd).real_pathname_string(str)
267
+ if str.respond_to? :force_encoding
268
+ # pathname is a sequence of bytes on Unix.
269
+ str.dup.force_encoding("ASCII-8BIT")
270
+ else
271
+ str
272
+ end
273
+ end
@@ -0,0 +1,77 @@
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 << 'mheader'
26
+
27
+ Tb::Cmd.default_option[:opt_mheader_count] = nil
28
+
29
+ def (Tb::Cmd).op_mheader
30
+ op = OptionParser.new
31
+ op.banner = 'Usage: tb mheader [OPTS] [TABLE]'
32
+ define_common_option(op, "ho", "--no-pager")
33
+ op.def_option('-c N', 'number of header records') {|arg| Tb::Cmd.opt_mheader_count = arg.to_i }
34
+ op
35
+ end
36
+
37
+ def (Tb::Cmd).main_mheader(argv)
38
+ op_mheader.parse!(argv)
39
+ exit_if_help('mheader')
40
+ argv = ['-'] if argv.empty?
41
+ header = []
42
+ if Tb::Cmd.opt_mheader_count
43
+ c = Tb::Cmd.opt_mheader_count
44
+ header_end_p = lambda {
45
+ c -= 1
46
+ c == 0 ? header.map {|a| a.compact.join(' ').strip } : nil
47
+ }
48
+ else
49
+ header_end_p = lambda {
50
+ h2 = header.map {|a| a.compact.join(' ').strip }.uniq
51
+ header.length == h2.length ? h2 : nil
52
+ }
53
+ end
54
+ with_table_stream_output {|gen|
55
+ Tb::CatReader.open(argv, true) {|tblreader|
56
+ tblreader.each {|ary|
57
+ if header
58
+ ary.each_with_index {|v,i|
59
+ header[i] ||= []
60
+ header[i] << v if header[i].empty? || header[i].last != v
61
+ }
62
+ h2 = header_end_p.call
63
+ if h2
64
+ gen << h2
65
+ header = nil
66
+ end
67
+ else
68
+ gen << ary
69
+ end
70
+ }
71
+ }
72
+ }
73
+ if header
74
+ warn "no header found."
75
+ end
76
+ end
77
+