[英]Indentation preserving string interpolation in scala
I was wondering if there is any way of preserving indentation while doing string interpolation in scala.我想知道在 Scala 中进行字符串插值时是否有任何方法可以保留缩进。 Essentially, I was wondering if I could interpose my own StringContext.本质上,我想知道是否可以插入我自己的 StringContext。 Macros would address this problem, but I'd like to wait until they are official.宏可以解决这个问题,但我想等到它们正式发布。
This is what I want:这就是我要的:
val x = "line1 \nline2"
val str = s"> ${x}"
str should evaluate to str 应该评估为
> line1
line2
Answering my question, and converting Daniel Sobral's very helpful answer to code.回答我的问题,并将 Daniel Sobral 非常有用的答案转换为代码。 Hopefully it will be of use to someone else with the same issue.希望它对有同样问题的其他人有用。 I have not used implicit classes since I am still pre-2.10.我还没有使用隐式类,因为我仍然是 2.10 之前的版本。
import Indenter._
and use string interpolation like so e" $foo "
import Indenter._
并像这样使用字符串插值e" $foo "
class Foo
x: Int
y: String
z: Double
should print应该打印
class IndentStringContext(sc: StringContext) {
def e(args: Any*):String = {
val sb = new StringBuilder()
for ((s, a) <- sc.parts zip args) {
sb append s
val ind = getindent(s)
if (ind.size > 0) {
sb append a.toString().replaceAll("\n", "\n" + ind)
} else {
sb append a.toString()
}
}
if (sc.parts.size > args.size)
sb append sc.parts.last
sb.toString()
}
// get white indent after the last new line, if any
def getindent(str: String): String = {
val lastnl = str.lastIndexOf("\n")
if (lastnl == -1) ""
else {
val ind = str.substring(lastnl + 1)
if (ind.trim.isEmpty) ind // ind is all whitespace. Use this
else ""
}
}
}
object Indenter {
// top level implicit defs allowed only in 2.10 and above
implicit def toISC(sc: StringContext) = new IndentStringContext(sc)
}
class IndentStringContext(sc: StringContext) { def e(args: Any*):String = { val sb = new StringBuilder() for ((s, a) <- sc.parts zip args) { sb append s val ind = getindent(s) if (ind.size > 0) { sb append a.toString().replaceAll("\\n", "\\n" + ind) } else { sb append a.toString() } } if (sc.parts.size > args.size) sb append sc.parts.last sb.toString() } // get white indent after the last new line, if any def getindent(str: String): String = { val lastnl = str.lastIndexOf("\\n") if (lastnl == -1) "" else { val ind = str.substring(lastnl + 1) if (ind.trim.isEmpty) ind // ind is all whitespace. Use this else "" } } } object Indenter { // top level implicit defs allowed only in 2.10 and above implicit def toISC(sc: StringContext) = new IndentStringContext(sc) }
You can write your own interpolators, and you can shadow the standard interpolators with your own.您可以编写自己的内插器,也可以使用自己的内插器遮蔽标准内插器。 Now, I have no idea what's the semantic behind your example, so I'm not even going to try.现在,我不知道你的例子背后的语义是什么,所以我什至不打算尝试。
Check out my presentation on Scala 2.10 on either Slideshare orSpeakerDeck , as they contain examples on all the manners in which you can write/override interpolators.在Slideshare或SpeakerDeck上查看我关于 Scala 2.10 的演示,因为它们包含有关您可以编写/覆盖插值器的所有方式的示例。 Starts on slide 40 (for now -- the presentation might be updated until 2.10 is finally out).从幻灯片 40 开始(目前 - 演示文稿可能会更新到 2.10 最终发布)。
For Anybody seeking a post 2.10 answer:对于任何寻求帖子 2.10 答案的人:
object Interpolators {
implicit class Regex(sc: StringContext) {
def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*)
}
implicit class IndentHelper(val sc: StringContext) extends AnyVal {
import sc._
def process = StringContext.treatEscapes _
def ind(args: Any*): String = {
checkLengths(args)
parts.zipAll(args, "", "").foldLeft("") {
case (a, (part, arg)) =>
val processed = process(part)
val prefix = processed.split("\n").last match {
case r"""([\s|]+)$d.*""" => d
case _ => ""
}
val argLn = arg.toString
.split("\n")
val len = argLn.length
// Todo: Fix newline bugs
val indented = argLn.zipWithIndex.map {
case (s, i) =>
val res = if (i < 1) { s } else { prefix + s }
if (i == len - 1) { res } else { res + "\n" }
}.mkString
a + processed + indented
}
}
}
}
Here's a short solution.这是一个简短的解决方案。 Full code and tests on Scastie . Scastie上的完整代码和测试。 There are two versions there, a plain indented
interpolator, but also a slightly more complex indentedWithStripMargin
interpolator which allows it to be a bit more readable:那里有两个版本,一个简单的indented
插值器,还有一个稍微复杂的indentedWithStripMargin
插值器,它使它更具可读性:
assert(indentedWithStripMargin"""abc
|123456${"foo\nbar"}-${"Line1\nLine2"}""" == s"""|abc
|123456foo
| bar-Line1
| Line2""".stripMargin)
Here is the core function:这是核心功能:
def indentedHelper(parts: List[String], args: List[String]): String = {
// In string interpolation, there is always one more string than argument
assert(parts.size == 1+args.size)
(parts, args) match {
// The simple case is where there is one part (and therefore zero args). In that case,
// we just return the string as-is:
case (part0 :: Nil, Nil) => part0
// If there is more than one part, we can simply take the first two parts and the first arg,
// merge them together into one part, and then use recursion. In other words, we rewrite
// indented"A ${10/10} B ${2} C ${3} D ${4} E"
// as
// indented"A 1 B ${2} C ${3} D ${4} E"
// and then we can rely on recursion to rewrite that further as:
// indented"A 1 B 2 C ${3} D ${4} E"
// then:
// indented"A 1 B 2 C 3 D ${4} E"
// then:
// indented"A 1 B 2 C 3 D 4 E"
case (part0 :: part1 :: tailparts, arg0 :: tailargs) => {
// If 'arg0' has newlines in it, we will need to insert spaces. To decide how many spaces,
// we count many characters after after the last newline in 'part0'. If there is no
// newline, then we just take the length of 'part0':
val i = part0.reverse.indexOf('\n')
val n = if (i == -1)
part0.size // if no newlines in part0, we just take its length
else
i // the number of characters after the last newline
// After every newline in arg0, we must insert 'n' spaces:
val arg0WithPadding = arg0.replaceAll("\n", "\n" + " "*n)
val mergeTwoPartsAndOneArg = part0 + arg0WithPadding + part1
// recurse:
indentedHelper(mergeTwoPartsAndOneArg :: tailparts, tailargs)
}
// The two cases above are exhaustive, but the compiler thinks otherwise, hence we need
// to add this dummy.
case _ => ???
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.