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

scalacss.Macros.scala Maven / Gradle / Ivy

There is a newer version: 0.5.6
Show newest version
package scalacss

import java.util.regex.Pattern
import scala.reflect.macros.blackbox.Context
import scala.reflect.NameTransformer

object Macros {

  private def name(c: Context): String = {
    val localName = c.internal.enclosingOwner.name.toString.trim

    // `style()` instead of `val x = style()` results in ""
    if (localName startsWith "<")
      ""
    else {

      // Try to extract a name, relative to the stylesheet class
      val className = c.prefix.actualType.typeSymbol.fullName
      val fullName  = c.internal.enclosingOwner.fullName
      if ((fullName.length > className.length + 1) && fullName.startsWith(className + ".")) {
        val relName = fullName.substring(className.length + 1).replace('.', '-')
        NameTransformer decode relName
      } else

        // Default to local name
        NameTransformer decode localName
    }
  }

  private def impl[A](c: Context, method: String): c.Expr[A] = {
    import c.universe._
    c.Expr(Apply(Ident(TermName(method)), Literal(Constant(name(c))) :: Nil))
  }

  // ===================================================================================================================

  import DslMacros._

  def implStyle    (c: Context): c.Expr[MStyle    ] = impl(c, "__macroStyle")
  def implStyleF   (c: Context): c.Expr[MStyleF   ] = impl(c, "__macroStyleF")
  def implKeyframes(c: Context): c.Expr[MKeyframes] = impl(c, "__macroKeyframes")

  trait DslMixin {
    protected def __macroStyle    (name: String): MStyle
    protected def __macroStyleF   (name: String): MStyleF
    protected def __macroKeyframes(name: String): MKeyframes
    protected def __macroKeyframe               : MStyle
    protected def __macroFontFace               : MFontFace

    final protected def style    : MStyle     = macro implStyle
    final protected def styleF   : MStyleF    = macro implStyleF
    final protected def keyframes: MKeyframes = macro implKeyframes
    final protected def keyframe : MStyle     = __macroKeyframe
    final protected def fontFace : MFontFace  = __macroFontFace
  }

  // ===================================================================================================================

  type Color = ValueT[ValueT.Color]

  object ColorLiteral {

    private def cssFnRegex(f: String, as: String*) = {
      val args = as mkString ","
      Pattern compile s"^$f\\($args\\)$$"
    }

    private def int = "(-?\\d+)"
    private def dbl = """(-?\d+(?:\.\d+)?|\.\d+)"""
    private def pct = s"$dbl%"

    private val ws   = "\\s+".r
    private val hex  = Pattern compile """^#(?:[0-9a-fA-F]{3}){1,2}$"""
    private val rgbI = cssFnRegex("rgb",  int, int, int)
    private val rgbP = cssFnRegex("rgb",  pct, pct, pct)
    private val rgba = cssFnRegex("rgba", int, int, int, dbl)
    private val hsl  = cssFnRegex("hsl",  int, pct, pct)
    private val hsla = cssFnRegex("hsla", int, pct, pct, dbl)

    def impl(c: Context)(args: c.Expr[Any]*): c.Expr[Color] = {
      import c.universe._

      c.prefix.tree match {
        case Apply(_, List(Apply(_, List(l @Literal(Constant(text0: String)))))) =>

          def fail(reason: String = null): Nothing = {
            var err = s"""Invalid colour literal: "$text0"."""
            if (reason ne null)
              err = s"$err $reason"
            c.abort(c.enclosingPosition, err)
          }

          val text = ws.replaceAllIn(text0, "").toLowerCase

          def pass = true
          def undecided = false

          def validateHex: Boolean =
            (text.charAt(0) == '#') && {
              if (hex.matcher(text).matches)
                pass
              else
                fail("Hex notation must be either #RRGGBB and #RGB.")
            }

          type V = String => Unit

          def validateInt(name: String, max: Int): V =
            s => {
              val i = s.toInt
              if (i < 0 || i > max)
                fail(s"Invalid $name value: $s")
            }

          def validateDbl(name: String, max: Double): V =
            s => {
              val d = s.toDouble
              if (d < 0 || d > max)
                fail(s"Invalid $name value: $s")
            }

          def validatePct(name: String): V =
            validateDbl(name, 100)

          def validateFn(p: Pattern, a: V, b: V, c: V, d: V = null): Boolean = {
            val m = p.matcher(text)
            if (m.matches) {
              a(m group 1)
              b(m group 2)
              c(m group 3)
              if (d ne null) d(m group 4)
              pass
            } else
              undecided
          }

          def ri = validateInt("red",   255)
          def gi = validateInt("green", 255)
          def bi = validateInt("blue",  255)
          def rp = validatePct("red")
          def gp = validatePct("green")
          def bp = validatePct("blue")
          def h  = validateInt("hue", 359)
          def s  = validatePct("saturation")
          def l  = validatePct("lightness")
          def a  = validateDbl("alpha", 1)

          def validateRgbI = validateFn(rgbI, ri, gi, bi)
          def validateRgbP = validateFn(rgbP, rp, gp, bp)
          def validateRgba = validateFn(rgba, ri, gi, bi, a)
          def validateHsl  = validateFn(hsl,  h, s, l)
          def validateHsla = validateFn(hsla, h, s, l, a)

          val validated =
            validateHex || validateRgbI || validateRgbP || validateRgba || validateHsl || validateHsla

          if (validated) {
            //println(showRaw(q"""_root_.scalacss.Color($text)"""))
            c.Expr(Apply(Select(Select(Ident(termNames.ROOTPKG), TermName("scalacss")), TermName("Color")),
              Literal(Constant(text)) :: Nil))
          } else
            fail()

        case t =>
          c.abort(c.enclosingPosition, s"Expected string literal. Got:\n$t($args)")
      }
    }
  }

  class ColourLiteral(val sc: StringContext) extends AnyVal {
    /** c"#fc6" provides a validates Color */
    def c(args: Any*): Color = macro ColorLiteral.impl
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy