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,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
+