Skip to content

Instantly share code, notes, and snippets.

@rzeigler
Last active November 9, 2020 19:41
Show Gist options
  • Save rzeigler/8f8f6c881273920ec6c3011eecd68afd to your computer and use it in GitHub Desktop.
Save rzeigler/8f8f6c881273920ec6c3011eecd68afd to your computer and use it in GitHub Desktop.
import cats.Monad
import cats.syntax.all._
import scala.collection.immutable.SortedMap
final case class MatrixVar(elem: String, map: SortedMap[String, String]) {
def render: String = {
val parts = map.toList.map(pair => show"${pair._1}=${pair._2}").intercalate(";")
show"$elem;$parts"
}
}
object MatrixVar {
def parse(s: String): Option[MatrixVar] = {
val firstSemi = s.indexOf(';')
if (firstSemi < 0) Some(MatrixVar(s, SortedMap.empty))
else if (firstSemi == 0) None
else
extractMap(s, firstSemi + 1)
.map(MatrixVar(s.substring(0, firstSemi), _))
}
final private case class RecS(s: String, position: Int, found: List[(String, String)])
private def extractMap(s: String, position: Int): Option[SortedMap[String, String]] = {
Monad[Option]
.tailRecM(RecS(s, position, List.empty))(extractMapImpl)
.map(result => SortedMap(result: _*))
}
// This is broken out to avoid closing over any input
private def extractMapImpl(state: RecS): Option[Either[RecS, List[(String, String)]]] = {
val nextSplit = state.s.indexOf(';', state.position)
// This is some kind of empty segment so parsing is broken
if (nextSplit == state.position)
None
// We are consuming the remainder of the string
else if (nextSplit < 0)
extractPart(state.s, state.position, state.s.length).map(kv => Right(kv :: state.found))
else
extractPart(state.s, state.position, nextSplit)
.map(kv => Left(state.copy(position = nextSplit + 1, found = kv :: state.found)))
}
// Extract a piece of a matrix url
private def extractPart(s: String, position: Int, end: Int): Option[(String, String)] = {
val delimSplit = s.indexOf('=', position)
// Track so that we can detect multiple equals in a single kv slot
val nextDelimSplit = s.indexOf('=', delimSplit + 1)
if (delimSplit == position)
None
else if (delimSplit >= end)
None
// There are multiple eqs in a single segment which we may as well treat as invalid
else if (nextDelimSplit < end && nextDelimSplit >= 0)
None
else
Some(s.substring(position, delimSplit) -> s.substring(delimSplit + 1, end))
}
def unapply(s: String): Option[MatrixVar] = MatrixVar.parse(s)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment