4

Are there any standard library facilities to do string interpolation/formatting at runtime? I'd like the formatting to behave exactly the same as the macro based s"scala ${implementation} except that my string format is loaded at runtime from a config file.

val format = config.getString("my.key")
val stringContext = parseFormat(format)
val res = stringContext.f("arg1", "arg2")

with parseFormat returning a StringContext.

I imagine, worst case, I could just split the string on "{}" sequences and use the parts to construct the StringContext.

// untested
def parseFormat(format: String): StringContext =
  new StringContext("""{}""".r.split(format): _*)

Is there an obvious solution that I'm missing or would the above hack do the trick?

3
  • 1
    I guess you want more from your format String from the capabilities of String#format ( docs.oracle.com/javase/7/docs/api/java/lang/…, java.lang.Object...) ) . Could you tell what you need? Commented Mar 25, 2014 at 18:38
  • I suppose that would do the trick. Is there a way to close my own question for sheer stupidity? Commented Mar 25, 2014 at 19:27
  • I do not mind if you just delete it, though it might give a guide for those who also face similar problems and have already forgotten String#format. :) Commented Mar 25, 2014 at 21:12

3 Answers 3

3

There are no silly questions. Only Sunday mornings.

First, don't use String.format.

scala> val s = "Count to %d"
s: String = Count to %d

scala> String format (s, 42)
<console>:9: error: overloaded method value format with alternatives:
  (x$1: java.util.Locale,x$2: String,x$3: Object*)String <and>
  (x$1: String,x$2: Object*)String
 cannot be applied to (String, Int)
              String format (s, 42)
                     ^

scala> s format 42
res1: String = Count to 42

But formatting can be expensive. So with your choice of escape handling:

scala> StringContext("Hello, {}. Today is {}." split "\\{}" : _*).s("Bob", "Tuesday")
res2: String = Hello, Bob. Today is Tuesday.

scala> StringContext("""Hello, \"{}.\" Today is {}.""" split "\\{}" : _*).s("Bob", "Tuesday")
res3: String = Hello, "Bob." Today is Tuesday.

scala> StringContext("""Hello, \"{}.\" Today is {}.""" split "\\{}" : _*).raw("Bob", "Tuesday")
res4: String = Hello, \"Bob.\" Today is Tuesday.

It turns out that split doesn't quite hack it.

scala> StringContext("Count to {}" split "\\{}" : _*) s 42
java.lang.IllegalArgumentException: wrong number of arguments (1) for interpolated string with 1 parts
  at scala.StringContext.checkLengths(StringContext.scala:65)
  at scala.StringContext.standardInterpolator(StringContext.scala:121)
  at scala.StringContext.s(StringContext.scala:94)
  ... 33 elided

So given

scala> val r = "\\{}".r
r: scala.util.matching.Regex = \{}

scala> def parts(s: String) = r split s
parts: (s: String)Array[String]

Maybe

scala> def f(parts: Seq[String], args: Any*) = (parts zip args map (p => p._1 + p._2)).mkString
f: (parts: Seq[String], args: Any*)String

So

scala> val count = parts("Count to {}")
count: Array[String] = Array("Count to ")

scala> f(count, 42)
res7: String = Count to 42

scala> f(parts("Hello, {}. Today is {}."), "Bob", "Tuesday")
res8: String = Hello, Bob. Today is Tuesday

Hey, wait!

scala> def f(parts: Seq[String], args: Any*) = (parts.zipAll(args, "", "") map (p => p._1 + p._2)).mkString
f: (parts: Seq[String], args: Any*)String

scala> f(parts("Hello, {}. Today is {}."), "Bob", "Tuesday")
res9: String = Hello, Bob. Today is Tuesday.

or

scala> def f(parts: Seq[String], args: Any*) = (for (i <- 0 until (parts.size max args.size)) yield (parts.applyOrElse(i, (_: Int) => "") + args.applyOrElse(i, (_: Int) => ""))).mkString
f: (parts: Seq[String], args: Any*)String

or

scala> def f(parts: Seq[String], args: Any*) = { val sb = new StringBuilder ; for (i <- 0 until (parts.size max args.size) ; ss <- List(parts, args)) { sb append ss.applyOrElse(i, (_: Int) => "") } ; sb.toString }
f: (parts: Seq[String], args: Any*)String

scala> f(parts("Hello, {}. Today is {}. {}"), "Bob", "Tuesday", "Bye!")
res16: String = Hello, Bob. Today is Tuesday. Bye!
Sign up to request clarification or add additional context in comments.

Comments

1

A. As of Scala 2.10.3, you can't use StringContext.f unless you know the number of arguments at compile time since the .f method is a macro.

B. Use String.format, just like you would in the good ol' days of Java.

Comments

0

I had a similar requirement where I was loading a Seq[String] from a config file which would become a command to be executed (using scala.sys.process). To simplify the format and ignore any potential escaping problems I also made the variable names a configurable option too.

The config looked something like this:

command = ["""C:\Program Files (x86)\PuTTY\pscp.exe""", "-P", "2222", "-i",
          ".vagrant/machines/default/virtualbox/private_key", "$source", "~/$target"]
source = "$source"
target = "$target"

I couldn't find a nice (or efficient) way of using the StringContext or "string".format so I rolled my own VariableCommand which is quite similar to StringContext however a single variable can appear zero or more times in any order and in any of the items.

The basic idea was to create a function which took the variable values and then would either take part of the string (e.g. "~/") or take the variable value (e.g. "test.conf") repeatedly to build up the result (e.g. "~/test.conf"). This function is created once which is where all the complexity is and then at substitution time it is really simple (and hopefully fast although I haven't done any performance testing, or much testing at all for that matter).

For those that might wonder why I was doing this it was for running automation tests cross platform using ansible (which doesn't support Windows control machines) for provisioning. This allowed me to copy the files to the target machine and run ansible locally.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.