読者です 読者をやめる 読者になる 読者になる

Crystal では何でもかんでも配列にしてあれこれするのは良くないぜ!

Rake のコードを Crystal に移植していたら、配列を関数の引数として展開できないせいでまずいことが起こった。

def standard_gazel_options(options)
  [
    [  "-A", "--all",
       "Show all tasks, even uncommented ones (in combination with -T or -D)",
       ->(value : String) { options.show_all_tasks = value }
    ], [
      "--backtrace=[OUT]",
      "Enable full backtrace.  OUT can be stderr (default) or stdout",
      ->(value : String) {
        options.backtrace = true
        #select_trace_output options, "backtrace", value
        value
      }
    ], [
      "-B", "--build-all",
      "Build all prerequisites,  including those which are up-to-date.",
      ->(value : String) {
        options.build_all = true
        value
      }
    ]#,
    #  []
  ]
end

parse = OptionParser.new do |opts|
  opts.banner = "gazel [-f <gazel file>] {options} targets..."
  opts.separator ""
  opts.separator "Options are ..."

  opts.on "-h", "--help", "Display this help message" do
    puts opts
    exit
  end

  standard_gazel_options(options).each do |args|
    opts.on *args                                   # エラー!!!
                                                    # Array を引数として展開することはできない!
  end
end

Crystal には Tuple があるので、それだったら展開できるんだけど、Array(T) から Tuple に変換する便利な関数が無いっぽい。
それに Crystal では、standard_gazel_options 関数が返す配列の型は Array(Array(String | String -> String)) なので、
要素が String なのか、String -> String なのかは確かめる必要がある。

それは激しくめんどくさいので、こういう場合は、Struct を作って、それの配列として standard_gazel_options 関数が返すことにしよう。
こんな風に、OptionDefine という構造体(Struct)を定義して:

struct OptionDefine
  property short_flag
  property long_flag
  property description
  property block

  def initialize(@short_flag, @long_flag, @description, @block)
  end
end

standard_gazel_options 関数をこういう風に書き換えよう:

def standard_gazel_options(options)
  [
    OptionDefine.new( "-A", "--all",
                      "Show all tasks, even uncommented ones (in combination with -T or -D)",
                      ->(value : String) { options.show_all_tasks = value } ),
    OptionDefine.new( nil, "--backtrace=[OUT]",
                      "Enable full backtrace.  OUT can be stderr (default) or stdout",
                      ->(value : String) {
                        options.backtrace = true
                        #select_trace_output options, "backtrace", value
                        value
                      } ),
    OptionDefine.new(  "-B", "--build-all",
                       "Build all prerequisites,  including those which are up-to-date.",
                       ->(value : String) {
                         options.build_all = true
                         value
                       } )
  ]
end

OptionDefine のメンバーを渡すところはこういう風に書くかもしれない:

  standard_gazel_options(options).each do |args|
    if args.short_flag.nil? then
      opts.on                  args.long_flag, args.description, &args.block
    else
      opts.on args.short_flag, args.long_flag, args.description, &args.block
    end
  end

(๑´ڡ`๑) いいかも