LoginSignup
1
0

More than 3 years have passed since last update.

CSVライブラリからTSVライブラリを作る ~col_sepなんて指定したくない~

Last updated at Posted at 2019-07-05

動機

スクレイピング結果をTSVで保存していくが、5回目ぐらいからcol_sep: "\t"を書くのが苦痛になってきた。
require "tsv"; tsv = TSV.open("path/to/file.tsv", "w");みたいに書きたい!書いてやる!!!

解答

ご自身のsite_rubyフォルダに下記のファイルを配置するだけ。

tsv.rb
# frozen_string_literal: true

require "csv"

class TSV < CSV
  DEFAULT_OPTIONS = {
    col_sep:            "\t",
    row_sep:            :auto,
    quote_char:         '"',
    field_size_limit:   nil,
    converters:         nil,
    unconverted_fields: nil,
    headers:            false,
    return_headers:     false,
    header_converters:  nil,
    skip_blanks:        false,
    force_quotes:       false,
    skip_lines:         nil,
    liberal_parsing:    false,
    quote_empty:        true,
  }.freeze

  def initialize(data,
                 col_sep: "\t",
                 row_sep: :auto,
                 quote_char: '"',
                 field_size_limit: nil,
                 converters: nil,
                 unconverted_fields: nil,
                 headers: false,
                 return_headers: false,
                 write_headers: nil,
                 header_converters: nil,
                 skip_blanks: false,
                 force_quotes: false,
                 skip_lines: nil,
                 liberal_parsing: false,
                 internal_encoding: nil,
                 external_encoding: nil,
                 encoding: nil,
                 nil_value: nil,
                 empty_value: "",
                 quote_empty: true)
    super
  end
end

解答に至るまで

CSVライブラリでcol_sep: "\t"と指定されていれば、望むTSVライブラリそのものです。
CSVライブラリで用いられるキーワード引数のデフォルト値だけ変更したTSVクラスを定義し、そのファイルを$:(= $LOAD_PATH= $-I)のどこかに配置すれば良いでしょう。

CSVライブラリの観察

まずCSVライブラリがどこにあるのかを探します。
ライブラリがどこにあるのか探すコマンド gem which を用いましょう。

>gem which csv
C:/Ruby261-x64/lib/ruby/2.6.0/csv.rb

# こんな感じに配置されている
C:/Ruby261-x64/lib/ruby/2.6.0
├── 他のファイル・ディレクトリ郡
├── csv.rb
└── csv
    ├── core_ext
    │   ├── array.rb
    │   └── string.rb
    ├── fields_converter.rb
    ├── match_p.rb
    ├── parser.rb
    ├── row.rb
    ├── table.rb
    ├── version.rb
    └── writer.rb

これらのファイルからキーワード引数col_sepと重要そうな部分だけ見ていきます。

csv.rb

class CSV

  # 略

  DEFAULT_OPTIONS = {
    col_sep:            ",", # ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
    row_sep:            :auto,
    quote_char:         '"',
    field_size_limit:   nil,
    converters:         nil,
    unconverted_fields: nil,
    headers:            false,
    return_headers:     false,
    header_converters:  nil,
    skip_blanks:        false,
    force_quotes:       false,
    skip_lines:         nil,
    liberal_parsing:    false,
    quote_empty:        true,
  }.freeze

  # 略

  def initialize(data,
                 col_sep: ",", # ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
                 row_sep: :auto,
                 quote_char: '"',
                 field_size_limit: nil,
                 converters: nil,
                 unconverted_fields: nil,
                 headers: false,
                 return_headers: false,
                 write_headers: nil,
                 header_converters: nil,
                 skip_blanks: false,
                 force_quotes: false,
                 skip_lines: nil,
                 liberal_parsing: false,
                 internal_encoding: nil,
                 external_encoding: nil,
                 encoding: nil,
                 nil_value: nil,
                 empty_value: "",
                 quote_empty: true)
    raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?

    # create the IO object we will read from
    @io = data.is_a?(String) ? StringIO.new(data) : data
    @encoding = determine_encoding(encoding, internal_encoding)

    @base_fields_converter_options = {
      nil_value: nil_value,
      empty_value: empty_value,
    }
    @initial_converters = converters
    @initial_header_converters = header_converters

    @parser_options = {
      column_separator: col_sep, # ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
      row_separator: row_sep,
      quote_character: quote_char,
      field_size_limit: field_size_limit,
      unconverted_fields: unconverted_fields,
      headers: headers,
      return_headers: return_headers,
      skip_blanks: skip_blanks,
      skip_lines: skip_lines,
      liberal_parsing: liberal_parsing,
      encoding: @encoding,
      nil_value: nil_value,
      empty_value: empty_value,
    }
    @parser = nil

    @writer_options = {
      encoding: @encoding,
      force_encoding: (not encoding.nil?),
      force_quotes: force_quotes,
      headers: headers,
      write_headers: write_headers,
      column_separator: col_sep, # ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
      row_separator: row_sep,
      quote_character: quote_char,
      quote_empty: quote_empty,
    }

    @writer = nil
    writer if @writer_options[:write_headers]
  end

 # 略

end

他に重要そうなファイルはありませんでした。
csv.rbDEFAULT_OPTIONSinitializecol_sepの値を変更すれば良さそうです。

TSVライブラリを作る

わざわざCSVライブラリを構成する全ファイルをどこかにコピーして編集して…なんて面倒です。
CSVクラスを継承したTSVクラスを定義し、col_sepの部分だけ変えます。
コードは解答を参照。

引数のデフォルト値を変えただけなので、initializeの中身はただのsuperでOKです。
superは、メソッド呼び出し(super・ブロック付き・yield) (Ruby 2.6.0)で以下のように書かれています。

super は現在のメソッドがオーバーライドしているメソッドを呼び出します。括弧と引数が省略された場合には現在のメソッドの引数がそのまま引き 渡されます。

TSVライブラリをrequireできる場所に配置する

自分の環境でrequireできる場所がどこか確認してみます。

>ruby -e 'puts $:'
C:/Ruby261-x64/lib/ruby/gems/2.6.0/gems/did_you_mean-1.3.0/lib
C:/Ruby261-x64/lib/ruby/site_ruby/2.6.0
C:/Ruby261-x64/lib/ruby/site_ruby/2.6.0/x64-msvcrt
C:/Ruby261-x64/lib/ruby/site_ruby
C:/Ruby261-x64/lib/ruby/vendor_ruby/2.6.0
C:/Ruby261-x64/lib/ruby/vendor_ruby/2.6.0/x64-msvcrt
C:/Ruby261-x64/lib/ruby/vendor_ruby
C:/Ruby261-x64/lib/ruby/2.6.0
C:/Ruby261-x64/lib/ruby/2.6.0/x64-mingw32

置き場所が沢山あって困りますね。variable $-I (Ruby 2.6.0)を参考にしましょう。
私はベンダーでもないですし、TSVライブラリはCSVライブラリに依存しているのでバージョン依存でもないでしょう。
site_ruby(C:/Ruby261-x64/lib/ruby/site_ruby)に配置することにしました。

anyenvrbenvを使用している場合は、環境変数RUBYLIBを設定し、そこに配置すればOKです。
参考:環境変数 (Ruby 2.6.0)

感想

EASYで助かった。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0