結局、UI部分をモジュールをクラスに置き換えた.

前回までモジュールだったUI部分をクラス化して、injectするように.
いつもの見慣れた構造.こっちのほうが読みやすいし、テストし易い.
Spyというキーワードを使ったのは、Test Spy at XUnitPatterns.comを参考に.
set_uiよりinject_uiを使ったのは、師匠の影響.
スパイがセットされるより、スパイがインジェクトされるほうが、スパイっぽいw


最近書いているのは、今まで書いたGUIプログラミングとはちょっと違う感覚をもっている.
MVCモデルというより、UnixのパイプアンドフィルターにややこしいGUIとかが、挟まった感じ.
ややこしいのは外に追いやって、injectすると、テストし易くなる.
たぶん、ファイル検索系もややこしいところなので分けたほうが、テストし易くなると思う.

require File.dirname(__FILE__) + '/../../lib/quickmate/choice_switch_command'

module QuickMate
  describe ChoiceSwitchCommand, "file choice targets" do
    def choice(expected)
      Pairs.new(expected)
    end
    
    before(:each) do
      @spy_ui = SpyUI.new
      @command = ChoiceSwitchCommand.new
      @command.inject_ui(@spy_ui)
      @fixtures_path = "#{ENV["TM_PROJECT_DIRECTORY"]}/Support/fixtures/exist_file"
    end

    it "should choice many file when current file is lib (one to many)" do
      @command.open_pair("#{@fixtures_path}/lib/foo/one_to_many.rb")
      @spy_ui.actual_paths.should == [
                    "#{@fixtures_path}/spec/foo/feature_1-one_to_many_spec.rb",
                    "#{@fixtures_path}/spec/foo/feature_2-one_to_many_spec.rb",
                    "#{@fixtures_path}/spec/foo/one_to_many_spec.rb"]
    end

    it "should choice one file when current file is spec (many to one)" do
      @command.open_pair("#{@fixtures_path}/spec/foo/feature_1-one_to_many_spec.rb")
      @spy_ui.actual_paths.should == ["#{@fixtures_path}/lib/foo/one_to_many.rb"]
    end

    it "should choice one file when current file is lib (one to one)" do
      @command.open_pair("#{@fixtures_path}/lib/foo/one_to_one.rb")
      @spy_ui.actual_paths.should == ["#{@fixtures_path}/spec/foo/one_to_one_spec.rb"]
    end
  end
  
  class SpyUI
    attr_reader :actual_paths
    def open_menu(path_line_views)
      @actual_paths = path_line_views.collect { |path_line_views| 
        path_line_views[0]
      }
    end    
  end
end
require File.dirname(__FILE__) + '/ui'

module QuickMate
  class ChoiceSwitchCommand
    
    PRE, TYPE, POST, FILE = 0, 1, 2, 3
    
    def initialize(
      config = { 
        :project_dir  => ENV["TM_PROJECT_DIRECTORY"],
        :spec_dir     => "/spec",
        :product_dir  => "/lib",
        # :prefix_spec  => "[^/]*-", # PEND ややこしくなったのでやめた.
        :sufix_spec   => "_spec", 
      })
      @conf = config
      @ui = UI.new
    end
    
    def inject_ui(ui)
      @ui = ui
    end

    def open_pair(path)
      token = token_pre_type_post_file(path)
      paths = select_pair_targets(token)
      if paths.size == 0
        # TODO 新規ファイルを作成ができるようにすること
        puts "not found pair. not support create file"
      else
        @ui.open_menu(path_line_views(paths))
      end
    end

    def path_line_views(paths) 
      paths.collect { |path| 
        [path, 1, File.basename(path)]
      }
    end
    
    def select_pair_targets(token)
      if token[TYPE] == @conf[:spec_dir]
        return select_files(lib_pattern(token))
      elsif token[TYPE] == @conf[:product_dir]
        return select_files(spec_pattern(token))
      end
      puts "select pair files error!! toke => #{ toeken.inspect }, @conf=>#{ @conf.inspect }"
    end

    def select_files(pattern)
      Dir.glob("#{@conf[:project_dir]}/**/*.rb").select{ |file| /#{pattern}/ =~ file }
    end

    def token_pre_type_post_file(path)
      dir_base = File.split(path)
      with_post = "^(.*?)(/lib|/app|/spec)(/.*?)$"
      dir_base[PRE] =~ /#{ with_post }/
      return $1, $2, $3, dir_base[1] unless $1 == nil
      # パッケージディレクトリがない場合は空文字列で補完する.
      without_post = "(.*?)(/lib|/app|/spec)"
      dir_base[PRE] =~ /#{ without_post }/
      return $1, $2, '', dir_base[TYPE] unless $1 == nil
      puts "token error !! path => #{ path }, @conf => #{ @conf.inspect }"
    end
    
    def spec_pattern(token)
      directory = "#{ token[PRE] }#{ @conf[:spec_dir] }#{ token[POST] }"
      file = "[^/]*#{ token[FILE].sub(".rb", "") }#{ @conf[:sufix_spec] }.rb$"
      "#{directory}/#{file}"
    end
    
    def lib_pattern(token)
      directory = "#{ token[PRE] }#{ @conf[:product_dir] }#{ token[POST] }"
      # プレフィックスとベースネームのトークンを'-'で プレフィックスにFixture名とか付けるつもり
      with_prefix = "^(.*?)-(.*?)#{ @conf[:sufix_spec] }.rb$"
      token[FILE] =~ /#{ with_prefix }/
      file = nil
      file = $2 + ".rb" unless $1 == nil
      without_prefix = "^(.*?)#{ @conf[:sufix_spec] }.rb$"
      token[FILE] =~ /#{ without_prefix }/
      file = $1 + ".rb" if file == nil
      "#{directory}/#{file}$"
    end
  end
end