7

In a nutshell:

"""I want to be able to
  |have the convenient formatting of a multiline string,
  |while using inline escape sequences\r\r\b\\
  |
  |How can this be done?""".stripMargin
root
  • 471
  • 5
  • 18

3 Answers3

13

Two options that I can think of:

  1. You can use StringContext.treatEscapes directly:

    StringContext.treatEscapes("""I want to be able to
            |have the convenient formatting of a multiline string,
            |while using inline escape sequences\r\r\b\\
            |
            |How can this be done?""".stripMargin)
    
  2. If the variable substitution feature of the "simple interpolator" (s) isn't disruptive to your needs, then try combining string interpolation (which converts escaped characters) with """-quotes (which doesn't escape ...):

    println("""he\\lo\nworld""")
    println(s"""he\\lo\nworld""")
    

    outputs

    he\\lo\nworld
    he\lo
    world
    

    For details, see the relevant SIP and this earlier question.

Community
  • 1
  • 1
Richard Sitze
  • 8,262
  • 3
  • 36
  • 48
3

In addition to the other answers - what about this idea?

s"""I want to be able to
  |have the convenient formatting of a multiline string,
  |while using inline escape sequences${"\r\r\b\"}\
  |
  |How can this be done?""".stripMargin

The only thing that won't work that way is the \ at the end of the line. In my example, you embed the escaped characters as a normal string, and then use string interpolation to insert them. (Mind the s before the opening triple quotes.)

Madoc
  • 5,841
  • 4
  • 25
  • 38
2

There is a convenient example hidden in the standard library. It's easy to tweak in a couple of ways to add standard processing. It's not obvious how embedded \r is intended, however, so caveat interpolator.

Update: for the record, the hard part was forgetting the sequence arg _*. Because it's Any*, there's no type error; the underlying interpolator just throws an error that the parts don't match the args.

Update: fixed underscore star so it doesn't italicize.

Example:

import reflect.internal.util.StripMarginInterpolator

object Test extends App {

  trait ZipMarginator extends StripMarginInterpolator {
    def zm(args: Any*): String = StringContext treatEscapes sm(args: _*)
  }
  implicit class ZipMarginOps(val stringContext: StringContext) extends ZipMarginator
  val sample =
zm"""I want to be able to
    |have the convenient formatting of a multiline string,
    |while using inline escape sequences
    |like\t\ttabs and \\Program Files\\backslashes.
    |
    |How can this be done?"""
  Console println sample

  implicit class ZipMarginOps2(val stringContext: StringContext) extends SStripMarginInterpolator {
    def sz(args: Any*): String = ssm(args: _*)
  }
  Console println sz"""
    |Another\t\texample."""
  Console println sz"""
    |Another\r\tex\nample.
    |Huh?"""

}

Here is the StripMargin..or with name changes to protect one's sanity, note the caveat about raw:

trait SStripMarginInterpolator {
  def stringContext: StringContext

  /**
   * A safe combination of [[scala.collection.immutable.StringLike#stripMargin]]
   * and [[scala.StringContext#raw]].
   *
   * The margin of each line is defined by whitespace leading up to a '|' character.
   * This margin is stripped '''before''' the arguments are interpolated into to string.
   *
   * String escape sequences are '''not''' processed; this interpolater is designed to
   * be used with triple quoted Strings.
   *
   * {{{
   * scala> val foo = "f|o|o"
   * foo: String = f|o|o
   * scala> sm"""|${foo}
   *             |"""
   * res0: String =
   * "f|o|o
   * "
   * }}}
   */
  final def ssm(args: Any*): String = {
    def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak
    def stripTrailingPart(s: String) = {
      val (pre, post) = s.span(c => !isLineBreak(c))
      pre + post.stripMargin
    }
    val stripped: List[String] = stringContext.parts.toList match {
      case head :: tail => head.stripMargin :: (tail map stripTrailingPart)
      case Nil => Nil
    }
    new StringContext(stripped: _*).s(args: _*)  // <= MODIFIED for s instead of raw
  }
}
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • Interesting, ty. If I read that right, it automates the stringMargin call, while otherwise doing exactly what the `s` interpolator does. May still be a problem if the original text shouldn't be subjected to variable substitution. – Richard Sitze Jul 26 '13 at 01:14
  • Should `def zm(args: Any*): String = StringContext treatEscapes sm(args: _*)` be instead `def zm(args: Any*): String = StringContext treatEscapes ssm(args: _*)` [s/sm/ssm]? – Richard Sitze Jul 26 '13 at 01:21
  • @RichardSitze The first example uses the inherited sm method from reflect.internal and calls treatEsc like your option 1; the second uses the copy/pasted version and passes the stripped part to standard s. But see the update, ha, it drove me temporarily insane to figure out that error. And now I see that SO has converted _* to emphasis markup. So I don't see anything straight today. – som-snytt Jul 26 '13 at 06:03