Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package delta
- // Use shows instance instead of toString on even primitives.
- import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}
- import shapeless.labelled.FieldType
- import cats.evidence.{As, Is}
- import Delta.Meta
- import cats.Eq
- import cats.data.Ior
- import cats.implicits._
- /**
- * Recursively looks through data structures of any complexity
- * and tracks down the changes as a Meta.
- *
- * The comparison are restricted using either
- * HasName or cats.Eq with in the shapes.
- */
- trait FindDeltaMeta[A] {
- def apply(a: A, b: A): Delta.Meta
- }
- object FindDeltaMeta extends LowPriorityInstances0 {
- def apply[A](implicit ev: FindDeltaMeta[A]): FindDeltaMeta[A] = ev
- }
- trait LowPriorityInstances0 extends LowPriorityInstances1 {
- implicit def hNilFindDeltaMeta: FindDeltaMeta[HNil] =
- (_, _) => Delta.Meta.empty
- implicit def findDiffA[A, R <: HList](
- implicit E: LabelledGeneric.Aux[A, R],
- D: FindDeltaMeta[R]
- ): FindDeltaMeta[A] = {
- (a, b) => D.apply(E.to(a), E.to(b))
- }
- }
- trait LowPriorityInstances1 extends LowPriorityInstances2 {
- implicit def hListWithSimpleAnyVal[A: HasName, K <: Symbol, T <: HList](
- implicit
- witness: Witness.Aux[K],
- IsAnyVal: A As AnyVal,
- D: Lazy[FindDeltaMeta[T]],
- ): FindDeltaMeta[FieldType[K, A] :: T] =
- (a, b) =>
- (if (HasName[A].name(a.head: A) === HasName[A].name(b.head: A)) {
- Meta.empty
- } else {
- Meta(
- witness.value.name, Ior.Both(
- HasName[A].name(a.head: A),
- HasName[A].name(b.head: A)
- )
- )
- }
- ) ++ D.value.apply(a.tail, b.tail)
- }
- trait LowPriorityInstances2 extends LowPriorityInstances3 {
- implicit def hLIstWithFofHListInsideItOption[A, K <: Symbol, H, InnerT <: HList, T <: HList](
- implicit
- witness: Witness.Aux[K],
- IsList: H As Option[A],
- eachH: LabelledGeneric.Aux[A, InnerT],
- HN: HasName[A],
- D: Lazy[FindDeltaMeta[T]],
- E: Lazy[FindDeltaMeta[InnerT]]
- ): FindDeltaMeta[FieldType[K, H] :: T] =
- (a, b) => {
- val leftOption = a.head.asInstanceOf[Option[A]]
- val rightOption = b.head.asInstanceOf[Option[A]]
- val r =
- (leftOption, rightOption) match {
- case (Some(la), Some(ra)) => E.value.apply(eachH.to(la), eachH.to(ra)).prependToKey(HasName[A].name(la))
- case (Some(la), None) => Meta(HasName[A].name(la), Ior.Left(la.toString))
- case (None, Some(ra)) => Meta(HasName[A].name(ra), Ior.Right(ra.toString))
- case _ => Meta.empty
- }
- r.prependToKey(witness.value.name) ++ D.value.apply(a.tail, b.tail)
- }
- }
- trait LowPriorityInstances3 extends LowPriorityInstances4 {
- implicit def hLIstWithFofHListInsideIt[A, K <: Symbol, H, InnerT <: HList, T <: HList](
- implicit
- witness: Witness.Aux[K],
- IsList: H As List[A],
- eachH: LabelledGeneric.Aux[A, InnerT],
- HN: HasName[A],
- D: Lazy[FindDeltaMeta[T]],
- E: Lazy[FindDeltaMeta[InnerT]]
- ): FindDeltaMeta[FieldType[K, H] :: T] =
- (a, b) => {
- val leftList = a.head.asInstanceOf[List[A]]
- val rightList = b.head.asInstanceOf[List[A]]
- val namesOnLeft = leftList.map(t => HasName[A].name(t))
- val toBeComparedOnRight =
- rightList.filter {
- bb => namesOnLeft.containsSlice(List(HasName[A].name(bb)))
- }
- // convert toString to shows and print it nicely
- val newData =
- rightList.filterNot(
- t => toBeComparedOnRight.map(t => HasName[A].name(t)).containsSlice(List(HasName[A].name(t)))
- ).map(t => Meta(HasName[A].name(t), Ior.Right(t.toString)).prependToKey(witness.value.name))
- // convert toString to shows and print it nicely
- val deletedData =
- leftList.filterNot(t => rightList.map(t => HasName[A].name(t)).containsSlice(List(HasName[A].name(t))))
- .map(t => Meta(HasName[A].name(t), Ior.Left(t.toString)).prependToKey(witness.value.name))
- val compareResourcesWithSameName =
- leftList.zip(toBeComparedOnRight).map {
- case (aa, bb) =>
- E.value.apply(eachH.to(aa), eachH.to(bb)).prependToKey(HasName[A].name(aa))
- }
- deletedData.fold(Nil)(_ ++ _) ++
- newData.fold(Nil)(_ ++ _) ++
- compareResourcesWithSameName.fold(Nil)(_ ++ _).prependToKey(witness.value.name) ++
- D.value.apply(a.tail, b.tail)
- }
- }
- trait LowPriorityInstances4 extends LowPriorityInstances5 {
- implicit def hListNamerWithHListInsideOfInsideOf[K <: Symbol, H, InnerT <: HList, T <: HList](
- implicit
- witness: Witness.Aux[K],
- eachH: LabelledGeneric.Aux[H, InnerT],
- H: HasName[H],
- D: Lazy[FindDeltaMeta[T]],
- E: Lazy[FindDeltaMeta[InnerT]]
- ): FindDeltaMeta[FieldType[K, H] :: T] =
- (a, b) => {
- val diff =
- if (H.name(a.head) === H.name(b.head)) {
- E.value.apply(eachH.to(a.head.asInstanceOf[H]), eachH.to(b.head.asInstanceOf[H]))
- .prependToKey(HasName[H].name(b.head)).prependToKey(witness.value.name)
- } else {
- // convert toString to shows and print it nicely
- Meta(HasName[H].name(b.head), Ior.Right(b.head.toString)).prependToKey(witness.value.name) ++
- Meta(HasName[H].name(a.head), Ior.Left(a.head.toString)).prependToKey(witness.value.name)
- }
- diff ++ D.value.apply(a.tail, b.tail)
- }
- }
- trait LowPriorityInstances5 {
- implicit def simpleHList[A, K <: Symbol, H: Eq, R <: HList, T <: HList](
- implicit
- witness: Witness.Aux[K],
- D: Lazy[FindDeltaMeta[T]]
- ): FindDeltaMeta[FieldType[K, H] :: T] =
- (a, b) => {
- (if ((a.head: H) === (b.head: H)) {
- Meta.empty
- } else {
- Meta(
- witness.value.name,
- Ior.Both((a.head: H).toString, (b.head: H).toString)
- )
- }
- ) ++ D.value.apply(a.tail, b.tail)
- }
- }
- ////////
- import cats.evidence.As
- import shapeless.ops.hlist.IsHCons
- import shapeless.{HList, LabelledGeneric}
- trait HasName[A] { self =>
- def name(a: A): String
- }
- object HasName {
- def apply[A](implicit ev: HasName[A]): HasName[A] = ev
- implicit val intName: HasName[Int] = _.toString
- implicit val stringName: HasName[String] = _.toString
- implicit val doubleName: HasName[Double] = _.toString
- implicit val longName: HasName[Long] = _.toString
- implicit def hListWithSimpleAnyVal[A, K <: Symbol, T <: HList](
- implicit
- IsAnyVal: A As AnyVal,
- L: LabelledGeneric.Aux[A, T],
- E: IsHCons[T]
- ): HasName[A] = a => L.to(a).head.toString
- }
- ////////
- ///////
- import cats.Show
- import cats.data.Ior
- final case class UpdateInfo private (key: String, action: String, previous: String, newValue: String)
- object UpdateInfo {
- /// To make terraformies happy :) Consider this to be the end of the world ugliness
- def mk(key: String, previousNewValue: String Ior String): UpdateInfo =
- previousNewValue match {
- case Ior.Both(a, b) => UpdateInfo(key, "update", a, b)
- case Ior.Left(a) => UpdateInfo(key, "delete", a, "")
- case Ior.Right(b) => UpdateInfo(key, "created", "", b)
- }
- implicit val updateInfo: Show[UpdateInfo] =
- Show.show[UpdateInfo](a =>
- s"${a.key} : ${a.previous} ---> ${a.newValue}"
- )
- }
- //////
- //////
- type Meta = List[UpdateInfo]
- object Meta {
- /**
- * To have the niceness of:
- * Meta("key", "old", "new") ++ Meta("anotherkey", "old", "new")
- */
- def apply(key: String, previousAndNewValue: String Ior String): Meta =
- List(UpdateInfo.mk(key, previousAndNewValue))
- def prependToKey(s: String, ma: Meta): Meta =
- ma.map { u => u.copy(key = s + "." + u.key) }
- def appendToKey(s: String, ma: Meta): Meta =
- ma.map { u => u.copy(key = u.key + "." + s) }
- def empty: Meta = Nil
- implicit val metaShow: Show[Meta] =
- Show.show(_.map(_.show).mkString("\n"))
- }
- //////
Add Comment
Please, Sign In to add comment