tb 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +62 -50
- data/bin/tb +22 -18
- data/lib/tb.rb +35 -19
- data/lib/tb/basic.rb +85 -86
- data/lib/tb/catreader.rb +33 -116
- data/lib/tb/cmd_cat.rb +31 -27
- data/lib/tb/cmd_consecutive.rb +45 -35
- data/lib/tb/cmd_crop.rb +86 -52
- data/lib/tb/cmd_cross.rb +113 -71
- data/lib/tb/cmd_cut.rb +49 -44
- data/lib/tb/cmd_git_log.rb +193 -0
- data/lib/tb/cmd_grep.rb +43 -32
- data/lib/tb/cmd_group.rb +63 -39
- data/lib/tb/cmd_gsub.rb +53 -43
- data/lib/tb/cmd_help.rb +51 -24
- data/lib/tb/cmd_join.rb +32 -35
- data/lib/tb/cmd_ls.rb +233 -205
- data/lib/tb/cmd_mheader.rb +47 -37
- data/lib/tb/cmd_nest.rb +94 -0
- data/lib/tb/cmd_newfield.rb +29 -33
- data/lib/tb/cmd_rename.rb +40 -32
- data/lib/tb/cmd_shape.rb +31 -24
- data/lib/tb/cmd_sort.rb +46 -25
- data/lib/tb/cmd_svn_log.rb +47 -28
- data/lib/tb/cmd_tar_tvf.rb +447 -0
- data/lib/tb/cmd_to_csv.rb +60 -0
- data/lib/tb/cmd_to_json.rb +60 -0
- data/lib/tb/cmd_to_pnm.rb +48 -0
- data/lib/tb/cmd_to_pp.rb +71 -0
- data/lib/tb/cmd_to_tsv.rb +48 -0
- data/lib/tb/cmd_to_yaml.rb +52 -0
- data/lib/tb/cmd_unnest.rb +118 -0
- data/lib/tb/cmdmain.rb +24 -20
- data/lib/tb/cmdtop.rb +33 -25
- data/lib/tb/cmdutil.rb +26 -66
- data/lib/tb/csv.rb +46 -34
- data/lib/tb/enum.rb +294 -0
- data/lib/tb/enumerable.rb +198 -7
- data/lib/tb/enumerator.rb +73 -0
- data/lib/tb/fieldset.rb +27 -19
- data/lib/tb/fileenumerator.rb +365 -0
- data/lib/tb/json.rb +50 -0
- data/lib/tb/pager.rb +6 -6
- data/lib/tb/pairs.rb +227 -0
- data/lib/tb/pnm.rb +23 -22
- data/lib/tb/reader.rb +52 -49
- data/lib/tb/record.rb +48 -19
- data/lib/tb/revcmp.rb +38 -0
- data/lib/tb/ropen.rb +74 -57
- data/lib/tb/search.rb +25 -21
- data/lib/tb/tsv.rb +31 -34
- data/sample/excel2csv +24 -20
- data/sample/poi-xls2csv.rb +24 -20
- data/sample/poi-xls2csv.sh +22 -18
- data/sample/tbplot +185 -127
- data/test-all-cov.rb +3 -3
- data/test-all.rb +1 -1
- data/test/test_basic.rb +26 -10
- data/test/test_catreader.rb +7 -6
- data/test/test_cmd_cat.rb +32 -0
- data/test/test_cmd_consecutive.rb +10 -0
- data/test/test_cmd_crop.rb +4 -4
- data/test/test_cmd_cross.rb +16 -4
- data/test/test_cmd_git_log.rb +46 -0
- data/test/test_cmd_help.rb +17 -12
- data/test/test_cmd_join.rb +21 -1
- data/test/test_cmd_ls.rb +3 -4
- data/test/test_cmd_mheader.rb +17 -11
- data/test/test_cmd_nest.rb +49 -0
- data/test/test_cmd_sort.rb +15 -0
- data/test/test_cmd_tar_tvf.rb +281 -0
- data/test/{test_cmd_csv.rb → test_cmd_to_csv.rb} +35 -21
- data/test/{test_cmd_json.rb → test_cmd_to_json.rb} +31 -3
- data/test/{test_cmd_pnm.rb → test_cmd_to_pnm.rb} +2 -2
- data/test/{test_cmd_pp.rb → test_cmd_to_pp.rb} +4 -4
- data/test/{test_cmd_tsv.rb → test_cmd_to_tsv.rb} +4 -4
- data/test/{test_cmd_yaml.rb → test_cmd_to_yaml.rb} +3 -3
- data/test/test_cmd_unnest.rb +89 -0
- data/test/test_cmdtty.rb +19 -13
- data/test/test_enumerable.rb +83 -1
- data/test/test_fileenumerator.rb +265 -0
- data/test/test_json.rb +15 -0
- data/test/test_pager.rb +3 -4
- data/test/test_pairs.rb +122 -0
- data/test/test_pnm.rb +24 -24
- data/test/test_reader.rb +35 -13
- data/test/test_revcmp.rb +10 -0
- data/test/test_tbenum.rb +173 -0
- metadata +51 -23
- data/lib/tb/cmd_csv.rb +0 -42
- data/lib/tb/cmd_json.rb +0 -60
- data/lib/tb/cmd_pnm.rb +0 -43
- data/lib/tb/cmd_pp.rb +0 -70
- data/lib/tb/cmd_tsv.rb +0 -43
- data/lib/tb/cmd_yaml.rb +0 -47
data/lib/tb/cmd_sort.rb
CHANGED
@@ -1,36 +1,43 @@
|
|
1
|
-
# Copyright (C) 2011 Tanaka Akira <akr@fsij.org>
|
1
|
+
# Copyright (C) 2011-2012 Tanaka Akira <akr@fsij.org>
|
2
2
|
#
|
3
3
|
# Redistribution and use in source and binary forms, with or without
|
4
|
-
# modification, are permitted provided that the following conditions
|
4
|
+
# modification, are permitted provided that the following conditions
|
5
|
+
# are met:
|
5
6
|
#
|
6
|
-
# 1. Redistributions of source code must retain the above copyright
|
7
|
-
# list of conditions and the following disclaimer.
|
8
|
-
# 2. Redistributions in binary form must reproduce the above
|
9
|
-
# this list of conditions and the following
|
10
|
-
# and/or other materials provided
|
11
|
-
#
|
12
|
-
#
|
7
|
+
# 1. Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following
|
11
|
+
# disclaimer in the documentation and/or other materials provided
|
12
|
+
# with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote
|
14
|
+
# products derived from this software without specific prior
|
15
|
+
# written permission.
|
13
16
|
#
|
14
|
-
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
15
|
-
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
16
|
-
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
17
|
-
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# IN
|
23
|
-
# OF
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
18
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
23
|
+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
26
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
24
28
|
|
25
29
|
Tb::Cmd.subcommands << 'sort'
|
26
30
|
|
27
31
|
Tb::Cmd.default_option[:opt_sort_f] = nil
|
32
|
+
Tb::Cmd.default_option[:opt_sort_r] = nil
|
28
33
|
|
29
34
|
def (Tb::Cmd).op_sort
|
30
35
|
op = OptionParser.new
|
31
|
-
op.banner =
|
36
|
+
op.banner = "Usage: tb sort [OPTS] [TABLE]\n" +
|
37
|
+
"Sort rows."
|
32
38
|
define_common_option(op, "hNo", "--no-pager")
|
33
39
|
op.def_option('-f FIELD,...', 'specify sort keys') {|fs| Tb::Cmd.opt_sort_f = fs }
|
40
|
+
op.def_option('-r', '--reverse', 'reverse order') { Tb::Cmd.opt_sort_r = true }
|
34
41
|
op
|
35
42
|
end
|
36
43
|
|
@@ -43,15 +50,29 @@ def (Tb::Cmd).main_sort(argv)
|
|
43
50
|
else
|
44
51
|
fs = nil
|
45
52
|
end
|
46
|
-
|
53
|
+
creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
|
54
|
+
header = []
|
47
55
|
if fs
|
48
|
-
blk = lambda {|
|
56
|
+
blk = lambda {|pairs| fs.map {|f| smart_cmp_value(pairs[f]) } }
|
49
57
|
else
|
50
|
-
blk = lambda {|
|
58
|
+
blk = lambda {|pairs| header.map {|f| smart_cmp_value(pairs[f]) } }
|
51
59
|
end
|
52
|
-
|
60
|
+
if Tb::Cmd.opt_sort_r
|
61
|
+
blk1 = blk
|
62
|
+
blk = lambda {|pairs| Tb::RevCmp.new(blk1.call(pairs)) }
|
63
|
+
end
|
64
|
+
er = Tb::Enumerator.new {|y|
|
65
|
+
creader.with_cumulative_header {|header0|
|
66
|
+
if header0
|
67
|
+
y.set_header(header0)
|
68
|
+
end
|
69
|
+
}.each {|pairs, header1|
|
70
|
+
header = header1
|
71
|
+
y.yield pairs
|
72
|
+
}
|
73
|
+
}.extsort_by(&blk)
|
53
74
|
with_output {|out|
|
54
|
-
|
75
|
+
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
55
76
|
}
|
56
77
|
end
|
57
78
|
|
data/lib/tb/cmd_svn_log.rb
CHANGED
@@ -1,26 +1,30 @@
|
|
1
|
-
# Copyright (C) 2011 Tanaka Akira <akr@fsij.org>
|
1
|
+
# Copyright (C) 2011-2012 Tanaka Akira <akr@fsij.org>
|
2
2
|
#
|
3
3
|
# Redistribution and use in source and binary forms, with or without
|
4
|
-
# modification, are permitted provided that the following conditions
|
4
|
+
# modification, are permitted provided that the following conditions
|
5
|
+
# are met:
|
5
6
|
#
|
6
|
-
# 1. Redistributions of source code must retain the above copyright
|
7
|
-
# list of conditions and the following disclaimer.
|
8
|
-
# 2. Redistributions in binary form must reproduce the above
|
9
|
-
# this list of conditions and the following
|
10
|
-
# and/or other materials provided
|
11
|
-
#
|
12
|
-
#
|
7
|
+
# 1. Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following
|
11
|
+
# disclaimer in the documentation and/or other materials provided
|
12
|
+
# with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote
|
14
|
+
# products derived from this software without specific prior
|
15
|
+
# written permission.
|
13
16
|
#
|
14
|
-
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
15
|
-
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
16
|
-
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
17
|
-
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# IN
|
23
|
-
# OF
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
18
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
23
|
+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
26
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
24
28
|
|
25
29
|
require 'rexml/document'
|
26
30
|
|
@@ -31,16 +35,25 @@ Tb::Cmd.default_option[:opt_svn_log_xml] = nil
|
|
31
35
|
|
32
36
|
def (Tb::Cmd).op_svn_log
|
33
37
|
op = OptionParser.new
|
34
|
-
op.banner =
|
38
|
+
op.banner = "Usage: tb svn-log [OPTS] -- [SVN-LOG-ARGS]\n" +
|
39
|
+
"Show the SVN log as a table."
|
35
40
|
define_common_option(op, "hNo", "--no-pager")
|
36
41
|
op.def_option('--svn-command COMMAND', 'specify the svn command (default: svn)') {|command| Tb::Cmd.opt_svn_log_svn_command = command }
|
37
42
|
op.def_option('--svn-log-xml FILE', 'specify the result svn log --xml') {|filename| Tb::Cmd.opt_svn_log_xml = filename }
|
38
43
|
op
|
39
44
|
end
|
40
45
|
|
46
|
+
Tb::Cmd.def_vhelp('svn-log', <<'End')
|
47
|
+
Example:
|
48
|
+
|
49
|
+
% tb svn-log
|
50
|
+
% tb svn-log -- -v
|
51
|
+
% tb svn-log -- -v http://svn.ruby-lang.org/repos/ruby/trunk
|
52
|
+
End
|
53
|
+
|
41
54
|
class Tb::Cmd::SVNLOGListener
|
42
|
-
def initialize(
|
43
|
-
@
|
55
|
+
def initialize(y)
|
56
|
+
@y = y
|
44
57
|
@header = nil
|
45
58
|
@elt_stack = []
|
46
59
|
@att_stack = []
|
@@ -82,14 +95,17 @@ class Tb::Cmd::SVNLOGListener
|
|
82
95
|
else
|
83
96
|
@header = %w[rev author date msg]
|
84
97
|
end
|
85
|
-
@
|
98
|
+
@y.set_header @header
|
86
99
|
end
|
87
100
|
if @log['paths']
|
88
101
|
@log['paths'].each {|h|
|
89
|
-
|
102
|
+
assoc = @log.to_a.reject {|f, v| !%w[rev author date msg].include?(f) }
|
103
|
+
assoc += h.to_a.reject {|f, v| !%w[kind action path].include?(f) }
|
104
|
+
@y.yield Tb::Pairs.new(assoc)
|
90
105
|
}
|
91
106
|
else
|
92
|
-
|
107
|
+
assoc = @log.to_a.reject {|f, v| !%w[rev author date msg].include?(f) }
|
108
|
+
@y.yield Tb::Pairs.new(assoc)
|
93
109
|
end
|
94
110
|
@log = nil
|
95
111
|
end
|
@@ -139,7 +155,7 @@ def (Tb::Cmd).svn_log_with_svn_log(argv)
|
|
139
155
|
}
|
140
156
|
else
|
141
157
|
svn = Tb::Cmd.opt_svn_log_svn_command || 'svn'
|
142
|
-
IO.popen([
|
158
|
+
IO.popen([svn, 'log', '--xml', *argv]) {|f|
|
143
159
|
yield f
|
144
160
|
}
|
145
161
|
end
|
@@ -148,11 +164,14 @@ end
|
|
148
164
|
def (Tb::Cmd).main_svn_log(argv)
|
149
165
|
op_svn_log.parse!(argv)
|
150
166
|
exit_if_help('svn-log')
|
151
|
-
|
167
|
+
er = Tb::Enumerator.new {|y|
|
152
168
|
svn_log_with_svn_log(argv) {|f|
|
153
|
-
listener = Tb::Cmd::SVNLOGListener.new(
|
169
|
+
listener = Tb::Cmd::SVNLOGListener.new(y)
|
154
170
|
REXML::Parsers::StreamParser.new(f, listener).parse
|
155
171
|
}
|
156
172
|
}
|
173
|
+
with_output {|out|
|
174
|
+
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
175
|
+
}
|
157
176
|
end
|
158
177
|
|
@@ -0,0 +1,447 @@
|
|
1
|
+
# Copyright (C) 2012 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
|
5
|
+
# are met:
|
6
|
+
#
|
7
|
+
# 1. Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following
|
11
|
+
# disclaimer in the documentation and/or other materials provided
|
12
|
+
# with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote
|
14
|
+
# products derived from this software without specific prior
|
15
|
+
# written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
18
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
23
|
+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
26
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
Tb::Cmd.subcommands << 'tar-tvf'
|
30
|
+
|
31
|
+
Tb::Cmd.default_option[:opt_tar_tvf_l] = 0
|
32
|
+
Tb::Cmd.default_option[:opt_tar_tvf_ustar] = nil
|
33
|
+
|
34
|
+
def (Tb::Cmd).op_tar_tvf
|
35
|
+
op = OptionParser.new
|
36
|
+
op.banner = "Usage: tb tar-tvf [OPTS] [TAR-FILE ...]\n" +
|
37
|
+
"Show the file listing of tar file."
|
38
|
+
define_common_option(op, "hNo", "--no-pager")
|
39
|
+
op.def_option('-l', 'show more attributes.') {|fs| Tb::Cmd.opt_tar_tvf_l += 1 }
|
40
|
+
op.def_option('--ustar', 'ustar format (POSIX.1-1988). No GNU and POSIX.1-2001 extension.') {|fs| Tb::Cmd.opt_tar_tvf_ustar = true }
|
41
|
+
op
|
42
|
+
end
|
43
|
+
|
44
|
+
Tb::Cmd::TAR_RECORD_LENGTH = 512
|
45
|
+
Tb::Cmd::TAR_HEADER_STRUCTURE = [
|
46
|
+
[:name, "Z100"], # [POSIX] NUL-terminated character strings except when all characters in the array contain non-NUL characters including the last character.
|
47
|
+
[:mode, "A8"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
48
|
+
[:uid, "A8"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
49
|
+
[:gid, "A8"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
50
|
+
[:size, "A12"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
51
|
+
[:mtime, "A12"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
52
|
+
[:chksum, "A8"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
53
|
+
[:typeflag, "a1"], # [POSIX] a single character.
|
54
|
+
[:linkname, "Z100"], # [POSIX] NUL-terminated character strings except when all characters in the array contain non-NUL characters including the last character.
|
55
|
+
[:magic, "Z6"], # [POSIX] terminated by a NUL character.
|
56
|
+
[:version, "Z2"], # [POSIX] two octets containing the characters "00" (zero-zero)
|
57
|
+
[:uname, "Z32"], # [POSIX] terminated by a NUL character.
|
58
|
+
[:gname, "Z32"], # [POSIX] terminated by a NUL character.
|
59
|
+
[:devmajor, "A8"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
60
|
+
[:devminor, "A8"], # [POSIX] leading zero-filled octal numbers using digits which terminated by one or more <space> or NUL characters.
|
61
|
+
[:prefix, "Z155"], # [POSIX] NUL-terminated character strings except when all characters in the array contain non-NUL characters including the last character.
|
62
|
+
]
|
63
|
+
Tb::Cmd::TAR_HEADER_TEPMLATE = Tb::Cmd::TAR_HEADER_STRUCTURE.map {|n, t| t }.join('')
|
64
|
+
|
65
|
+
Tb::Cmd::TAR_TYPEFLAG = {
|
66
|
+
"\0" => :regular, # [POSIX] For backwards-compatibility.
|
67
|
+
'0' => :regular, # [POSIX]
|
68
|
+
'1' => :link, # [POSIX]
|
69
|
+
'2' => :symlink, # [POSIX]
|
70
|
+
'5' => :directory, # [POSIX]
|
71
|
+
'3' => :character_special, # [POSIX]
|
72
|
+
'4' => :block_special, # [POSIX]
|
73
|
+
'6' => :fifo, # [POSIX]
|
74
|
+
'7' => :contiguous, # [POSIX] Reserved for high-performance file. (It is come from "contiguous file" (S_IFCTG) of Masscomp?)
|
75
|
+
}
|
76
|
+
|
77
|
+
def (Tb::Cmd).tar_tvf_parse_seconds_from_epoch(val)
|
78
|
+
if /\./ =~ val
|
79
|
+
num = ($` + $').to_i
|
80
|
+
den = 10 ** $'.length
|
81
|
+
t = Rational(num, den)
|
82
|
+
begin
|
83
|
+
Time.at(t)
|
84
|
+
rescue TypeError
|
85
|
+
ti = t.floor
|
86
|
+
Time.at(ti, ((t-ti) * 1000000).floor)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
Time.at(val.to_i)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Tb::Cmd::TAR_PAX_KEYWORD_RECOGNIZERS = {
|
94
|
+
'atime' => [:atime, lambda {|val| Tb::Cmd.tar_tvf_parse_seconds_from_epoch(val) }],
|
95
|
+
'mtime' => [:mtime, lambda {|val| Tb::Cmd.tar_tvf_parse_seconds_from_epoch(val) }],
|
96
|
+
'ctime' => [:ctime, lambda {|val| Tb::Cmd.tar_tvf_parse_seconds_from_epoch(val) }],
|
97
|
+
'gid' => [:gid, lambda {|val| val.to_i }],
|
98
|
+
'gname' => [:gname, lambda {|val| val }],
|
99
|
+
'uid' => [:uid, lambda {|val| val.to_i }],
|
100
|
+
'uname' => [:uname, lambda {|val| val }],
|
101
|
+
'linkpath' => [:linkname, lambda {|val| val }],
|
102
|
+
'path' => [:path, lambda {|val| val }],
|
103
|
+
'size' => [:size, lambda {|val| val.to_i }],
|
104
|
+
}
|
105
|
+
|
106
|
+
Tb::Cmd::TAR_CSV_HEADER = %w[mode filemode uid user gid group devmajor devminor size mtime path linkname]
|
107
|
+
Tb::Cmd::TAR_CSV_LONG_HEADER = %w[mode filemode uid user gid group devmajor devminor size mtime atime ctime path linkname size_in_tar tar_typeflag tar_magic tar_version tar_chksum]
|
108
|
+
|
109
|
+
def (Tb::Cmd).tar_tvf_parse_header(header_record)
|
110
|
+
ary = header_record.unpack(Tb::Cmd::TAR_HEADER_TEPMLATE)
|
111
|
+
h = {}
|
112
|
+
Tb::Cmd::TAR_HEADER_STRUCTURE.each_with_index {|(k, _), i|
|
113
|
+
h[k] = ary[i]
|
114
|
+
}
|
115
|
+
[:mode, :uid, :gid, :size, :mtime, :chksum, :devmajor, :devminor].each {|k|
|
116
|
+
h[k] = h[k].to_i(8)
|
117
|
+
}
|
118
|
+
h[:mtime] = Time.at(h[:mtime])
|
119
|
+
if h[:prefix].empty?
|
120
|
+
h[:path] = h[:name]
|
121
|
+
else
|
122
|
+
h[:path] = h[:prefix] + '/' + h[:name]
|
123
|
+
end
|
124
|
+
header_record_for_chksum = header_record.dup
|
125
|
+
header_record_for_chksum[148, 8] = ' ' * 8
|
126
|
+
if header_record_for_chksum.sum(0) != h[:chksum]
|
127
|
+
warn "invalid checksum: #{h[:path].inspect}"
|
128
|
+
end
|
129
|
+
h
|
130
|
+
end
|
131
|
+
|
132
|
+
class Tb::Cmd::TarFormatError < StandardError
|
133
|
+
end
|
134
|
+
|
135
|
+
class Tb::Cmd::TarReader
|
136
|
+
def initialize(input)
|
137
|
+
@input = input
|
138
|
+
@offset = 0
|
139
|
+
end
|
140
|
+
attr_reader :offset
|
141
|
+
|
142
|
+
def get_single_record(kind)
|
143
|
+
record = @input.read(Tb::Cmd::TAR_RECORD_LENGTH)
|
144
|
+
if !record
|
145
|
+
return nil
|
146
|
+
end
|
147
|
+
if record.length != Tb::Cmd::TAR_RECORD_LENGTH
|
148
|
+
warn "premature end of tar archive (#{kind})"
|
149
|
+
raise Tb::Cmd::TarFormatError
|
150
|
+
end
|
151
|
+
@offset += Tb::Cmd::TAR_RECORD_LENGTH
|
152
|
+
record
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_single_record(kind)
|
156
|
+
record = get_single_record(kind)
|
157
|
+
if !record
|
158
|
+
warn "premature end of tar archive (#{kind})"
|
159
|
+
raise Tb::Cmd::TarFormatError
|
160
|
+
end
|
161
|
+
record
|
162
|
+
end
|
163
|
+
|
164
|
+
def read_exactly(size, kind)
|
165
|
+
record = @input.read(size)
|
166
|
+
if !record
|
167
|
+
warn "premature end of tar archive (#{kind})"
|
168
|
+
raise Tb::Cmd::TarFormatError
|
169
|
+
end
|
170
|
+
if record.length != size
|
171
|
+
warn "premature end of tar archive (#{kind})"
|
172
|
+
raise Tb::Cmd::TarFormatError
|
173
|
+
end
|
174
|
+
@offset += size
|
175
|
+
record
|
176
|
+
end
|
177
|
+
|
178
|
+
def skip(size, kind)
|
179
|
+
begin
|
180
|
+
@input.seek(size, IO::SEEK_CUR)
|
181
|
+
rescue Errno::ESPIPE
|
182
|
+
rest = size
|
183
|
+
while 0 < rest
|
184
|
+
if rest < 4096
|
185
|
+
s = rest
|
186
|
+
else
|
187
|
+
s = 4096
|
188
|
+
end
|
189
|
+
ret = @input.read(s)
|
190
|
+
if !ret || ret.length != s
|
191
|
+
warn "premature end of tar archive content (#{kind})"
|
192
|
+
raise Tb::Cmd::TarFormatError
|
193
|
+
end
|
194
|
+
rest -= s
|
195
|
+
end
|
196
|
+
end
|
197
|
+
@offset += size
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def (Tb::Cmd).tar_tvf_read_end_of_archive_indicator(reader)
|
202
|
+
# The end of archive indicator is two consecutive records of NULs.
|
203
|
+
# The first record is already read.
|
204
|
+
second_end_of_archive_indicator_record = reader.get_single_record("second record of the end of archive indicator")
|
205
|
+
if !second_end_of_archive_indicator_record
|
206
|
+
# some tarballs have only one record of NULs.
|
207
|
+
return
|
208
|
+
end
|
209
|
+
if /\A\0*\z/ !~ second_end_of_archive_indicator_record
|
210
|
+
warn "The second record of end of tar archive indicator is not zero"
|
211
|
+
raise Tb::Cmd::TarFormatError
|
212
|
+
end
|
213
|
+
# It is acceptable that there may be garbage after the end of tar
|
214
|
+
# archive indicator. ("ustar Interchange Format" in POSIX)
|
215
|
+
end
|
216
|
+
|
217
|
+
def (Tb::Cmd).tar_tvf_check_extension_record(reader, h, content_blocklength)
|
218
|
+
prefix_parameters = {}
|
219
|
+
case h[:typeflag]
|
220
|
+
when 'L' # GNU
|
221
|
+
content = reader.read_exactly(content_blocklength, 'GNU long file name')[0, h[:size]][/\A[^\0]*/]
|
222
|
+
prefix_parameters[:path] = content
|
223
|
+
return prefix_parameters
|
224
|
+
when 'K' # GNU
|
225
|
+
content = reader.read_exactly(content_blocklength, 'GNU long link name')[0, h[:size]][/\A[^\0]*/]
|
226
|
+
prefix_parameters[:linkname] = content
|
227
|
+
return prefix_parameters
|
228
|
+
when 'x' # pax (POSIX.1-2001)
|
229
|
+
content = reader.read_exactly(content_blocklength, 'pax Extended Header content')[0, h[:size]]
|
230
|
+
while /\A(\d+) / =~ content
|
231
|
+
lenlen = $&.length
|
232
|
+
len = $1.to_i
|
233
|
+
param = content[lenlen, len-lenlen]
|
234
|
+
content = content[len..-1]
|
235
|
+
if /\n\z/ =~ param
|
236
|
+
param.chomp!("\n")
|
237
|
+
else
|
238
|
+
warn "pax hearder record doesn't end with a newline: #{param.inspect}"
|
239
|
+
end
|
240
|
+
if /=/ !~ param
|
241
|
+
warn "pax hearder record doesn't contain a equal character: #{param.inspect}"
|
242
|
+
else
|
243
|
+
key = $`
|
244
|
+
val = $'
|
245
|
+
if Tb::Cmd::TAR_PAX_KEYWORD_RECOGNIZERS[key]
|
246
|
+
if val == ''
|
247
|
+
prefix_parameters[symkey] = nil
|
248
|
+
else
|
249
|
+
symkey, recognizer = Tb::Cmd::TAR_PAX_KEYWORD_RECOGNIZERS[key]
|
250
|
+
prefix_parameters[symkey] = recognizer.call(val)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
return prefix_parameters
|
256
|
+
end
|
257
|
+
nil
|
258
|
+
end
|
259
|
+
|
260
|
+
def (Tb::Cmd).tar_tvf_each(f)
|
261
|
+
offset = 0
|
262
|
+
reader = Tb::Cmd::TarReader.new(f)
|
263
|
+
prefix_parameters = {}
|
264
|
+
while true
|
265
|
+
header_record = reader.get_single_record("file header")
|
266
|
+
if !header_record
|
267
|
+
break
|
268
|
+
end
|
269
|
+
if /\A\0*\z/ =~ header_record
|
270
|
+
tar_tvf_read_end_of_archive_indicator(reader)
|
271
|
+
break
|
272
|
+
end
|
273
|
+
h = tar_tvf_parse_header(header_record)
|
274
|
+
content_numrecords = (h[:size] + Tb::Cmd::TAR_RECORD_LENGTH - 1) / Tb::Cmd::TAR_RECORD_LENGTH
|
275
|
+
content_blocklength = content_numrecords * Tb::Cmd::TAR_RECORD_LENGTH
|
276
|
+
if !Tb::Cmd.opt_tar_tvf_ustar
|
277
|
+
extension_params = tar_tvf_check_extension_record(reader, h, content_blocklength)
|
278
|
+
if extension_params
|
279
|
+
prefix_parameters.update extension_params
|
280
|
+
next
|
281
|
+
end
|
282
|
+
end
|
283
|
+
prefix_parameters.each {|k, v|
|
284
|
+
if v.nil?
|
285
|
+
h.delete k
|
286
|
+
else
|
287
|
+
h[k] = v
|
288
|
+
end
|
289
|
+
}
|
290
|
+
case Tb::Cmd::TAR_TYPEFLAG[h[:typeflag]]
|
291
|
+
when :link, :symlink, :directory, :character_special, :block_special, :fifo
|
292
|
+
# xxx: hardlink may have contents for posix archive.
|
293
|
+
else
|
294
|
+
reader.skip(content_blocklength, 'file content')
|
295
|
+
end
|
296
|
+
h[:size_in_tar] = reader.offset - offset
|
297
|
+
yield h
|
298
|
+
offset = reader.offset
|
299
|
+
prefix_parameters = {}
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def (Tb::Cmd).tar_tvf_open_with0(arg)
|
304
|
+
if arg == '-'
|
305
|
+
yield $stdin
|
306
|
+
else
|
307
|
+
open(arg, 'rb') {|f|
|
308
|
+
yield f
|
309
|
+
}
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def (Tb::Cmd).tar_tvf_open_with(arg)
|
314
|
+
tar_tvf_open_with0(arg) {|f|
|
315
|
+
magic = f.read(8)
|
316
|
+
case magic
|
317
|
+
when /\A\x1f\x8b/, /\A\037\235/ # \x1f\x8b is gzip format. \037\235 is "compress" format of old Unix.
|
318
|
+
decompression = ['gzip', '-dc']
|
319
|
+
when /\ABZh/
|
320
|
+
decompression = ['bzip2', '-dc']
|
321
|
+
when /\A\xFD7zXZ\x00/
|
322
|
+
decompression = ['xz', '-dc']
|
323
|
+
end
|
324
|
+
begin
|
325
|
+
f.rewind
|
326
|
+
seek_success = true
|
327
|
+
rescue Errno::ESPIPE
|
328
|
+
seek_success = false
|
329
|
+
end
|
330
|
+
# Ruby 1.9 dependent.
|
331
|
+
if decompression
|
332
|
+
if seek_success
|
333
|
+
IO.popen(decompression + [{:in => f}], 'rb') {|pipe|
|
334
|
+
yield pipe
|
335
|
+
}
|
336
|
+
else
|
337
|
+
IO.pipe {|r, w|
|
338
|
+
w.binmode
|
339
|
+
IO.popen(decompression + [{:in => r}], 'rb') {|pipe|
|
340
|
+
w << magic
|
341
|
+
th = Thread.new {
|
342
|
+
IO.copy_stream(f, w)
|
343
|
+
w.close
|
344
|
+
}
|
345
|
+
begin
|
346
|
+
yield pipe
|
347
|
+
ensure
|
348
|
+
th.join
|
349
|
+
end
|
350
|
+
}
|
351
|
+
}
|
352
|
+
end
|
353
|
+
else
|
354
|
+
if seek_success
|
355
|
+
yield f
|
356
|
+
else
|
357
|
+
IO.pipe {|r, w|
|
358
|
+
w.binmode
|
359
|
+
w << magic
|
360
|
+
th = Thread.new {
|
361
|
+
IO.copy_stream(f, w)
|
362
|
+
w.close
|
363
|
+
}
|
364
|
+
begin
|
365
|
+
yield r
|
366
|
+
ensure
|
367
|
+
th.join
|
368
|
+
end
|
369
|
+
}
|
370
|
+
end
|
371
|
+
end
|
372
|
+
}
|
373
|
+
end
|
374
|
+
|
375
|
+
def (Tb::Cmd).tar_tvf_format_filemode(typeflag, mode)
|
376
|
+
entry_type =
|
377
|
+
case Tb::Cmd::TAR_TYPEFLAG[typeflag]
|
378
|
+
when :regular then '-'
|
379
|
+
when :directory then 'd'
|
380
|
+
when :character_special then 'c'
|
381
|
+
when :block_special then 'b'
|
382
|
+
when :fifo then 'p'
|
383
|
+
when :symlink then 'l'
|
384
|
+
when :link then 'h'
|
385
|
+
when :contiguous then 'C'
|
386
|
+
else '?'
|
387
|
+
end
|
388
|
+
m = mode
|
389
|
+
sprintf("%s%c%c%c%c%c%c%c%c%c",
|
390
|
+
entry_type,
|
391
|
+
(m & 0400 == 0 ? ?- : ?r),
|
392
|
+
(m & 0200 == 0 ? ?- : ?w),
|
393
|
+
(m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
|
394
|
+
(m & 04000 == 0 ? ?x : ?s)),
|
395
|
+
(m & 0040 == 0 ? ?- : ?r),
|
396
|
+
(m & 0020 == 0 ? ?- : ?w),
|
397
|
+
(m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
|
398
|
+
(m & 02000 == 0 ? ?x : ?s)),
|
399
|
+
(m & 0004 == 0 ? ?- : ?r),
|
400
|
+
(m & 0002 == 0 ? ?- : ?w),
|
401
|
+
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
|
402
|
+
(m & 01000 == 0 ? ?x : ?t)))
|
403
|
+
end
|
404
|
+
|
405
|
+
def (Tb::Cmd).main_tar_tvf(argv)
|
406
|
+
op_tar_tvf.parse!(argv)
|
407
|
+
exit_if_help('tar-tvf')
|
408
|
+
argv = ['-'] if argv.empty?
|
409
|
+
er = Tb::Enumerator.new {|y|
|
410
|
+
if Tb::Cmd.opt_tar_tvf_l == 0
|
411
|
+
header = Tb::Cmd::TAR_CSV_HEADER
|
412
|
+
else
|
413
|
+
header = Tb::Cmd::TAR_CSV_LONG_HEADER
|
414
|
+
end
|
415
|
+
y.set_header header
|
416
|
+
argv.each {|filename|
|
417
|
+
tar_tvf_open_with(filename) {|f|
|
418
|
+
tar_tvf_each(f) {|h|
|
419
|
+
formatted = {}
|
420
|
+
formatted["mode"] = sprintf("0%o", h[:mode])
|
421
|
+
formatted["filemode"] = tar_tvf_format_filemode(h[:typeflag], h[:mode])
|
422
|
+
formatted["uid"] = h[:uid].to_s
|
423
|
+
formatted["gid"] = h[:gid].to_s
|
424
|
+
formatted["size"] = h[:size].to_s
|
425
|
+
formatted["mtime"] = h[:mtime].iso8601(0 < Tb::Cmd.opt_tar_tvf_l ? 9 : 0)
|
426
|
+
formatted["atime"] = h[:atime].iso8601(0 < Tb::Cmd.opt_tar_tvf_l ? 9 : 0) if h[:atime]
|
427
|
+
formatted["ctime"] = h[:ctime].iso8601(0 < Tb::Cmd.opt_tar_tvf_l ? 9 : 0) if h[:ctime]
|
428
|
+
formatted["user"] = h[:uname]
|
429
|
+
formatted["group"] = h[:gname]
|
430
|
+
formatted["devmajor"] = h[:devmajor].to_s
|
431
|
+
formatted["devminor"] = h[:devminor].to_s
|
432
|
+
formatted["path"] = h[:path]
|
433
|
+
formatted["linkname"] = h[:linkname]
|
434
|
+
formatted["size_in_tar"] = h[:size_in_tar]
|
435
|
+
formatted["tar_chksum"] = h[:chksum]
|
436
|
+
formatted["tar_typeflag"] = h[:typeflag]
|
437
|
+
formatted["tar_magic"] = h[:magic]
|
438
|
+
formatted["tar_version"] = h[:version]
|
439
|
+
y.yield Tb::Pairs.new(header.map {|f2| [f2, formatted[f2]] })
|
440
|
+
}
|
441
|
+
}
|
442
|
+
}
|
443
|
+
}
|
444
|
+
with_output {|out|
|
445
|
+
er.write_to_csv(out, !Tb::Cmd.opt_N)
|
446
|
+
}
|
447
|
+
end
|