Last active
May 4, 2021 19:00
-
-
Save afsalthaj/809650b4c6e895084110269a3ecb8702 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com..s3paths | |
import cats.free.Cofree | |
import com.s3paths.nonfree.Component.Version | |
import com.s3paths.nonfree.Component.RunTime | |
import com.s3paths.nonfree.Component.Adhoc | |
import cats.Traverse | |
import cats.Eval | |
// Well we need name and pos | |
import cats.Id | |
import cats.~> | |
import cats.implicits._ | |
// Thats the abstraction that was hidden | |
import atto._, Atto._ | |
import cats.Applicative | |
object nonfree { | |
sealed trait Component | |
object Component { | |
def keyValueParser[A](n: String, v: Parser[A]): Parser[(String, A)] = | |
string(n) <~ string("=") flatMap (n => v.map(v => (n, v))) | |
case class Version(n: String, v: Int) extends Component | |
object Version { | |
def parser(name: String): Parser[Version] = | |
keyValueParser(name, int).map({ case (a, b) => Version(a, b) }) | |
} | |
case class RunTime(n: String, v: Long) extends Component | |
object RunTime { | |
def parser(name: String): Parser[RunTime] = | |
keyValueParser(name, long).map({ case (a, b) => RunTime(a, b) }) | |
} | |
case class Adhoc(n: String, v: String) extends Component | |
object Adhoc { | |
val anyCharExceptSlash: Parser[String] = | |
many(satisfy(t => t != '/' && !(t.isSpaceChar))).map(_.mkString) | |
def parser(name: String): Parser[Adhoc] = | |
keyValueParser(name, anyCharExceptSlash).map({ case (a, b) => Adhoc(a, b) }) | |
} | |
} | |
// oversimplified, coz a path can itself a metadata, and tomorrow it may not be s3. But forget about all that | |
case class Path(current: Component, next: Option[Path]) | |
val path = Path(Component.Version("minor_version", 10), Some(Path(Component.RunTime("run_time", 11), None))) | |
} | |
object free extends App { | |
case class PathA[A](current: nonfree.Component, next: Option[A]) | |
object PathA { | |
implicit val traversePathA: Traverse[PathA] = new Traverse[PathA] { | |
override def foldLeft[A, B](fa: PathA[A], b: B)(f: (B, A) => B): B = | |
fa.next match { | |
case Some(value) => f(b, value) | |
case None => b | |
} | |
override def foldRight[A, B](fa: PathA[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = | |
fa.next match { | |
case Some(value) => f(value, lb) | |
case None => lb | |
} | |
override def traverse[G[_]: Applicative, A, B](fa: PathA[A])(f: A => G[B]): G[PathA[B]] = { | |
val ogb = fa.next.map(f) | |
ogb match { | |
case None => Applicative[G].pure(PathA(fa.current, None)) | |
case Some(value) => Applicative[G].map(value)(a => PathA(fa.current, Some(a))) | |
} | |
} | |
} | |
} | |
val inclusion: Eval ~> cats.Id = new (Eval ~> cats.Id) { | |
override def apply[A](fa: Eval[A]): Id[A] = fa.value | |
} | |
val parsers: List[Parser[nonfree.Component]] = | |
List(Version.parser("minor_version"), RunTime.parser("run_time")) | |
def parser: Parser[Cofree[PathA, Int]] = | |
for { | |
loc <- pos | |
current <- parsers.combineAll // Monoid of parser is `orElse` | |
_ <- string("/") | |
next <- opt(parser) | |
} yield Cofree(loc, cats.Eval.now(PathA(current, next))) | |
type Structure = List[(Int, String)] | |
def structure(parsed: Cofree[PathA, Int]): Structure = { | |
def combine(a: (Int, String), b: Option[Structure]): Structure = | |
Applicative[Option] | |
.ap2[(Int, String), Structure, Structure](Some(_ :: _))(Some(a), b) | |
.getOrElse(Nil) | |
Cofree.cataM[PathA, Id, Int, Structure](parsed)( | |
(a, fb) => | |
fb.current match { | |
case Version(n, _) => combine((a, n), fb.next) | |
case RunTime(n, _) => combine((a, n), fb.next) | |
case Adhoc(n, _) => combine((a, n), fb.next) | |
} | |
)(inclusion) | |
} | |
val path1 = "minor_version=1/run_time=222222/relationship_id=3/abc.parquet" | |
val path2 = "run_time=222222/minor_version=1/relationship_id=4/abc.parquet" | |
val parsed1 = | |
parser | |
.parse(path1) | |
.done | |
.option | |
val parsed2 = | |
parser | |
.parse(path2) | |
.done | |
.option | |
val isSameStructure = | |
Applicative[Option].map2( | |
parsed1, | |
parsed2 | |
)((a, b) => structure(a) == structure(b)) | |
println(parsed1.map(structure)) | |
println(parsed2.map(structure)) | |
println(isSameStructure) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment