#!/usr/bin/env ruby

require 'parsearg'

class C2T
  SPECIAL_METHODS = {
    '+'   => 'PLUS',
    '-'   => 'MINUS',
    '*'   => 'MUL',
    '/'   => 'DIV',
    '%'   => 'MOD',
    '**'  => 'POW',
    '&'   => 'AND',
    '|'   => 'OR',
    '^'   => 'XOR',
    '~'   => 'REV',
    '<<'  => 'LSHIFT',
    '>>'  => 'RSHIFT',
    '<'   => 'LT',
    '<='  => 'LE',
    '>'   => 'GT',
    '>='  => 'GE',
    '<=>' => 'CMP',
    '=='  => 'EQUAL',
    '===' => 'VERY_EQUAL',
    '=~'  => 'MATCH',
    '[]'  =>  'AREF',
    '[]=' => 'ASET',
    '-@'  => 'MINUS_AT',
    '+@'  => 'PLUS_AT'
  }

  def initialize
    @insert_assertion = true
  end

  def insert_assert=(do_insert)
    @insert_assertion = do_insert
  end

  def test_method_name(method)
    if SPECIAL_METHODS.include?(method)
      "test_#{SPECIAL_METHODS[method]}" + " # '#{method}'"
    else
      "test_#{method}"
    end
  end

  def test_singleton_method_name(m)
    if SPECIAL_METHODS.include?(m)
      "test_s_#{SPECIAL_METHODS[m]}" + " # singleton method '#{m}'"
    else 
      "test_s_#{m}"
    end
  end

  def def_test_method(m)
    assert_str = ''
    if @insert_assertion
      assert_str = 'assert_fail("untested")'
    end
    <<STR

  def #{m}
    #{assert_str}
  end
STR
  end

  def test_class_name(klass)
    "Test#{klass.gsub(':', '_')}"
  end

  def def_test_class(klass)
    "class #{test_class_name(klass)} < RUNIT::TestCase"
  end

  def def_test_singleton_methods(klass)
    str = ''
    klass.singleton_methods.collect{|m|
      test_singleton_method_name(m)
    }.sort.each do |m|
      str.concat def_test_method(m)
    end
    str
  end

  def def_test_initialize_method(klass)
    str = ''
    if klass.private_instance_methods.include?("initialize")
      str.concat def_test_method( test_singleton_method_name("new"))
    end
    str
  end

  def def_test_instance_methods(klass)
    str = ''
    klass.instance_methods.collect{|f|
      test_method_name(f)
    }.sort.each do |f|
      str.concat def_test_method(f)
    end
    str
  end

  def c2t(klass)
    if klass.instance_of?(String)
      str = def_test_class(klass)
    else
      str = def_test_class(klass.name)
    end
    str += "\n"
    if klass.kind_of?(Module)
      str += def_test_instance_methods(klass)
      str += def_test_initialize_method(klass)
      str += def_test_singleton_methods(klass)
    end
    str += "\nend\n"
  end
end

class TestFrame
  def initialize
    @c2t = C2T.new
  end

  def require_frame
    <<STR
require 'rubyunit'
STR
  end

  def require_target(file)
    "require '#{file}'"
  end

  def create_frame(klass, file=nil, do_insert_assert=true)
    @c2t.insert_assert = do_insert_assert
    str = require_frame
    if file
      require file
      str += require_target(file)
      str += "\n"
    end
    begin
      k = eval(klass)
    rescue NameError
      k = klass
    end
    str += "\n"
    str += @c2t.c2t(k)  
    str += "\n"
  end
end

def print_usage
  print <<USAGE_EOF
USAGE : #{File.basename($0)} [(--na|--noassertion)] class [file]
  --na   : do not insert assert_fail in each test methods.
  --noassertion : same as '--na'
USAGE_EOF
end

$USAGE='print_usage'

if $0 == __FILE__
  parseArgs(1, nil, nil, "na", "noassertion")
  do_insert_assert = (!$OPT_na && !$OPT_noassertion)
  tf = TestFrame.new
  print tf.create_frame(ARGV.shift, ARGV.shift, do_insert_assert)
end

