Adding a nice trim for multi-line strings in Scala
As already mentioned in my PHP article on this topic, I frequently store multi-line strings in my programs. Today, let's use ascii based game art for our example.
Language version used in this article: Scala 3.6.2
The Artwork
Here is the artwork of a book that I want to display in the console:
|\\\\ ////|
| \\\V/// |
| |~~~| |
| |===| |
\ | | /
\|===|/
Basic Multi-Line String
In Scala, regular strings have to be defined on a single line. For example, the following results in a compiler error:
val a = "
Hello World
"
To define multi-line strings in this fashion, we have to use triple quotes """
. This is how that would look like:
@main
def main(): Unit = {
val sprite = """
|\\\\ ////|
| \\\V/// |
| |~~~| |
| |===| |
\ | | /
\|===|/
"""
println(sprite)
}
The output is kind of OK, but not really:
|\\\\ ////|
| \\\V/// |
| |~~~| |
| |===| |
\ | | /
\|===|/
We get additional blank lines at the top and bottom and hardcoded whitespace on the left, that mirrors the indentation level of the source code. For our game this is not what we want. Each sprite should render without any additional margins.
Scala Book solution
If you follow the Scala Book, you will find this solution:
val message =
"""|Multi
|Line
|String""".stripMargin
It avoids blank lines by opening and closing the string on the same lines where the content starts and ends.
Additionally, the call to stripMargin
on the resulting string removes whitespace left to a designated trim character on each line. By default, that trim character is |
.
In my case, |
is also part of the artwork itself, so I chose to use a different trim character. I picked t
, but it's as good as any other character.
@main
def main(): Unit = {
val sprite =
"""t|\\\\ ////|
t| \\\V/// |
t| |~~~| |
t| |===| |
t \ | | /
t \|===|/""".stripMargin('t')
println(sprite)
}
Output:
|\\\\ ////|
| \\\V/// |
| |~~~| |
| |===| |
\ | | /
\|===|/
It works, but I lose the ability to easily copy / paste text snippets into and out of my code 😔
Custom String Extension
When I lamented about my problem on the Scala Discord server, I got some suggestions from the very helpful community within minutes. I chose to solve it with an extension method.
In this case, I add a nice
method to the Java.lang.String
type:
package StringExtensions
extension (input: String) {
def nice: String = {
val contentLines = input
.split("\\r?\\n")
.drop(1)
.dropRight(1)
val indent = contentLines
.filter(_.trim.nonEmpty)
.map(_.takeWhile(_ == ' ').length)
.minOption
.getOrElse(0)
def trimmed(line: String): String =
if line.length >= indent
then line.drop(indent)
else line
contentLines.map(trimmed).mkString("\n")
}
}
What it does:
- removes one line from the top and one line from the bottom
- finds the minimum indentation
- cuts off excessive whitespace from all the lines
And then we can use it from another package like this:
import StringExtensions.nice
@main
def main(): Unit = {
val sprite = """
|\\\\ ////|
| \\\V/// |
| |~~~| |
| |===| |
\ | | /
\|===|/
""".nice
println(sprite)
}
And once again we get the proper output:
|\\\\ ////|
| \\\V/// |
| |~~~| |
| |===| |
\ | | /
\|===|/
Conclusion
Getting predictable and user-friendly multi-line strings to work in Scala was bit of a challenge. But this challenge gave me a good reason to try and extend the language myself, which was surprisingly easy and a lot of fun. With my nice
helper method, I think I will be very productive going forward.