package scala import java.text.DecimalFormat import language.implicitConversions import org.junit.Test import org.junit.Assert._ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import scala.tools.testing.AssertUtil._ object StringContextTestUtils { private val decimalSeparator: Char = new DecimalFormat().getDecimalFormatSymbols().getDecimalSeparator() private val numberPattern = """(\d+)\.(\d+.*)""".r implicit class StringContextOps(val sc: StringContext) extends AnyVal { // Use this String interpolator to avoid problems with a locale-dependent decimal mark. def locally(numbers: String*): String = { val numbersWithCorrectLocale = numbers.map(applyProperLocale) sc.s(numbersWithCorrectLocale: _*) } // Handles cases like locally"3.14" - it's prettier than locally"${"3.14"}". def locally(): String = sc.parts.map(applyProperLocale).mkString private def applyProperLocale(number: String): String = { val numberPattern(intPart, fractionalPartAndSuffix) = number s"$intPart$decimalSeparator$fractionalPartAndSuffix" } } } @RunWith(classOf[JUnit4]) class StringContextTest { import StringContext._ import StringContextTestUtils.StringContextOps @Test def noEscape() = { val s = "string" val res = processEscapes(s) assertEquals(s, res) } @Test def tabbed() = { val s = """a\tb""" val res = processEscapes(s) assertEquals("a\tb", res) } @Test def quoted() = { val s = """hello, \"world\"""" val res = processEscapes(s) assertEquals("""hello, "world"""", res) } @Test def octal() = { val s = """\123cala""" val res = treatEscapes(s) assertEquals("Scala", res) } @Test def doubled() = { val s = """\123cala\123yntax""" val res = treatEscapes(s) assertEquals("ScalaSyntax", res) } @Test def badly() = assertThrows[InvalidEscapeException] { val s = """Scala\""" val res = treatEscapes(s) assertEquals("Scala", res) } @Test def noOctal() = assertThrows[InvalidEscapeException] { val s = """\123cala""" val res = processEscapes(s) assertEquals("Scala", res) } @Test def t6631_baseline() = assertEquals("\f\r\n\t", s"""\f\r\n\t""") @Test def t6631_badEscape() = assertThrows[InvalidEscapeException] { s"""\x""" } // verifying that the standard interpolators can be supplanted @Test def antiHijack_?() = { object AllYourStringsAreBelongToMe { case class StringContext(args: Any*) { def s(args: Any) = "!!!!" } } import AllYourStringsAreBelongToMe._ //assertEquals("????", s"????") assertEquals("!!!!", s"????") // OK to hijack core interpolator ids } @Test def fIf() = { val res = f"${if (true) 2.5 else 2.5}%.2f" val expected = locally"2.50" assertEquals(expected, res) } @Test def fIfNot() = { val res = f"${if (false) 2.5 else 3.5}%.2f" val expected = locally"3.50" assertEquals(expected, res) } @Test def fHeteroArgs() = { val res = f"${3.14}%.2f rounds to ${3}%d" val expected = locally"${"3.14"} rounds to 3" assertEquals(expected, res) } @Test def `f interpolator baseline`(): Unit = { implicit def stringToBoolean(s: String): Boolean = java.lang.Boolean.parseBoolean(s) implicit def stringToChar(s: String): Char = s(0) implicit def str2fmt(s: String): java.util.Formattable = new java.util.Formattable { def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("%s", s) } val b_true = true val b_false = false val i = 42 val f_zero = 0.0 val f_zero_- = -0.0 val s = "Scala" val fff = new java.util.Formattable { def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("4") } import java.util.{ Calendar, Locale } val c = Calendar.getInstance(Locale.US) c.set(2012, Calendar.MAY, 26) implicit def strToDate(x: String): Calendar = c val ss = List[(String, String)] ( // 'b' / 'B' (category: general) // ----------------------------- f"${b_false}%b" -> "false", f"${b_true}%b" -> "true", f"${null}%b" -> "false", f"${false}%b" -> "false", f"${true}%b" -> "true", f"${true && false}%b" -> "false", f"${new java.lang.Boolean(false)}%b" -> "false", f"${new java.lang.Boolean(true)}%b" -> "true", f"${null}%B" -> "FALSE", f"${false}%B" -> "FALSE", f"${true}%B" -> "TRUE", f"${new java.lang.Boolean(false)}%B" -> "FALSE", f"${new java.lang.Boolean(true)}%B" -> "TRUE", f"${"true"}%b" -> "true", f"${"false"}%b"-> "false", // 'h' | 'H' (category: general) // ----------------------------- f"${null}%h" -> "null", f"${f_zero}%h" -> "0", f"${f_zero_-}%h" -> "80000000", f"${s}%h" -> "4c01926", f"${null}%H" -> "NULL", f"${s}%H" -> "4C01926", // 's' | 'S' (category: general) // ----------------------------- f"${null}%s" -> "null", f"${null}%S" -> "NULL", f"${s}%s" -> "Scala", f"${s}%S" -> "SCALA", f"${5}" -> "5", f"${i}" -> "42", f"${'foo}" -> "'foo", f"${Thread.State.NEW}" -> "NEW", // 'c' | 'C' (category: character) // ------------------------------- f"${120:Char}%c" -> "x", f"${120:Byte}%c" -> "x", f"${120:Short}%c" -> "x", f"${120:Int}%c" -> "x", f"${new java.lang.Character('x')}%c" -> "x", f"${new java.lang.Byte(120:Byte)}%c" -> "x", f"${new java.lang.Short(120:Short)}%c" -> "x", f"${new java.lang.Integer(120)}%c" -> "x", f"${'x' : java.lang.Character}%c" -> "x", f"${(120:Byte) : java.lang.Byte}%c" -> "x", f"${(120:Short) : java.lang.Short}%c" -> "x", f"${120 : java.lang.Integer}%c" -> "x", f"${"Scala"}%c" -> "S", // 'd' | 'o' | 'x' | 'X' (category: integral) // ------------------------------------------ f"${120:Byte}%d" -> "120", f"${120:Short}%d" -> "120", f"${120:Int}%d" -> "120", f"${120:Long}%d" -> "120", f"${60 * 2}%d" -> "120", f"${new java.lang.Byte(120:Byte)}%d" -> "120", f"${new java.lang.Short(120:Short)}%d" -> "120", f"${new java.lang.Integer(120)}%d" -> "120", f"${new java.lang.Long(120)}%d" -> "120", f"${120 : java.lang.Integer}%d" -> "120", f"${120 : java.lang.Long}%d" -> "120", f"${BigInt(120)}%d" -> "120", f"${new java.math.BigInteger("120")}%d" -> "120", f"${4}%#10X" -> " 0X4", f"She is ${fff}%#s feet tall." -> "She is 4 feet tall.", f"Just want to say ${"hello, world"}%#s..." -> "Just want to say hello, world...", { implicit val strToShort = (s: String) => java.lang.Short.parseShort(s) ; f"${"120"}%d" } -> "120", { implicit val strToInt = (s: String) => 42 ; f"${"120"}%d" } -> "42", // 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' (category: floating point) // ------------------------------------------------------------------ f"${3.4f}%e" -> locally"3.400000e+00", f"${3.4}%e" -> locally"3.400000e+00", f"${3.4f : java.lang.Float}%e" -> locally"3.400000e+00", f"${3.4 : java.lang.Double}%e" -> locally"3.400000e+00", f"${BigDecimal(3.4)}%e" -> locally"3.400000e+00", f"${new java.math.BigDecimal(3.4)}%e" -> locally"3.400000e+00", f"${3}%e" -> locally"3.000000e+00", f"${3L}%e" -> locally"3.000000e+00", // 't' | 'T' (category: date/time) // ------------------------------- f"${c}%TD" -> "05/26/12", f"${c.getTime}%TD" -> "05/26/12", f"${c.getTime.getTime}%TD" -> "05/26/12", f"""${"1234"}%TD""" -> "05/26/12", // literals and arg indexes f"%%" -> "%", f" mind%n------%nmatter" -> """| mind |------ |matter""".stripMargin.lines.mkString(compat.Platform.EOL), f"${i}%d % "42 42 9", f"${7}%d % "7 7 9", f"${7}%d %2$$d ${9}%d" -> "7 9 9", f"${null}%d % "null FALSE", f"${5: Any}" -> "5", f"${5}%s% "55", f"${3.14}%s,% locally"3.14,${"3.140000"}", f"z" -> "z" ) for ((f, s) <- ss) assertEquals(s, f) } }