All Downloads are FREE. Search and download functionalities are using the official Maven repository.

breeze.config.GenerateHelp.scala Maven / Gradle / Ivy

The newest version!
package breeze.config

import com.thoughtworks.paranamer.AdaptiveParanamer
import java.lang.reflect.Type
import collection.mutable.ArrayBuffer
import java.io.File
import java.{lang=>jl}

import ReflectionUtils._

/**
 *  Generates a Help message from a case class. If a constructor parameter has a "Help" annotation
 *  present, it will display that along with basic type information.
 * @author dlwh
 */
object GenerateHelp {
  /**
   * Generates help for the given manifest
   */
  def apply[C:Manifest](conf: Configuration = Configuration.empty):String = {
    val man = implicitly[Manifest[C]]
    val reader = new AdaptiveParanamer()

    def recGen(staticManifest: Manifest[_], prefix: String):Seq[Format] = {
      val clss = staticManifest.erasure
      val ann = clss.getAnnotation(classOf[Help])
      val res = new ArrayBuffer[Format]
      if(ann != null) {
        res += Break += Group(prefix,clss.getName,ann.text) += Break
      }

      val dynamicClass: Class[_] = conf.recursiveGetProperty(prefix).map{
        Class.forName(_)
      } getOrElse (clss)
      if (dynamicClass.getConstructors.isEmpty)
        return res

      val staticTypeVars: Seq[String] = staticManifest.erasure.getTypeParameters.map(_.toString)
      val staticTypeVals: Seq[OptManifest[_]] = staticManifest.typeArguments
      val staticTypeMap: Map[String, OptManifest[_]] = (staticTypeVars zip staticTypeVals).toMap withDefaultValue (NoManifest)

      val dynamicTypeMap = solveTypes(staticTypeMap, staticManifest.erasure, dynamicClass)

      // Handle ctor parameters
      val toRecurse = ArrayBuffer[(String,Manifest[_])]()

      val ctor = dynamicClass.getConstructors apply 0
      val paramNames = reader.lookupParameterNames(ctor)
      val defaults = lookupDefaultValues(dynamicClass, paramNames)
      val anns = ctor.getParameterAnnotations
      val typedParams = ctor.getGenericParameterTypes.map { mkManifest(dynamicTypeMap, _)}

      for( i <- 0 until paramNames.length) {
        val myAnns = anns(i)
        val tpe = typedParams(i)
        val name = paramNames(i)
        val default = try { defaults(i)().toString } catch { case e: Exception => ""}
        val ann = myAnns.collectFirst{case h: Help => h}
        ann match {
          case None if isPrimitive(tpe.erasure) =>
            res += Param(wrap(prefix,name), prettyString(tpe),default, "")
          case Some(help) =>
            res += Param(wrap(prefix,name), prettyString(tpe),default, help.text)
          case _ =>
        }
        if(!isPrimitive(tpe.erasure)) {
          toRecurse += (name -> tpe)
        }
      }
      for( (name,tpe) <- toRecurse) {
        res ++= recGen(tpe,wrap(prefix,name))
      }

      res
    }

    val formats = recGen(man,"")
    val paramSplit = formats.foldLeft(0)(_ max _.paramLength)
    val minLength = formats.foldLeft(0)(_ max _.minLineLength)
    val buf = new StringBuilder()
    formats.foldLeft(buf) { (b,s) =>
      b ++= s.mkString(paramSplit - s.paramLength, minLength)
      b += '\n'
      b
    }

    buf.toString
  }

  private trait Format {
    def paramLength: Int = 0
    def minLineLength: Int = 0
    def mkString(splitWidth:Int, lineLength: Int):String
  }

  private case class Param(name: String, typeString: String, default: String, helpString: String) extends Format {
    override def paramLength = name.length + typeString.length + default.length + {if(default.length == 0) 4 else 7}
    override def minLineLength: Int = paramLength + helpString.length + 1
    def mkString(splitWidth: Int, lineLength: Int) = {
      "--" + name + ": " + typeString + {if(default.length == 0) "" else  " = " + default} +  (" " * splitWidth) + "  " + helpString
    }
  }

  private case object Break extends Format {
    def mkString(splitWidth:Int, lineLength: Int) = "=" * lineLength.min(10)
  }
  private case class Group(name: String, className: String, helpString: String) extends Format {
    val string = "Parameter Group " + name + " (" + className +")\n"
    def mkString(splitWidth:Int, lineLength: Int) = string +"\n" + helpString
    override def minLineLength = string.length max helpString.length
  }



  private def prettyString(tpe: Manifest[_]) = tpe match {
    case Manifest.Int => "Int"
    case Manifest.Float => "Float"
    case Manifest.Boolean => "Boolean"
    case Manifest.Long => "Long"
    case Manifest.Double => "Double"
    case Manifest.Char => "Char"
    case Manifest.Byte => "Byte"
    case c => if(c.erasure == classOf[String]) "String" else if (c.erasure == classOf[File]) "File" else tpe.toString
  }

  private def wrap(prefix: String, name: String):String = {
    if(prefix.isEmpty) name
    else prefix + "." + name
  }

  private val STRING = classOf[String]
  private val FILE = classOf[java.io.File]

  private def isPrimitive(tpe: Type):Boolean = tpe match {
    case jl.Integer.TYPE => true
    case jl.Float.TYPE => true
    case jl.Boolean.TYPE => true
    case jl.Long.TYPE => true
    case jl.Double.TYPE => true
    case jl.Character.TYPE => true
    case jl.Byte.TYPE => true
    case STRING => true
    case FILE => true
    case _ => false
  }

  @Help(text="Recursion works!")
  case class Rec[T](i: T)
  case class Params(str: Int, bo: Boolean, @Help(text="woooooo") f: File , rec: Rec[Int])

  def main(args: Array[String]) {
    println(GenerateHelp[Params]())
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy