Top Level Namespace

Defined Under Namespace

Modules: Enumerable Classes: Tb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.decode_a1_addressing_col(str) ⇒ Object



42
43
44
# File 'lib/tb/cmd_crop.rb', line 42

def (Tb::Cmd).decode_a1_addressing_col(str)
  (26**str.length-1)/25+str.tr("A-Z", "0-9A-P").to_i(26)
end

.exit_if_help(subcommand) ⇒ Object



86
87
88
89
90
91
# File 'lib/tb/cmd_help.rb', line 86

def (Tb::Cmd).exit_if_help(subcommand)
  if 0 < Tb::Cmd.opt_help
    show_help(subcommand)
    exit
  end
end

.git_each_commit(f) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/tb/cmd_git.rb', line 183

def (Tb::Cmd).git_each_commit(f)
  while chunk = f.gets("\x01commit-separator\x01\n")
    chunk.chomp!("\x01commit-separator\x01\n")
    next if chunk.empty? # beginning of the output
    if /\nend-commit\n/ !~ chunk
      warn "unexpected git output (end-commit): #{chunk.inspect}"
      next
    end
    commit_info, files = $`, $'
    files.sub!(/\A\n/, '')
    h = git_parse_commit(commit_info, files)
    yield h
  end

end

.git_parse_commit(commit_info, files) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/tb/cmd_git.rb', line 134

def (Tb::Cmd).git_parse_commit(commit_info, files)
  commit_info = commit_info.split(/\n(?=[a-z])/)
  files_raw = {}
  files_numstat = {}
  files.split(/\n/).each {|file_line|
    if /\A:(\d+) (\d+) ([0-9a-f]+) ([0-9a-f]+) (\S+)\t(.+)\z/ =~ file_line
      mode1, mode2, hash1, hash2, status, filename = $1, $2, $3, $4, $5, $6
      filename = git_unescape_filename(filename)
      files_raw[filename] = [mode1, mode2, hash1, hash2, status]
    elsif /\A(\d+|-)\t(\d+|-)\t(.+)\z/ =~ file_line
      add, del, filename = $1, $2, $3
      add = add == '-' ? nil : add.to_i
      del = del == '-' ? nil : del.to_i
      filename = git_unescape_filename(filename)
      files_numstat[filename] = [add, del]
    else
      warn "unexpected git output (raw/numstat): #{file_line.inspect}"
    end
  }
  Tb.csv_stream_output(files_csv="") {|gen|
    gen << %w[mode1 mode2 hash1 hash2 add del status filename]
    files_raw.each {|filename, (mode1, mode2, hash1, hash2, status)|
      add, del = files_numstat[filename]
      gen << [mode1, mode2, hash1, hash2, add, del, status, filename]
    }
  }
  h = {}
  commit_info.each {|s|
    if /:/ !~ s
      warn "unexpected git output (header:value): #{s.inspect}"
      next
    end
    k = $`
    v = $'.gsub(/\n /, "\n") # remove indent generated by %w(0,0,1)
    case k
    when /\A(?:author-date|committer-date)/
      v = v.sub(/\A(\d+-\d\d-\d\d) (\d\d:\d\d:\d\d) ([-+]\d\d\d\d)\z/, '\1T\2\3')
    when /\Aparents\z/
      v = ['parent', *v.split(/ /)].map {|c| c + "\n" }.join("")
    when /\Aref-names\z/
      v = v.strip.gsub(/\A\(|\)\z/, '')
      v = ['ref-name', *v.split(/, /)].map {|c| c + "\n" }.join("")
    end
    h[k] = v
  }
  h['files'] = files_csv
  h
end

.git_unescape_filename(filename) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/tb/cmd_git.rb', line 107

def (Tb::Cmd).git_unescape_filename(filename)
  if /\A"/ =~ filename
    $'.chomp('"').gsub(/\\((\d\d\d)|[abtnvfr"\\])/) {
      str = $1
      if $2
        [str.to_i(8)].pack("C")
      else
        case str
        when 'a' then "\a"
        when 'b' then "\b"
        when 't' then "\t"
        when 'n' then "\n"
        when 'v' then "\v"
        when 'f' then "\f"
        when 'r' then "\r"
        when '"' then '"'
        when '\\' then "\\"
        else
          warn "unexpected escape: #{str.inspect}"
        end
      end
    }
  else
    filename
  end
end

.git_with_git_log(dir) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/tb/cmd_git.rb', line 72

def (Tb::Cmd).git_with_git_log(dir)
  if Tb::Cmd.opt_git_debug_input
    File.open(Tb::Cmd.opt_git_debug_input) {|f|
      yield f
    }
  else
    git = Tb::Cmd.opt_git_command || 'git'
    # depends Ruby 1.9.
    command = [
      git,
      'log',
      "--pretty=#{Tb::Cmd::GIT_LOG_PRETTY_FORMAT}",
      '--decorate=full',
      '--raw',
      '--numstat',
      '--abbrev=40',
      '.',
      {:chdir=>dir}
    ]
    $stderr.puts "git command line: #{command.inspect}" if 1 <= Tb::Cmd.opt_debug
    if Tb::Cmd.opt_git_debug_output
      # File.realdirpath is required before Ruby 2.0.
      command.last[:out] = File.realdirpath(Tb::Cmd.opt_git_debug_output)
      system(*command)
      File.open(Tb::Cmd.opt_git_debug_output) {|f|
        yield f
      }
    else
      IO.popen(command) {|f|
        yield f
      }
    end
  end
end

.list_summary_of_subcommandsObject



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/tb/cmd_help.rb', line 46

def (Tb::Cmd).list_summary_of_subcommands
  with_output {|f|
    f.print <<'End'
End
    subcommand_maxlen = Tb::Cmd.subcommands.map {|subcommand| subcommand.length }.max
    fmt = "%-#{subcommand_maxlen}s : %s"
    Tb::Cmd.subcommands.each {|subcommand|
      banner = self.subcommand_send("op", subcommand).banner
      summary = banner[/^Usage: (.*)\n(.*)/, 2]
      f.puts(fmt % [subcommand, summary])
    }
  }
end

.main(argv) ⇒ Object



43
44
45
46
47
48
# File 'lib/tb/cmdmain.rb', line 43

def (Tb::Cmd).main(argv)
  main_body(argv)
rescue SystemExit
  $stderr.puts $!.message if $!.message != 'exit'
  raise
end

.main_body(argv) ⇒ Object

Copyright © 2011-2012 Tanaka Akira <[email protected]>

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
   copyright notice, this list of conditions and the following
   disclaimer in the documentation and/or other materials provided
   with the distribution.
3. The name of the author may not be used to endorse or promote
   products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/tb/cmdmain.rb', line 29

def (Tb::Cmd).main_body(argv)
  subcommand = argv.shift
  if subcommand == '-h' || subcommand == '--help'
    main_help(argv)
  elsif Tb::Cmd.subcommands.include?(subcommand)
    self.subcommand_send("main", subcommand, argv)
  elsif subcommand == nil
    usage_list_subcommands
    true
  else
    err "unexpected subcommand: #{subcommand.inspect}"
  end
end

.main_cat(argv) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/tb/cmd_cat.rb', line 61

def (Tb::Cmd).main_cat(argv)
  op_cat.parse!(argv)
  exit_if_help('cat')
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N, Tb::Cmd.opt_cat_with_filename)
  output_tbenum(creader)
end

.main_consecutive(argv) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/tb/cmd_consecutive.rb', line 56

def (Tb::Cmd).main_consecutive(argv)
  op_consecutive.parse!(argv)
  exit_if_help('consecutive')
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    buf = []
    empty = true
    creader.with_cumulative_header {|header0|
      if header0
        y.set_header header0.map {|f| (1..Tb::Cmd.opt_consecutive_n).map {|i| "#{f}_#{i}" } }.flatten(1)
      end
    }.each {|pairs, header|
      buf << pairs
      if buf.length == Tb::Cmd.opt_consecutive_n
        pairs2 = {}
        header.each {|f|
          Tb::Cmd.opt_consecutive_n.times {|i|
            ps = buf[i]
            next if !ps.has_key?(f)
            v = ps[f]
            pairs2["#{f}_#{i+1}"] = v
          }
        }
        empty = false
        y.yield pairs2
        buf.shift
      end
    }
  }
  output_tbenum(er)
end

.main_crop(argv) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/tb/cmd_crop.rb', line 46

def (Tb::Cmd).main_crop(argv)
  op_crop.parse!(argv)
  exit_if_help('crop')
  argv = ['-'] if argv.empty?
  stream = false
  if Tb::Cmd.opt_crop_range
    case Tb::Cmd.opt_crop_range
    when /\AR(\d+)C(\d+):R(\d+)C(\d+)\z/ # 1-based (R1C1 reference style)
      stream = true
      range_row1 = $1.to_i
      range_col1 = $2.to_i
      range_row2 = $3.to_i
      range_col2 = $4.to_i
    when /\A([A-Z]+)(\d+):([A-Z]+)(\d+)\z/ # 1-based (A1 reference style)
      stream = true
      range_col1 = decode_a1_addressing_col($1)
      range_row1 = $2.to_i
      range_col2 = decode_a1_addressing_col($3)
      range_row2 = $4.to_i
    else
      raise ArgumentError, "unexpected range argument: #{Tb::Cmd.opt_crop_range.inspect}"
    end
  end
  if stream
    creader = Tb::CatReader.open(argv, true)
    er = Tb::Enumerator.new {|y|
      rownum = 1
      creader.each {|pairs|
        if range_row2 < rownum
          break
        end
        if range_row1 <= rownum
          pairs2 = pairs.reject {|f, v|
            f = f.to_i
            f < range_col1 || range_col2 < f
          }
          y.yield pairs2
        end
        rownum += 1
      }
    }
    Tb::Cmd.opt_N = true
    output_tbenum(er)
  else
    creader = Tb::CatReader.open(argv, true)
    last_nonempty_row = nil
    lmargin_min = nil
    ter = Enumerator.new {|y|
      numrows = 0
      creader.each {|pairs|
        ary = []
        pairs.each {|f, v|
          ary[f.to_i-1] = v
        }
        while !ary.empty? && (ary.last.nil? || ary.last == '')
          ary.pop
        end
        if numrows == 0 && ary.empty?
          next
        end
        if !ary.empty?
          lmargin = 0
          while lmargin < ary.length
            if !ary[lmargin].nil? && ary[lmargin] != ''
              break 
            end
            lmargin += 1
          end
          if lmargin_min.nil? || lmargin < lmargin_min
            lmargin_min = lmargin
          end
        end
        last_nonempty_row = numrows if !ary.empty?
        y.yield ary
        numrows += 1
      }
    }.to_fileenumerator
    er = Tb::Enumerator.new {|y|
      ter.each_with_index {|ary, rownum|
        if last_nonempty_row < rownum
          break
        end
        ary.slice!(0, lmargin_min)
        pairs = Hash[ary.map.with_index {|v, i| ["#{i+1}", v]}]
        y.yield pairs
      }
    }
    Tb::Cmd.opt_N = true
    output_tbenum(er)
  end
end

.main_cross(argv) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/tb/cmd_cross.rb', line 45

def (Tb::Cmd).main_cross(argv)
  op_cross.parse!(argv)
  exit_if_help('cross')
  err('no vkey-fields given.') if argv.empty?
  vkfs = split_field_list_argument(argv.shift)
  err('no hkey-fields given.') if argv.empty?
  hkfs = split_field_list_argument(argv.shift)
  vhkfs = vkfs + hkfs
  if Tb::Cmd.opt_cross_fields.empty?
    num_aggregate_fields = 1
    opt_cross_fields = (vkfs + hkfs).map {|f| [f, Tb::Func::First, f] } +
      [['count', Tb::Func::Count, nil]]
  else
    num_aggregate_fields = Tb::Cmd.opt_cross_fields.length
    opt_cross_fields = (vkfs + hkfs).map {|f| [f, Tb::Func::First, f] } +
      Tb::Cmd.opt_cross_fields.map {|arg|
      agg_spec, new_field = split_field_list_argument(arg)
      new_field ||= agg_spec
      begin
        func_srcf = parse_aggregator_spec2(agg_spec)
      rescue ArgumentError
        err($!.message)
      end
      [new_field, *func_srcf]
    }
  end
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    hvs_hash = {}
    hvs_list = nil
    aggs_hash = nil
    op = Tb::Zipper.new(opt_cross_fields.map {|dstf, func, srcf| func })
    er = creader.with_header {|header0|
      vhkfs.each {|f|
        if !header0.include?(f)
          err("field not found: #{f}")
        end
      }
    }.extsort_reduce(op) {|pairs|
      vvs = vkfs.map {|f| pairs[f] }
      hvs = hkfs.map {|f| pairs[f] }
      vvsc = vvs.map {|v| Tb::Func.smart_cmp_value(v) }
      hvsc = hvs.map {|v| Tb::Func.smart_cmp_value(v) }
      hvs_hash[hvs] = hvsc
      aggs = opt_cross_fields.map {|dstf, func, srcf| func.start(srcf ? pairs[srcf] : true) }
      [[vvsc, hvsc], aggs]
    }
    all_representative = lambda {|_| 1 }
    all_before = lambda {|_|
      hvs_list = hvs_hash.keys.sort_by {|hvs| hvs_hash[hvs] }
      n = vkfs.length + hvs_list.length * num_aggregate_fields
      header1 = (1..n).map {|i| i.to_s }
      y.set_header header1
      hkfs.each_with_index {|hkf, i|
        next if Tb::Cmd.opt_cross_compact && i == hkfs.length - 1
        h1 = {}
        j = vkfs.length
        h1[j.to_s] = hkf
        hvs_list.each {|hkvs|
          num_aggregate_fields.times {
            j += 1
            h1[j.to_s] = hkvs[i]
          }
        }
        y.yield h1
      }
      h2 = {}
      j = 0
      vkfs.each {|vkf|
        j += 1
        h2[j.to_s] = vkf
      }
      hvs_list.each {|hkvs|
        opt_cross_fields.last(num_aggregate_fields).each {|dstf, func, srcf|
          j += 1
          if Tb::Cmd.opt_cross_compact
            h2[j.to_s] = hkvs[-1]
          else
            h2[j.to_s] = dstf
          end
        }
      }
      y.yield h2
    }
    v_representative = lambda {|((vvsc, _), _)|
      vvsc
    }
    v_before = lambda {|_|
      aggs_hash = {}
    }
    body = lambda {|(_, aggs)|
      hvs = aggs[vkfs.length, hkfs.length]
      aggs_hash[hvs] = op.aggregate(aggs)
    }
    v_after = lambda {|(_, aggs)|
      vvs = aggs[0, vkfs.length]
      ary = vvs
      hvs_list.each {|hvs|
        if aggs_hash.has_key? hvs
          ary.concat(aggs_hash[hvs].last(num_aggregate_fields))
        else
          ary.concat([nil] * num_aggregate_fields)
        end
      }
      pairs = {}
      ary.each_with_index {|v, i|
        pairs[(i+1).to_s] = v
      }
      y.yield pairs
    }
    er.detect_nested_group_by(
      [[all_representative, all_before],
       [v_representative, v_before, v_after]]).each(&body)
  }
  Tb::Cmd.opt_N = true
  output_tbenum(er)
end

.main_cut(argv) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/tb/cmd_cut.rb', line 42

def (Tb::Cmd).main_cut(argv)
  op_cut.parse!(argv)
  exit_if_help('cut')
  err('no fields given.') if argv.empty?
  fs = split_field_list_argument(argv.shift)
  argv = ['-'] if argv.empty?
  Tb::CatReader.open(argv, Tb::Cmd.opt_N) {|tblreader|
    if Tb::Cmd.opt_cut_v
      er = Tb::Enumerator.new {|y|
        tblreader.with_header {|header0|
          if header0
            y.set_header header0 - fs
          end
        }.each {|pairs|
          y.yield pairs.reject {|k, v| fs.include? k }
        }
      }
      output_tbenum(er)
    else
      er = Tb::Enumerator.new {|y|
        tblreader.with_header {|header0|
          if header0
            fieldset = Tb::FieldSet.new(*header0)
            fs.each {|f|
              fieldset.index_from_field_ex(f)
            }
          end
          y.set_header fs
        }.each {|pairs|
          y.yield pairs.reject {|k, v| !fs.include?(k) }
        }
      }
      output_tbenum(er)
    end
  }
end

.main_git(argv) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/tb/cmd_git.rb', line 199

def (Tb::Cmd).main_git(argv)
  op_git.parse!(argv)
  exit_if_help('git')
  argv = ['.'] if argv.empty?
  er = Tb::Enumerator.new {|y|
    y.set_header Tb::Cmd::GIT_LOG_HEADER
    argv.each {|dir|
      git_with_git_log(dir) {|f|
        f.set_encoding("ASCII-8BIT") if f.respond_to? :set_encoding
        git_each_commit(f) {|h|
          y.yield h
        }
      }
    }
  }
  output_tbenum(er)
end

.main_grep(argv) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/tb/cmd_grep.rb', line 48

def (Tb::Cmd).main_grep(argv)
  op_grep.parse!(argv)
  exit_if_help('grep')
  if Tb::Cmd.opt_grep_ruby
    pred = eval("lambda {|_| #{Tb::Cmd.opt_grep_ruby} }")
  elsif Tb::Cmd.opt_grep_e
    re = Regexp.new(Tb::Cmd.opt_grep_e)
    pred = Tb::Cmd.opt_grep_f ? lambda {|_| re =~ _[Tb::Cmd.opt_grep_f] } :
                                lambda {|_| _.any? {|k, v| re =~ v.to_s } }
  else
    err("no regexp given.") if argv.empty?
    re = Regexp.new(argv.shift)
    pred = Tb::Cmd.opt_grep_f ? lambda {|_| re =~ _[Tb::Cmd.opt_grep_f] } :
                                lambda {|_| _.any? {|k, v| re =~ v.to_s } }
  end
  opt_v = Tb::Cmd.opt_grep_v ? true : false
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    header = nil
    creader.with_header {|header0|
      header = header0
      y.set_header header
    }.each {|pairs|
      h = {}
      pairs.each {|f, v|
        h[f] = v
      }
      found = pred.call(h)
      found = opt_v ^ !!(found)
      if found
        y.yield pairs
      end
    }
  }
  output_tbenum(er)
end

.main_group(argv) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/tb/cmd_group.rb', line 44

def (Tb::Cmd).main_group(argv)
  op_group.parse!(argv)
  exit_if_help('group')
  err("no key fields given.") if argv.empty?
  kfs = split_field_list_argument(argv.shift)
  opt_group_fields = kfs.map {|f| [f, Tb::Func::First, f] } +
    Tb::Cmd.opt_group_fields.map {|arg|
    aggregation_spec, new_field = split_field_list_argument(arg)
    new_field ||= aggregation_spec
    [new_field,
      *begin
        parse_aggregator_spec2(aggregation_spec)
      rescue ArgumentError
        err($!.message)
      end
    ]
  }
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  result = Tb::Enumerator.new {|y|
    op = Tb::Zipper.new(opt_group_fields.map {|dstf, func, srcf| func })
    er = creader.extsort_reduce(op) {|pairs|
      [kfs.map {|f| Tb::Func.smart_cmp_value(pairs[f]) },
       opt_group_fields.map {|dstf, func, srcf| func.start(srcf ? pairs[srcf] : true) } ]
    }
    fields = opt_group_fields.map {|dstf, func, srcf| dstf }
    y.set_header(fields)
    er.each {|_, vals|
      pairs = opt_group_fields.zip(vals).map {|(dstf, func, _), val|
        [dstf, func.aggregate(val)]
      }
      y.yield Hash[pairs]
    }
  }
  output_tbenum(result)
end

.main_gsub(argv) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/tb/cmd_gsub.rb', line 44

def (Tb::Cmd).main_gsub(argv)
  op_gsub.parse!(argv)
  exit_if_help('gsub')
  if Tb::Cmd.opt_gsub_e
    re = Regexp.new(Tb::Cmd.opt_gsub_e)
  else
    err('no regexp given.') if argv.empty?
    re = Regexp.new(argv.shift)
  end
  err('no substitution given.') if argv.empty?
  repl = argv.shift
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    creader.with_cumulative_header {|header0|
      if header0
        y.set_header header0
      end
    }.each {|pairs, header|
      fs = header.dup
      fs.pop while !fs.empty? && !pairs.has_key?(fs.last)
      if Tb::Cmd.opt_gsub_f
        pairs2 = pairs.map {|f, v|
          if f == Tb::Cmd.opt_gsub_f
            v ||= ''
            [f, v.gsub(re, repl)]
          else
            [f, v]
          end
        }
      else
        pairs2 = pairs.map {|f, v|
          v ||= ''
          [f, v.gsub(re, repl)]
        }
      end
      y.yield Hash[pairs2]
    }
  }
  output_tbenum(er)
end

.main_help(argv) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tb/cmd_help.rb', line 108

def (Tb::Cmd).main_help(argv)
  op_help.parse!(argv)
  if Tb::Cmd.opt_help_s
    list_summary_of_subcommands
    exit
  elsif argv.empty?
    if Tb::Cmd.opt_help == 0
      usage_list_subcommands
      return true
    else
      argv.unshift 'help'
      Tb::Cmd.opt_help -= 1
    end
  end
  Tb::Cmd.opt_help += 1
  subcommand = argv.shift
  exit_if_help(subcommand)
end

.main_join(argv) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/tb/cmd_join.rb', line 62

def (Tb::Cmd).main_join(argv)
  op_join.parse!(argv)
  exit_if_help('join')
  retain_left = Tb::Cmd.opt_join_retain_left
  retain_right = Tb::Cmd.opt_join_retain_right
  err('two tables required at least.') if argv.length < 2
  result = tablereader_open(argv.shift)
  argv.each {|filename|
    tbl = tablereader_open(filename)
    $stderr.puts "shared keys: #{(result.list_fields & tbl.list_fields).inspect}" if 1 <= Tb::Cmd.opt_debug
    result = result.natjoin2_outer(tbl, Tb::Cmd.opt_join_outer_missing, retain_left, retain_right)
  }
  output_tbenum(result)
end

.main_ls(argv) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/tb/cmd_ls.rb', line 48

def (Tb::Cmd).main_ls(argv)
  op_ls.parse!(argv)
  exit_if_help('ls')
  argv = ['.'] if argv.empty?
  opts = {
    :a => Tb::Cmd.opt_ls_a,
    :A => Tb::Cmd.opt_ls_A,
    :l => Tb::Cmd.opt_ls_l,
    :R => Tb::Cmd.opt_ls_R,
  }
  ls = nil
  er = Tb::Enumerator.new {|y|
    ls = Tb::Cmd::Ls.new(y, opts)
    ls.set_header
    argv.each {|arg|
      ls.ls_run(Pathname(ls.real_pathname_string(arg)))
    }
  }
  output_tbenum(er)
  if ls.fail
    exit false
  end
end

.main_melt(argv) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/tb/cmd_melt.rb', line 64

def (Tb::Cmd).main_melt(argv)
  op_melt.parse!(argv)
  exit_if_help('melt')
  err('no key-fields given.') if argv.empty?
  key_fields = split_field_list_argument(argv.shift)
  key_fields_hash = Hash[key_fields.map {|f| [f, true] }]
  if Tb::Cmd.opt_melt_regexps.empty? && Tb::Cmd.opt_melt_list.empty?
    melt_fields_pattern = //
  else
    list = Tb::Cmd.opt_melt_list + Tb::Cmd.opt_melt_regexps
    melt_fields_pattern = /\A#{Regexp.union(list)}\z/
  end
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    header = []
    header << Tb::Cmd.opt_melt_recnum if Tb::Cmd.opt_melt_recnum
    header.concat key_fields
    header << Tb::Cmd.opt_melt_variable_field
    header << Tb::Cmd.opt_melt_value_field
    y.set_header header
    creader.each_with_index {|pairs, i|
      recnum = i + 1
      h0 = {}
      h0[Tb::Cmd.opt_melt_recnum] = nil if Tb::Cmd.opt_melt_recnum
      key_fields.each {|kf|
        h0[kf] = pairs[kf]
      }
      pairs.each {|f, v|
        next if key_fields_hash[f]
        next if melt_fields_pattern !~ f
        h = h0.dup
        h[Tb::Cmd.opt_melt_recnum] = recnum if Tb::Cmd.opt_melt_recnum
        h[Tb::Cmd.opt_melt_variable_field] = f
        h[Tb::Cmd.opt_melt_value_field] = v
        y.yield h
      }
    }
  }
  output_tbenum(er)
end

.main_mheader(argv) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/tb/cmd_mheader.rb', line 42

def (Tb::Cmd).main_mheader(argv)
  op_mheader.parse!(argv)
  exit_if_help('mheader')
  argv = ['-'] if argv.empty?
  header = []
  if Tb::Cmd.opt_mheader_count
    c = Tb::Cmd.opt_mheader_count
    header_end_p = lambda {
      c -= 1
      c == 0 ? header.map {|a| a.compact.join(' ').strip } : nil
    }
  else
    header_end_p = lambda {
      h2 = header.map {|a| a.compact.join(' ').strip }.uniq
      header.length == h2.length ? h2 : nil
    }
  end
  creader = Tb::CatReader.open(argv, true)
  er = Tb::Enumerator.new {|y|
    creader.each {|pairs|
      if header
        ary = []
        pairs.each {|f, v| ary[f.to_i-1] = v }
        ary.each_with_index {|v,i|
          header[i] ||= []
          header[i] << v if header[i].empty? || header[i].last != v
        }
        h2 = header_end_p.call
        if h2
          pairs2 = Hash[h2.map.with_index {|v, i| ["#{i+1}", v] }]
          y.yield pairs2
          header = nil
        end
      else
        y.yield pairs
      end
    }
  }
  Tb::Cmd.opt_N = true
  output_tbenum(er)
  if header
    warn "unique header fields not recognized."
  end
end

.main_nest(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/tb/cmd_nest.rb', line 39

def (Tb::Cmd).main_nest(argv)
  op_nest.parse!(argv)
  exit_if_help('nest')
  err('no fields given.') if argv.empty?
  fields = split_field_list_argument(argv.shift)
  newfield, *oldfields = fields
  oldfields_hash = {}
  oldfields.each {|f| oldfields_hash[f] = true }
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    sorted = creader.with_header {|header0|
      oldfields.each {|f|
        if !header0.include?(f)
          err("field not found: #{f.inspect}")
        end
      }
      y.set_header(header0.reject {|f| oldfields_hash[f] } + [newfield])
    }.map {|pairs|
      cv = pairs.reject {|f, v|
        oldfields_hash[f]
      }.map {|f, v|
        [Tb::Func.smart_cmp_value(f), Tb::Func.smart_cmp_value(v)]
      }.sort
      [cv, pairs]
    }

    nested = nil
    before_group = lambda {|(_, _)|
      nested = []
    }
    body = lambda {|(_, pairs)|
      nested << pairs.reject {|f, v| !oldfields_hash[f] }
    }
    after_group = lambda {|(_, last_pairs)|
      Tb.csv_stream_output(nested_csv="") {|ngen|
        ngen << oldfields
        nested.each {|npairs|
          ngen << oldfields.map {|of| npairs[of] }
        }
      }
      assoc = last_pairs.reject {|f, v| oldfields_hash[f] }.to_a
      assoc << [newfield, nested_csv]
      pairs = Hash[assoc]
      y.yield pairs
    }
    sorted.detect_group_by(before_group, after_group) {|cv,| cv }.each(&body)
  }
  output_tbenum(er)
end

.main_newfield(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/tb/cmd_newfield.rb', line 39

def (Tb::Cmd).main_newfield(argv)
  op_newfield.parse!(argv)
  exit_if_help('newfield')
  err('no new field name given.') if argv.empty?
  field = argv.shift
  err('no ruby expression given.') if argv.empty?
  rubyexp = argv.shift
  pr = eval("lambda {|_| #{rubyexp} }")
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = creader.newfield(field) {|pairs| pr.call(pairs) }
  output_tbenum(er)
end

.main_rename(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/tb/cmd_rename.rb', line 39

def (Tb::Cmd).main_rename(argv)
  op_rename.parse!(argv)
  exit_if_help('rename')
  err('rename fields not given.') if argv.empty?
  fs = split_field_list_argument(argv.shift)
  argv = ['-'] if argv.empty?
  h = {}
  fs.each_slice(2) {|sf, df| h[sf] = df }
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    header = nil
    creader.with_header {|header0|
      header = header0
      h.each {|sf, df|
        unless header.include? sf
          err "field not found: #{sf.inspect}"
        end
      }
      y.set_header header.map {|f| h.fetch(f, f) }
    }.each {|pairs|
      y.yield Hash[pairs.map {|f, v| [h.fetch(f, f), v] }]
    }
  }
  output_tbenum(er)
end

.main_shape(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/tb/cmd_shape.rb', line 39

def (Tb::Cmd).main_shape(argv)
  op_shape.parse!(argv)
  exit_if_help('shape')
  filenames = argv.empty? ? ['-'] : argv
  result = Tb.new(%w[header_fields min_fields max_fields records filename])
  filenames.each {|filename|
    tablereader_open(filename) {|tblreader|
      min_num_fields = nil
      max_num_fields = nil
      num_records = 0
      num_header_fields = nil
      tblreader.with_header {|header|
        num_header_fields = header.length
      }.each {|pairs|
        ary = pairs.values
        num_records += 1
        n = ary.length
        if min_num_fields.nil?
          min_num_fields = max_num_fields = n
        else
          min_num_fields = n if n < min_num_fields
          max_num_fields = n if max_num_fields < n
        end
      }
      result.insert({'header_fields'=>num_header_fields,
                     'min_fields'=>min_num_fields,
                     'max_fields'=>max_num_fields,
                     'records'=>num_records,
                     'filename'=>filename})
    }
  }
  Tb::Cmd.opt_N = false
  output_tbenum(result)
end

.main_sort(argv) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/tb/cmd_sort.rb', line 44

def (Tb::Cmd).main_sort(argv)
  op_sort.parse!(argv)
  exit_if_help('sort')
  argv = ['-'] if argv.empty?
  if Tb::Cmd.opt_sort_f
    fs = split_field_list_argument(Tb::Cmd.opt_sort_f)
  else
    fs = nil
  end
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  header = []
  if fs
    blk = lambda {|pairs| fs.map {|f| Tb::Func.smart_cmp_value(pairs[f]) } }
  else
    blk = lambda {|pairs| header.map {|f| Tb::Func.smart_cmp_value(pairs[f]) } }
  end
  if Tb::Cmd.opt_sort_r
    blk1 = blk
    blk = lambda {|pairs| Tb::RevCmp.new(blk1.call(pairs)) }
  end
  er = Tb::Enumerator.new {|y|
    creader.with_cumulative_header {|header0|
      if header0
        y.set_header(header0)
      end
    }.each {|pairs, header1|
      header = header1
      y.yield pairs
    }
  }.extsort_by(&blk)
  output_tbenum(er)
end

.main_svn(argv) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
# File 'lib/tb/cmd_svn.rb', line 173

def (Tb::Cmd).main_svn(argv)
  op_svn.parse!(argv)
  exit_if_help('svn')
  er = Tb::Enumerator.new {|y|
    svn_with_svn_log(argv) {|f|
      listener = Tb::Cmd::SVNLOGListener.new(y)
      REXML::Parsers::StreamParser.new(f, listener).parse
    }
  }
  output_tbenum(er)
end

.main_to_csv(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/tb/cmd_to_csv.rb', line 39

def (Tb::Cmd).main_to_csv(argv)
  op_to_csv.parse!(argv)
  exit_if_help('to-csv')
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  header = []
  ter = Tb::Enumerator.new {|y|
    creader.with_cumulative_header.each {|pairs, header1|
      header = header1
      y.yield pairs
    }
  }.to_fileenumerator
  er = Tb::Enumerator.new {|y|
    y.set_header header
    ter.each {|pairs|
      y.yield Hash[header.map {|f| [f, pairs[f]] }]
    }
  }
  with_output {|out|
    er.write_to_csv(out, !Tb::Cmd.opt_N)
  }
end

.main_to_json(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/tb/cmd_to_json.rb', line 39

def (Tb::Cmd).main_to_json(argv)
  require 'json'
  op_to_json.parse!(argv)
  exit_if_help('to-json')
  argv = ['-'] if argv.empty?
  with_output {|out|
    out.print "["
    sep = nil
    argv.each {|filename|
      sep = ",\n\n" if sep
      tablereader_open(filename) {|tblreader|
        tblreader.each {|pairs|
          out.print sep if sep
          out.print JSON.pretty_generate(Hash[pairs.to_a])
          sep = ",\n"
        }
      }
    }
    out.puts "]"
  }
end

.main_to_pnm(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/tb/cmd_to_pnm.rb', line 39

def (Tb::Cmd).main_to_pnm(argv)
  op_to_pnm.parse!(argv)
  exit_if_help('to-pnm')
  argv = ['-'] if argv.empty?
  tbl = Tb::CatReader.open(argv, Tb::Cmd.opt_N).to_tb
  with_output {|out|
    tbl.generate_pnm(out)
  }
end

.main_to_pp(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/tb/cmd_to_pp.rb', line 39

def (Tb::Cmd).main_to_pp(argv)
  op_to_pp.parse!(argv)
  exit_if_help('to-pp')
  argv.unshift '-' if argv.empty?
  with_output {|out|
    argv.each {|filename|
      tablereader_open(filename) {|tblreader|
        tblreader.each {|pairs|
          a = pairs.reject {|f, v| v.nil? }
          q = PP.new(out, 79)
          q.guard_inspect_key {
            q.group(1, '{', '}') {
              q.seplist(a, nil, :each) {|kv|
                k, v = kv
                q.group {
                  q.pp k
                  q.text '=>'
                  q.group(1) {
                    q.breakable ''
                    q.pp v
                  }
                }
              }
            }
          }
          q.flush
          out << "\n"
        }
      }
    }
  }
end

.main_to_tsv(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/tb/cmd_to_tsv.rb', line 39

def (Tb::Cmd).main_to_tsv(argv)
  op_to_tsv.parse!(argv)
  exit_if_help('to-tsv')
  argv = ['-'] if argv.empty?
  tbl = Tb::CatReader.open(argv, Tb::Cmd.opt_N).to_tb
  with_output {|out|
    tbl_generate_tsv(tbl, out)
  }
end

.main_to_yaml(argv) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/tb/cmd_to_yaml.rb', line 39

def (Tb::Cmd).main_to_yaml(argv)
  require 'yaml'
  op_to_yaml.parse!(argv)
  exit_if_help('to-yaml')
  argv = ['-'] if argv.empty?
  tbl = Tb::CatReader.open(argv, Tb::Cmd.opt_N).to_tb
  ary = tbl.map {|rec| rec.to_h }
  with_output {|out|
    YAML.dump(ary, out)
    out.puts
  }
end

.main_unmelt(argv) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tb/cmd_unmelt.rb', line 61

def (Tb::Cmd).main_unmelt(argv)
  op_unmelt.parse!(argv)
  exit_if_help('unmelt')
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  if Tb::Cmd.opt_unmelt_keys.empty?
    key_fields = nil
  else
    if Tb::Cmd.opt_unmelt_recnum
      key_fields = [Tb::Cmd.opt_unmelt_recnum]
    else
      key_fields = []
    end
    key_fields += Tb::Cmd.opt_unmelt_keys
  end
  melt_fields_hash = {}
  er = Tb::Enumerator.new {|y|
    creader.chunk {|pairs|
      keys = {}
      if key_fields
        key_fields.each {|k|
          keys[k] = pairs[k]
        }
      else
        pairs.each_key {|k|
          next if k == Tb::Cmd.opt_unmelt_variable_field ||
                  k == Tb::Cmd.opt_unmelt_value_field
          keys[k] = pairs[k]
        }
      end
      keys
    }.each {|keys, pairs_ary|
      if Tb::Cmd.opt_unmelt_recnum
        keys.delete Tb::Cmd.opt_unmelt_recnum
      end
      rec = keys.dup
      pairs_ary.each {|pairs|
        var = pairs[Tb::Cmd.opt_unmelt_variable_field]
        val = pairs[Tb::Cmd.opt_unmelt_value_field]
        melt_fields_hash[var] = true
        if rec.has_key? var
          y.yield rec
          rec = keys.dup
        end
        rec[var] = val
      }
      y.yield rec
    }
  }
  if !Tb::Cmd.opt_unmelt_missing_value
    er2 = er
  else
    er2 = Tb::Enumerator.new {|y|
      er.to_fileenumerator.with_header {|header|
        y.set_header header
      }.each {|pairs|
        melt_fields_hash.each_key {|f|
          pairs[f] ||= Tb::Cmd.opt_unmelt_missing_value
        }
        y.yield pairs
      }
    }
  end
  output_tbenum(er2)
end

.main_unnest(argv) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/tb/cmd_unnest.rb', line 44

def (Tb::Cmd).main_unnest(argv)
  op_unnest.parse!(argv)
  exit_if_help('unnest')
  err('no field given.') if argv.empty?
  target_field = argv.shift
  argv = ['-'] if argv.empty?
  creader = Tb::CatReader.open(argv, Tb::Cmd.opt_N)
  er = Tb::Enumerator.new {|y|
    nested_fields = {}
    ter = Tb::Enumerator.new {|y2|
      creader.with_header {|header0|
        unless header0.include? target_field
          err("field not found: #{target_field.inspect}")
        end
        y2.set_header header0
      }.each {|pairs|
        pairs2 = {}
        pairs.each {|f, v|
          if f != target_field
            pairs2[f] = v
          elsif v.nil?
            pairs2[f] = v
          else
            nested_tbl = Tb.parse_csv(v)
            nested_tbl.list_fields.each {|f2|
              unless nested_fields.has_key? f2
                nested_fields[f2] = nested_fields.size
              end
            }
            pairs2[f] = nested_tbl
          end
        }
        y2.yield pairs2
      }
    }.to_fileenumerator
    nested_fields_ary = nested_fields.keys
    if Tb::Cmd.opt_unnest_prefix
      nested_fields_ary.map! {|f| Tb::Cmd.opt_unnest_prefix + f }
    end
    ter.with_header {|header0|
      header2 = []
      header0.each {|f|
        if f != target_field
          header2 << f
        else
          header2.concat nested_fields_ary
        end
      }
      y.set_header header2
    }.each {|pairs|
      pairs2 = pairs.reject {|f, v| f == target_field }
      ntbl = pairs[target_field]
      if ntbl.nil? || ntbl.empty?
        if Tb::Cmd.opt_unnest_outer
          y.yield pairs2
        end
      else
        ntbl.each {|npairs|
          pairs3 = pairs2.dup
          npairs.each {|nf, nv|
            if Tb::Cmd.opt_unnest_prefix
              nf = Tb::Cmd.opt_unnest_prefix + nf
            end
            pairs3[nf] = nv
          }
          y.yield pairs3
        }
      end
    }
  }
  output_tbenum(er)
end

.op_catObject



33
34
35
36
37
38
39
40
# File 'lib/tb/cmd_cat.rb', line 33

def (Tb::Cmd).op_cat
  op = OptionParser.new
  op.banner = "Usage: tb cat [OPTS] [TABLE ...]\n" +
    "Concatenate tables vertically."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-H', '--with-filename', 'add filename column') { Tb::Cmd.opt_cat_with_filename = true }
  op
end

.op_consecutiveObject



33
34
35
36
37
38
39
40
# File 'lib/tb/cmd_consecutive.rb', line 33

def (Tb::Cmd).op_consecutive
  op = OptionParser.new
  op.banner = "Usage: tb consecutive [OPTS] [TABLE ...]\n" +
    "Concatenate consecutive rows."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-n NUM', 'gather NUM records.  (default: 2)') {|n| Tb::Cmd.opt_consecutive_n = n.to_i }
  op
end

.op_cropObject



33
34
35
36
37
38
39
40
# File 'lib/tb/cmd_crop.rb', line 33

def (Tb::Cmd).op_crop
  op = OptionParser.new
  op.banner = "Usage: tb crop [OPTS] [TABLE ...]\n" +
    "Extract rectangle in a table."
  define_common_option(op, "ho", "--no-pager")
  op.def_option('-r RANGE', 'range.  i.e. "2,1-4,3", "B1:D3"') {|arg| Tb::Cmd.opt_crop_range = arg }
  op
end

.op_crossObject



34
35
36
37
38
39
40
41
42
43
# File 'lib/tb/cmd_cross.rb', line 34

def (Tb::Cmd).op_cross
  op = OptionParser.new
  op.banner = "Usage: tb cross [OPTS] VKEY-FIELD1,... HKEY-FIELD1,... [TABLE ...]\n" +
    "Create a cross table. (a.k.a contingency table, pivot table)"
  define_common_option(op, "ho", "--no-pager")
  op.def_option('-a AGGREGATION-SPEC[,NEW-FIELD]',
                '--aggregate AGGREGATION-SPEC[,NEW-FIELD]') {|arg| Tb::Cmd.opt_cross_fields << arg }
  op.def_option('-c', '--compact', 'compact format') { Tb::Cmd.opt_cross_compact = true }
  op
end

.op_cutObject



33
34
35
36
37
38
39
40
# File 'lib/tb/cmd_cut.rb', line 33

def (Tb::Cmd).op_cut
  op = OptionParser.new
  op.banner = "Usage: tb cut [OPTS] FIELD,... [TABLE]\n" +
    "Select columns."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-v', 'invert match') { Tb::Cmd.opt_cut_v = true }
  op
end

.op_gitObject



35
36
37
38
39
40
41
42
43
44
# File 'lib/tb/cmd_git.rb', line 35

def (Tb::Cmd).op_git
  op = OptionParser.new
  op.banner = "Usage: tb git [OPTS] [GIT-DIR ...]\n" +
    "Show the GIT log as a table."
  define_common_option(op, "hNod", "--no-pager", '--debug')
  op.def_option('--git-command COMMAND', 'specify the git command (default: git)') {|command| Tb::Cmd.opt_git_command = command }
  op.def_option('--debug-git-output FILE', 'store the raw output of git (for debug)') {|filename| Tb::Cmd.opt_git_debug_output = filename }
  op.def_option('--debug-git-input FILE', 'use the file as output of git (for debug)') {|filename| Tb::Cmd.opt_git_debug_input = filename }
  op
end

.op_grepObject



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/tb/cmd_grep.rb', line 36

def (Tb::Cmd).op_grep
  op = OptionParser.new
  op.banner = "Usage: tb grep [OPTS] REGEXP [TABLE ...]\n" +
    "Search rows using regexp or ruby expression."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-f FIELD', 'search field') {|field| Tb::Cmd.opt_grep_f = field }
  op.def_option('-e REGEXP', 'predicate written in ruby.  A hash is given as _.  no usual regexp argument.') {|pattern| Tb::Cmd.opt_grep_e = pattern }
  op.def_option('--ruby RUBY-EXP', 'specify a regexp.  no usual regexp argument.') {|ruby_exp| Tb::Cmd.opt_grep_ruby = ruby_exp }
  op.def_option('-v', 'ouput the records which doesn\'t match') { Tb::Cmd.opt_grep_v = true }
  op
end

.op_groupObject



33
34
35
36
37
38
39
40
41
42
# File 'lib/tb/cmd_group.rb', line 33

def (Tb::Cmd).op_group
  op = OptionParser.new
  op.banner = "Usage: tb group [OPTS] KEY-FIELD1,... [TABLE ...]\n" +
    "Group and aggregate rows."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-a AGGREGATION-SPEC[,NEW-FIELD]',
                '--aggregate AGGREGATION-SPEC[,NEW-FIELD]') {|arg| Tb::Cmd.opt_group_fields << arg }
  op.def_option('--no-pager', 'don\'t use pager') { Tb::Cmd.opt_no_pager = true }
  op
end

.op_gsubObject



34
35
36
37
38
39
40
41
42
# File 'lib/tb/cmd_gsub.rb', line 34

def (Tb::Cmd).op_gsub
  op = OptionParser.new
  op.banner = "Usage: tb gsub [OPTS] REGEXP STRING [TABLE ...]\n" +
    "Substitute cells."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-f FIELD', 'target field') {|field| Tb::Cmd.opt_gsub_f = field }
  op.def_option('-e REGEXP', 'specify regexp, possibly begins with a hyphen') {|pattern| Tb::Cmd.opt_gsub_e = pattern }
  op
end

.op_helpObject



60
61
62
63
64
65
66
67
# File 'lib/tb/cmd_help.rb', line 60

def (Tb::Cmd).op_help
  op = OptionParser.new
  op.banner = "Usage: tb help [OPTS] [SUBCOMMAND]\n" +
    "Show help message of tb command."
  define_common_option(op, "hvo", "--no-pager")
  op.def_option('-s', 'show summary of subcommands') { Tb::Cmd.opt_help_s = true }
  op
end

.op_joinObject



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/tb/cmd_join.rb', line 35

def (Tb::Cmd).op_join
  op = OptionParser.new
  op.banner = "Usage: tb join [OPTS] [TABLE1 TABLE2 ...]\n" +
    "Concatenate tables horizontally as left/right/full natural join."
  define_common_option(op, 'hNod', '--no-pager', '--debug')
  op.def_option('--outer', 'outer join') {
    Tb::Cmd.opt_join_retain_left = true
    Tb::Cmd.opt_join_retain_right = true
  }
  op.def_option('--left', 'left outer join') {
    Tb::Cmd.opt_join_retain_left = true
    Tb::Cmd.opt_join_retain_right = false
  }
  op.def_option('--right', 'right outer join') {
    Tb::Cmd.opt_join_retain_left = false
    Tb::Cmd.opt_join_retain_right = true
  }
  op.def_option('--outer-missing=DEFAULT', 'missing value for outer join') {|missing|
    if Tb::Cmd.opt_join_retain_left == nil
      Tb::Cmd.opt_join_retain_left = true
      Tb::Cmd.opt_join_retain_right = true
    end
    Tb::Cmd.opt_join_outer_missing = missing
  }
  op
end

.op_lsObject



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/tb/cmd_ls.rb', line 36

def (Tb::Cmd).op_ls
  op = OptionParser.new
  op.banner = "Usage: tb ls [OPTS] [FILE ...]\n" +
    "List directory entries as a table."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-a', 'don\'t ignore filenames beginning with a period.') {|fs| Tb::Cmd.opt_ls_a = true }
  op.def_option('-A', 'don\'t ignore filenames beginning with a period, except "." and "..".') {|fs| Tb::Cmd.opt_ls_A = true }
  op.def_option('-l', 'show attributes.  -ll for more attributes.') {|fs| Tb::Cmd.opt_ls_l += 1 }
  op.def_option('-R', 'recursive.') {|fs| Tb::Cmd.opt_ls_R = true }
  op
end

.op_meltObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/tb/cmd_melt.rb', line 37

def (Tb::Cmd).op_melt
  op = OptionParser.new
  op.banner = "Usage: tb melt KEY-FIELDS-LIST [OPTS] [TABLE ...]\n" +
    "split value fields into records."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('--recnum[=FIELD]',
                'add recnum field (default don\'t add)') {|field|
    Tb::Cmd.opt_melt_recnum = field || 'recnum'
  }
  op.def_option('-R REGEXP',
                '--melt-regexp REGEXP',
                'regexp for melt fields') {|regexp|
    Tb::Cmd.opt_melt_regexps << Regexp.compile(regexp)
  }
  op.def_option('--melt-fields FIELD,...',
                'list of melt fields') {|fields|
    Tb::Cmd.opt_melt_list.concat split_field_list_argument(fields)
  }
  op.def_option('--variable-field FIELD', 'variable field. (default: variable)') {|field|
    Tb::Cmd.opt_melt_variable_field = field
  }
  op.def_option('--value-field FIELD', 'value field. (default: value)') {|field|
    Tb::Cmd.opt_melt_value_field = field
  }
  op
end

.op_mheaderObject



33
34
35
36
37
38
39
40
# File 'lib/tb/cmd_mheader.rb', line 33

def (Tb::Cmd).op_mheader
  op = OptionParser.new
  op.banner = "Usage: tb mheader [OPTS] [TABLE]\n" +
    "Collapse multi rows header."
  define_common_option(op, "ho", "--no-pager")
  op.def_option('-c N', 'number of header records') {|arg| Tb::Cmd.opt_mheader_count = arg.to_i }
  op
end

.op_nestObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_nest.rb', line 31

def (Tb::Cmd).op_nest
  op = OptionParser.new
  op.banner = "Usage: tb nest [OPTS] NEWFIELD,OLDFIELD1,OLDFIELD2,... [TABLE ...]\n" +
    "Nest fields."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_newfieldObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_newfield.rb', line 31

def (Tb::Cmd).op_newfield
  op = OptionParser.new
  op.banner = "Usage: tb newfield [OPTS] FIELD RUBY-EXP [TABLE]\n" +
    "Add a field."
  define_common_option(op, "ho", "--no-pager")
  op
end

.op_renameObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_rename.rb', line 31

def (Tb::Cmd).op_rename
  op = OptionParser.new
  op.banner = "Usage: tb rename [OPTS] SRC,DST,... [TABLE]\n" +
    "Rename field names."
  define_common_option(op, "ho", "--no-pager")
  op
end

.op_shapeObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_shape.rb', line 31

def (Tb::Cmd).op_shape
  op = OptionParser.new
  op.banner = "Usage: tb shape [OPTS] [TABLE ...]\n" +
    "Show table size."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_sortObject



34
35
36
37
38
39
40
41
42
# File 'lib/tb/cmd_sort.rb', line 34

def (Tb::Cmd).op_sort
  op = OptionParser.new
  op.banner = "Usage: tb sort [OPTS] [TABLE]\n" +
    "Sort rows."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('-f FIELD,...', 'specify sort keys') {|fs| Tb::Cmd.opt_sort_f = fs }
  op.def_option('-r', '--reverse', 'reverse order') { Tb::Cmd.opt_sort_r = true }
  op
end

.op_svnObject



36
37
38
39
40
41
42
43
44
# File 'lib/tb/cmd_svn.rb', line 36

def (Tb::Cmd).op_svn
  op = OptionParser.new
  op.banner = "Usage: tb svn [OPTS] -- [SVN-LOG-ARGS]\n" +
    "Show the SVN log as a table."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('--svn-command COMMAND', 'specify the svn command (default: svn)') {|command| Tb::Cmd.opt_svn_command = command }
  op.def_option('--svn-log-xml FILE', 'specify the result svn log --xml') {|filename| Tb::Cmd.opt_svn_log_xml = filename }
  op
end

.op_to_csvObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_to_csv.rb', line 31

def (Tb::Cmd).op_to_csv
  op = OptionParser.new
  op.banner = "Usage: tb to-csv [OPTS] [TABLE ...]\n" +
    "Convert a table to CSV (Comma Separated Value)."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_to_jsonObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_to_json.rb', line 31

def (Tb::Cmd).op_to_json
  op = OptionParser.new
  op.banner = "Usage: tb to-json [OPTS] [TABLE]\n" +
    "Convert a table to JSON (JavaScript Object Notation)."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_to_pnmObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_to_pnm.rb', line 31

def (Tb::Cmd).op_to_pnm
  op = OptionParser.new
  op.banner = "Usage: tb to-pnm [OPTS] [TABLE]\n" +
    "Convert a table to PNM (Portable Anymap: PPM, PGM, PBM)."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_to_ppObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_to_pp.rb', line 31

def (Tb::Cmd).op_to_pp
  op = OptionParser.new
  op.banner = "Usage: tb to-pp [OPTS] [TABLE]\n" +
    "Convert a table to pretty printed format."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_to_tsvObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_to_tsv.rb', line 31

def (Tb::Cmd).op_to_tsv
  op = OptionParser.new
  op.banner = "Usage: tb to-tsv [OPTS] [TABLE]\n" +
    "Convert a table to TSV (Tab Separated Value)."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_to_yamlObject



31
32
33
34
35
36
37
# File 'lib/tb/cmd_to_yaml.rb', line 31

def (Tb::Cmd).op_to_yaml
  op = OptionParser.new
  op.banner = "Usage: tb to-yaml [OPTS] [TABLE]\n" +
    "Convert a table to YAML (YAML Ain't a Markup Language)."
  define_common_option(op, "hNo", "--no-pager")
  op
end

.op_unmeltObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/tb/cmd_unmelt.rb', line 37

def (Tb::Cmd).op_unmelt
  op = OptionParser.new
  op.banner = "Usage: tb unmelt [OPTS] [TABLE ...]\n" +
    "merge melted records into a record."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('--recnum[=FIELD]',
                'use FIELD as an additional key and remove it from the result. (default: not specified)') {|field|
    Tb::Cmd.opt_unmelt_recnum = field || 'recnum'
  }
  op.def_option('--keys FIELD,...', 'key fields. (default: all fields except variable and value)') {|fields|
    Tb::Cmd.opt_unmelt_keys.concat split_field_list_argument(fields)
  }
  op.def_option('--variable-field FIELD', 'variable field. (default: variable)') {|field|
    Tb::Cmd.opt_unmelt_variable_field = field
  }
  op.def_option('--value-field FIELD', 'value field. (default: value)') {|field|
    Tb::Cmd.opt_unmelt_value_field = field
  }
  op.def_option('--missing-value FIELD', 'used for missing values. (default: not specified)') {|value|
    Tb::Cmd.opt_unmelt_missing_value = value
  }
  op
end

.op_unnestObject



34
35
36
37
38
39
40
41
42
# File 'lib/tb/cmd_unnest.rb', line 34

def (Tb::Cmd).op_unnest
  op = OptionParser.new
  op.banner = "Usage: tb unnest [OPTS] FIELD [TABLE ...]\n" +
    "Unnest a field."
  define_common_option(op, "hNo", "--no-pager")
  op.def_option('--prefix PREFIX', 'field prefix') {|prefix| Tb::Cmd.opt_unnest_prefix = prefix }
  op.def_option('--outer', 'retain rows for empty nested table') { Tb::Cmd.opt_unnest_outer = true }
  op
end

.show_help(subcommand) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/tb/cmd_help.rb', line 93

def (Tb::Cmd).show_help(subcommand)
  if Tb::Cmd.subcommands.include?(subcommand)
    with_output {|f|
      f.puts self.subcommand_send("op", subcommand)
      if 2 <= Tb::Cmd.opt_help && Tb::Cmd.verbose_help[subcommand]
        f.puts
        f.puts Tb::Cmd.verbose_help[subcommand]
      end
    }
    true
  else
    err "unexpected subcommand: #{subcommand.inspect}"
  end
end

.svn_with_svn_log(argv) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/tb/cmd_svn.rb', line 160

def (Tb::Cmd).svn_with_svn_log(argv)
  if Tb::Cmd.opt_svn_log_xml
    File.open(Tb::Cmd.opt_svn_log_xml) {|f|
      yield f
    }
  else
    svn = Tb::Cmd.opt_svn_command || 'svn'
    IO.popen([svn, 'log', '--xml', *argv]) {|f|
      yield f
    }
  end
end

.usage_list_subcommandsObject



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/tb/cmd_help.rb', line 33

def (Tb::Cmd).usage_list_subcommands
  with_output {|f|
    f.print <<'End'
Usage:
End
    Tb::Cmd.subcommands.each {|subcommand|
      banner = self.subcommand_send("op", subcommand).banner
      usage = banner[/^Usage: (.*)/, 1]
      f.puts "  " + usage
    }
  }
end

Instance Method Details

#err(msg) ⇒ Object

Raises:

  • (SystemExit)


95
96
97
# File 'lib/tb/cmdutil.rb', line 95

def err(msg)
  raise SystemExit.new(1, msg)
end

#output_tbenum(te) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/tb/cmdutil.rb', line 179

def output_tbenum(te)
  filename = Tb::Cmd.opt_output
  if /\A([a-z0-9]{2,}):/ =~ filename
    fmt = $1
    filename = $'
  else
    fmt = nil
  end
  if !fmt
    case filename
    when /\.csv\z/
      fmt = 'csv'
    when /\.json\z/
      fmt = 'json'
    end
  end
  if fmt
    case fmt
    when 'csv'
      write_proc = lambda {|out| te.write_to_csv(out, !Tb::Cmd.opt_N) }
    when 'json'
      write_proc = lambda {|out| te.write_to_json(out) }
    else
      err("unexpected format: #{fmt.inspect}")
    end
  end
  write_proc ||= lambda {|out| te.write_to_csv(out, !Tb::Cmd.opt_N) }
  with_output(filename) {|out|
    write_proc.call(out)
  }
end

#parse_aggregator_spec(spec) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/tb/cmdutil.rb', line 99

def parse_aggregator_spec(spec)
  case spec
  when 'count'
    ['count', nil]
  when /\Asum\((.*)\)\z/
    ['sum', $1]
  when /\Aavg\((.*)\)\z/
    ['avg', $1]
  when /\Amax\((.*)\)\z/
    ['max', $1]
  when /\Amin\((.*)\)\z/
    ['min', $1]
  when /\Avalues\((.*)\)\z/
    ['values', $1]
  when /\Auniquevalues\((.*)\)\z/
    ['uniquevalues', $1]
  else
    raise ArgumentError, "unexpected aggregation spec: #{spec.inspect}"
  end
end

#parse_aggregator_spec2(spec) ⇒ Object



120
121
122
123
124
125
126
127
# File 'lib/tb/cmdutil.rb', line 120

def parse_aggregator_spec2(spec)
  name, field = parse_aggregator_spec(spec)
  func = Tb::Func::AggregationFunctions[name]
  if !func
    raise ArgumentError, "unexpected aggregation spec: #{spec.inspect}"
  end
  [func, field]
end

#split_csv_argument(arg) ⇒ Object



133
134
135
136
# File 'lib/tb/cmdutil.rb', line 133

def split_csv_argument(arg)
  Tb.csv_stream_input(arg) {|ary| return ary }
  return []
end

#split_field_list_argument(arg) ⇒ Object



129
130
131
# File 'lib/tb/cmdutil.rb', line 129

def split_field_list_argument(arg)
  split_csv_argument(arg).map {|f| f || '' }
end

#tablereader_open(filename, &b) ⇒ Object



138
139
140
# File 'lib/tb/cmdutil.rb', line 138

def tablereader_open(filename, &b)
  Tb.open_reader(filename, {:numeric=>Tb::Cmd.opt_N}, &b)
end

#tbl_generate_tsv(tbl, out) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/tb/cmdutil.rb', line 142

def tbl_generate_tsv(tbl, out)
  if Tb::Cmd.opt_N
    header = tbl.list_fields
    Tb.tsv_stream_output(out) {|gen|
      tbl.each {|rec|
        gen << rec.values_at(*header)
      }
    }
  else
    tbl.generate_tsv(out)
  end
end

#with_output(filename = Tb::Cmd.opt_output) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/tb/cmdutil.rb', line 155

def with_output(filename=Tb::Cmd.opt_output)
  if filename && filename != '-'
    tmp = filename + ".part"
    begin
      File.open(tmp, 'w') {|f|
        yield f
      }
      if File.exist?(filename) && FileUtils.compare_file(filename, tmp)
        File.unlink tmp
      else
        File.rename tmp, filename
      end
    ensure
      File.unlink tmp if File.exist? tmp
    end
  elsif $stdout.tty? && !Tb::Cmd.opt_no_pager
    Tb::Pager.open {|pager|
      yield pager
    }
  else
    yield $stdout
  end
end