( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

Scalaでmethod_missing的なことができる"applyDynamic"を使って"Term::ANSIColor"みたいなの作った

Scala2.9から、DynamicってのでRubyのmethod_missing的なことができるようになる.....ハズだったんだが、
2.9正式リリースの数時間前にいきなりこの機能はサポートされなくなった。


といっても全く使えなくなったわけではなく、scalacやscalaコマンドに-Xexperimentalオプションを使えば使えるようになる。


で、Dynamicってどんなのかというと、Dynamicというトレイトを実装したクラスは、定義されていないメソッドが呼び出されるとapplyDynamic(methodName:String)(args:Any*)というメソッドが代わりに呼び出されるようになる。

  scala> class Foo extends Dynamic {
       |   def applyDynamic(methodName:String)(args:Any*) = {
       |     println( "methodName = " + methodName )
       |     println( "args = " + args)
       |   }
       | }
  defined class Foo

  scala> val foo = new Foo
  foo: Foo = Foo@4c0a7e2e

  scala> foo.bar
  dynatype: $line7.$read.$iw.$iw.$iw.$iw.foo.applyDynamic("bar")()
  methodName = bar
  args = List()


で、こいつを使って、Rubyの"Term::ANSIColor"みたいなヤツを書いてみた。

f:id:yuroyoro:20110527203717p:image:w640

/**
 * This library can be used to color/uncolor strings using ANSI escape sequences.
 *
 * To implementation, referenced Ruby's "Term::ANSIColor"(http://term-ansicolor.rubyforge.org)
 *
 * NOTE
 *  this version's implementation is using Dynamic trait and applyDynamic.
 *  "applyDynamic" is not supported yet.
 *  to enable this feature, you should specify -Xexperimental option  when running scala/scalac command.
 *
 * Usage:
 *  val coloredStr =  "This text would be colored RED!!".colored.red + " and this part would be BLUE!!".colored.blue 
 *  println(coloredStr)
 *
 */

object Colored {
  implicit def stringToColered(s:String) = Colored(s)
  implicit def ColoredToString(c:Colored) = c.toString

  val attributes = Map(
    'clear         ->   0,
    'reset         ->   0,
    'bold          ->   1,
    'dark          ->   2,
    'italic        ->   3,
    'underline     ->   4,
    'underscore    ->   4,
    'blink         ->   5,
    'rapid_blink   ->   6,
    'negative      ->   7,
    'concealed     ->   8,
    'strikethrough ->   9,
    'black         ->  30,
    'red           ->  31,
    'green         ->  32,
    'yellow        ->  33,
    'blue          ->  34,
    'magenta       ->  35,
    'cyan          ->  36,
    'white         ->  37,
    'onBlack      ->  40,
    'onRed        ->  41,
    'onGreen      ->  42,
    'onYellow     ->  43,
    'onBlue       ->  44,
    'onMagenta    ->  45,
    'onCyan       ->  46,
    'onWhite      ->  47
  ).map{case (k, v) => (k, "\033[%dm" format v) }

  private val Reset = escape('reset) getOrElse(0)

  private def escape(attr:Symbol):Option[String] = attributes.get(attr)
  private def escape(attr:String):Option[String] = escape(Symbol(attr))

  private def escaped(attr:Symbol)(s: => String):Colored = Colored( escape(attr) + s )
  private def escaped(attr:String)(s: => String):Colored = escaped(Symbol(attr))(s)

  def apply(s:String) = new Colored{ val str = s }
  val colored = Colored("")
}

trait Colored extends Dynamic {
  val str:String

  def +(other:String):Colored = Colored( this.toString + other)
  def +(other:Colored):Colored = Colored( this.toString + other.toString )

  def colored = this

  def applyDynamic(attr: String)(args: Any*):Colored =
    coloring(attr) + args.headOption.getOrElse("")

  private def coloring(attr:String) =
    Colored.escape(attr) map{ esc => Colored(esc + str) } getOrElse(this)

  override def toString = str + Colored.Reset

}

object Main extends App {
import Colored._

println( "This text would be colored RED!!".colored.red + " and this part would be BLUE!!".colored.blue )
println( "白字に赤背景であばばばばばばばば".colored.onWhiltecolored.onRed.white)

}


F.Y.I