diff --git a/src/main/scala/scala/next/NextStringOpsExtensions.scala b/src/main/scala/scala/next/NextStringOpsExtensions.scala new file mode 100644 index 0000000..8142942 --- /dev/null +++ b/src/main/scala/scala/next/NextStringOpsExtensions.scala @@ -0,0 +1,46 @@ +package scala +package next + +private[next] final class NextStringOpsExtensions( + private val str: String +) extends AnyVal { + /** Split this string around the separator character; as a [[List]] + * + * If this string is blank, + * returns an empty list. + * + * If this string is not a blank string, + * returns a list containing the substrings terminated by the start of the string, + * the end of the string or the separator character. + * + * By default, this method discards empty substrings. + * + * @param separator the character used as the delimiter. + * @param preserveEmptySubStrings set as `true` to preserve empty substrings. + * @return a [[List]] of substrings. + */ + def splitAsList(separator: Char, preserveEmptySubStrings: Boolean = false): List[String] = { + @annotation.tailrec + def loop(currentIdx: Int, acc: List[String]): List[String] = { + def addToAcc(substring: String): List[String] = + if (substring.isEmpty && !preserveEmptySubStrings) + acc + else + substring :: acc + + val newIndex = str.lastIndexOf(separator, currentIdx - 1) + if (newIndex == -1) + addToAcc(substring = str.substring(0, currentIdx)) + else + loop( + currentIdx = newIndex, + acc = addToAcc(substring = str.substring(newIndex + 1, currentIdx)) + ) + } + + loop( + currentIdx = str.length, + acc = List.empty + ) + } +} diff --git a/src/main/scala/scala/next/package.scala b/src/main/scala/scala/next/package.scala index fb21a9a..0e496c5 100644 --- a/src/main/scala/scala/next/package.scala +++ b/src/main/scala/scala/next/package.scala @@ -12,6 +12,8 @@ package scala +import scala.language.implicitConversions + package object next { implicit final class OptionOpsExtensions[A](private val v: Option[A]) extends AnyVal { /** Apply the side-effecting function `f` to the option's value @@ -22,4 +24,9 @@ package object next { */ def tapEach[B](f: A => B): Option[A] = { v.foreach(f); v } } + + implicit final def scalaNextSyntaxForStringOps( + str: String + ): NextStringOpsExtensions = + new NextStringOpsExtensions(str) } diff --git a/src/test/scala/scala/next/TestStringOpsExtensions.scala b/src/test/scala/scala/next/TestStringOpsExtensions.scala new file mode 100644 index 0000000..e94bf07 --- /dev/null +++ b/src/test/scala/scala/next/TestStringOpsExtensions.scala @@ -0,0 +1,107 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.next + +import org.junit.Assert._ +import org.junit.Test + +final class TestStringOpsExtensions { + @Test + def splitAsListEmptyStringNoPreserveEmpty(): Unit = { + val str = "" + + assertTrue(str.splitAsList(',').isEmpty) + } + + @Test + def splitAsListEmptyStringPreserveEmpty(): Unit = { + val str = "" + val expected = List("") + + assertEquals(expected, str.splitAsList(separator = ',', preserveEmptySubStrings = true)) + } + + @Test + def splitAsListBlankStrings(): Unit = { + val strings = List( + " ", + " ", + "\t", + "\t\t\t", + "\n", + "\n\n\n", + " \t \t \n" + ) + + strings.foreach { str => + val expected = List(str) + assertEquals(expected, str.splitAsList(',')) + } + } + + @Test + def splitAsListDelimiterNotFound(): Unit = { + val str = "Hello World" + val expected = List(str) + + assertEquals(expected, str.splitAsList(',')) + } + + @Test + def splitAsListSingleDelimiter(): Unit = { + val str = "Hello,World" + val expected = List("Hello", "World") + + assertEquals(expected, str.splitAsList(',')) + } + + @Test + def splitAsListMultipleDelimiters(): Unit = { + val str = "Hello,World,Good,Bye,World" + val expected = List("Hello", "World", "Good", "Bye", "World") + + assertEquals(expected, str.splitAsList(',')) + } + + @Test + def splitAsListEmptySubStrings(): Unit = { + val str = "Hello,,World," + val expected = List("Hello", "World") + + assertEquals(expected, str.splitAsList(',')) + } + + @Test + def splitAsListDelimiterAtTheEnd(): Unit = { + val str = "Hello,World," + val expected = List("Hello", "World") + + assertEquals(expected, str.splitAsList(',')) + } + + @Test + def splitAsListDelimiterAtTheBeginning(): Unit = { + val str = ",Hello,World" + val expected = List("Hello", "World") + + assertEquals(expected, str.splitAsList(',')) + } + + @Test + def splitAsListPreserveEmptySubStrings(): Unit = { + val str = ",Hello,,World," + val expected = List("", "Hello", "", "World", "") + + assertEquals(expected, str.splitAsList(separator = ',', preserveEmptySubStrings = true)) + } +}