Guest User

Untitled

a guest
Aug 14th, 2018
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.66 KB | None | 0 0
  1. package delta
  2.  
  3. // Use shows instance instead of toString on even primitives.
  4. import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}
  5. import shapeless.labelled.FieldType
  6. import cats.evidence.{As, Is}
  7. import Delta.Meta
  8. import cats.Eq
  9. import cats.data.Ior
  10. import cats.implicits._
  11.  
  12. /**
  13. * Recursively looks through data structures of any complexity
  14. * and tracks down the changes as a Meta.
  15. *
  16. * The comparison are restricted using either
  17. * HasName or cats.Eq with in the shapes.
  18. */
  19. trait FindDeltaMeta[A] {
  20. def apply(a: A, b: A): Delta.Meta
  21. }
  22.  
  23. object FindDeltaMeta extends LowPriorityInstances0 {
  24. def apply[A](implicit ev: FindDeltaMeta[A]): FindDeltaMeta[A] = ev
  25. }
  26.  
  27. trait LowPriorityInstances0 extends LowPriorityInstances1 {
  28. implicit def hNilFindDeltaMeta: FindDeltaMeta[HNil] =
  29. (_, _) => Delta.Meta.empty
  30.  
  31. implicit def findDiffA[A, R <: HList](
  32. implicit E: LabelledGeneric.Aux[A, R],
  33. D: FindDeltaMeta[R]
  34. ): FindDeltaMeta[A] = {
  35. (a, b) => D.apply(E.to(a), E.to(b))
  36. }
  37. }
  38.  
  39. trait LowPriorityInstances1 extends LowPriorityInstances2 {
  40. implicit def hListWithSimpleAnyVal[A: HasName, K <: Symbol, T <: HList](
  41. implicit
  42. witness: Witness.Aux[K],
  43. IsAnyVal: A As AnyVal,
  44. D: Lazy[FindDeltaMeta[T]],
  45. ): FindDeltaMeta[FieldType[K, A] :: T] =
  46. (a, b) =>
  47. (if (HasName[A].name(a.head: A) === HasName[A].name(b.head: A)) {
  48. Meta.empty
  49. } else {
  50. Meta(
  51. witness.value.name, Ior.Both(
  52. HasName[A].name(a.head: A),
  53. HasName[A].name(b.head: A)
  54. )
  55. )
  56. }
  57. ) ++ D.value.apply(a.tail, b.tail)
  58. }
  59.  
  60. trait LowPriorityInstances2 extends LowPriorityInstances3 {
  61. implicit def hLIstWithFofHListInsideItOption[A, K <: Symbol, H, InnerT <: HList, T <: HList](
  62. implicit
  63. witness: Witness.Aux[K],
  64. IsList: H As Option[A],
  65. eachH: LabelledGeneric.Aux[A, InnerT],
  66. HN: HasName[A],
  67. D: Lazy[FindDeltaMeta[T]],
  68. E: Lazy[FindDeltaMeta[InnerT]]
  69. ): FindDeltaMeta[FieldType[K, H] :: T] =
  70. (a, b) => {
  71. val leftOption = a.head.asInstanceOf[Option[A]]
  72. val rightOption = b.head.asInstanceOf[Option[A]]
  73.  
  74. val r =
  75. (leftOption, rightOption) match {
  76. case (Some(la), Some(ra)) => E.value.apply(eachH.to(la), eachH.to(ra)).prependToKey(HasName[A].name(la))
  77. case (Some(la), None) => Meta(HasName[A].name(la), Ior.Left(la.toString))
  78. case (None, Some(ra)) => Meta(HasName[A].name(ra), Ior.Right(ra.toString))
  79. case _ => Meta.empty
  80. }
  81.  
  82. r.prependToKey(witness.value.name) ++ D.value.apply(a.tail, b.tail)
  83. }
  84. }
  85.  
  86. trait LowPriorityInstances3 extends LowPriorityInstances4 {
  87. implicit def hLIstWithFofHListInsideIt[A, K <: Symbol, H, InnerT <: HList, T <: HList](
  88. implicit
  89. witness: Witness.Aux[K],
  90. IsList: H As List[A],
  91. eachH: LabelledGeneric.Aux[A, InnerT],
  92. HN: HasName[A],
  93. D: Lazy[FindDeltaMeta[T]],
  94. E: Lazy[FindDeltaMeta[InnerT]]
  95. ): FindDeltaMeta[FieldType[K, H] :: T] =
  96. (a, b) => {
  97. val leftList = a.head.asInstanceOf[List[A]]
  98. val rightList = b.head.asInstanceOf[List[A]]
  99. val namesOnLeft = leftList.map(t => HasName[A].name(t))
  100.  
  101. val toBeComparedOnRight =
  102. rightList.filter {
  103. bb => namesOnLeft.containsSlice(List(HasName[A].name(bb)))
  104. }
  105.  
  106. // convert toString to shows and print it nicely
  107. val newData =
  108. rightList.filterNot(
  109. t => toBeComparedOnRight.map(t => HasName[A].name(t)).containsSlice(List(HasName[A].name(t)))
  110. ).map(t => Meta(HasName[A].name(t), Ior.Right(t.toString)).prependToKey(witness.value.name))
  111.  
  112. // convert toString to shows and print it nicely
  113. val deletedData =
  114. leftList.filterNot(t => rightList.map(t => HasName[A].name(t)).containsSlice(List(HasName[A].name(t))))
  115. .map(t => Meta(HasName[A].name(t), Ior.Left(t.toString)).prependToKey(witness.value.name))
  116.  
  117. val compareResourcesWithSameName =
  118. leftList.zip(toBeComparedOnRight).map {
  119. case (aa, bb) =>
  120. E.value.apply(eachH.to(aa), eachH.to(bb)).prependToKey(HasName[A].name(aa))
  121. }
  122.  
  123. deletedData.fold(Nil)(_ ++ _) ++
  124. newData.fold(Nil)(_ ++ _) ++
  125. compareResourcesWithSameName.fold(Nil)(_ ++ _).prependToKey(witness.value.name) ++
  126. D.value.apply(a.tail, b.tail)
  127. }
  128. }
  129.  
  130. trait LowPriorityInstances4 extends LowPriorityInstances5 {
  131. implicit def hListNamerWithHListInsideOfInsideOf[K <: Symbol, H, InnerT <: HList, T <: HList](
  132. implicit
  133. witness: Witness.Aux[K],
  134. eachH: LabelledGeneric.Aux[H, InnerT],
  135. H: HasName[H],
  136. D: Lazy[FindDeltaMeta[T]],
  137. E: Lazy[FindDeltaMeta[InnerT]]
  138. ): FindDeltaMeta[FieldType[K, H] :: T] =
  139. (a, b) => {
  140.  
  141. val diff =
  142. if (H.name(a.head) === H.name(b.head)) {
  143. E.value.apply(eachH.to(a.head.asInstanceOf[H]), eachH.to(b.head.asInstanceOf[H]))
  144. .prependToKey(HasName[H].name(b.head)).prependToKey(witness.value.name)
  145. } else {
  146. // convert toString to shows and print it nicely
  147. Meta(HasName[H].name(b.head), Ior.Right(b.head.toString)).prependToKey(witness.value.name) ++
  148. Meta(HasName[H].name(a.head), Ior.Left(a.head.toString)).prependToKey(witness.value.name)
  149. }
  150.  
  151. diff ++ D.value.apply(a.tail, b.tail)
  152. }
  153. }
  154.  
  155. trait LowPriorityInstances5 {
  156. implicit def simpleHList[A, K <: Symbol, H: Eq, R <: HList, T <: HList](
  157. implicit
  158. witness: Witness.Aux[K],
  159. D: Lazy[FindDeltaMeta[T]]
  160. ): FindDeltaMeta[FieldType[K, H] :: T] =
  161. (a, b) => {
  162. (if ((a.head: H) === (b.head: H)) {
  163. Meta.empty
  164. } else {
  165. Meta(
  166. witness.value.name,
  167. Ior.Both((a.head: H).toString, (b.head: H).toString)
  168. )
  169. }
  170. ) ++ D.value.apply(a.tail, b.tail)
  171. }
  172. }
  173. ////////
  174.  
  175.  
  176.  
  177. import cats.evidence.As
  178. import shapeless.ops.hlist.IsHCons
  179. import shapeless.{HList, LabelledGeneric}
  180.  
  181. trait HasName[A] { self =>
  182. def name(a: A): String
  183. }
  184.  
  185. object HasName {
  186. def apply[A](implicit ev: HasName[A]): HasName[A] = ev
  187.  
  188. implicit val intName: HasName[Int] = _.toString
  189. implicit val stringName: HasName[String] = _.toString
  190. implicit val doubleName: HasName[Double] = _.toString
  191. implicit val longName: HasName[Long] = _.toString
  192.  
  193. implicit def hListWithSimpleAnyVal[A, K <: Symbol, T <: HList](
  194. implicit
  195. IsAnyVal: A As AnyVal,
  196. L: LabelledGeneric.Aux[A, T],
  197. E: IsHCons[T]
  198. ): HasName[A] = a => L.to(a).head.toString
  199. }
  200.  
  201.  
  202.  
  203.  
  204.  
  205.  
  206. ////////
  207.  
  208.  
  209.  
  210.  
  211.  
  212.  
  213. ///////
  214.  
  215.  
  216.  
  217. import cats.Show
  218. import cats.data.Ior
  219.  
  220. final case class UpdateInfo private (key: String, action: String, previous: String, newValue: String)
  221.  
  222. object UpdateInfo {
  223. /// To make terraformies happy :) Consider this to be the end of the world ugliness
  224. def mk(key: String, previousNewValue: String Ior String): UpdateInfo =
  225. previousNewValue match {
  226. case Ior.Both(a, b) => UpdateInfo(key, "update", a, b)
  227. case Ior.Left(a) => UpdateInfo(key, "delete", a, "")
  228. case Ior.Right(b) => UpdateInfo(key, "created", "", b)
  229. }
  230.  
  231. implicit val updateInfo: Show[UpdateInfo] =
  232. Show.show[UpdateInfo](a =>
  233. s"${a.key} : ${a.previous} ---> ${a.newValue}"
  234. )
  235. }
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243. //////
  244.  
  245.  
  246.  
  247.  
  248.  
  249.  
  250. //////
  251.  
  252.  
  253. type Meta = List[UpdateInfo]
  254.  
  255. object Meta {
  256. /**
  257. * To have the niceness of:
  258. * Meta("key", "old", "new") ++ Meta("anotherkey", "old", "new")
  259. */
  260. def apply(key: String, previousAndNewValue: String Ior String): Meta =
  261. List(UpdateInfo.mk(key, previousAndNewValue))
  262.  
  263. def prependToKey(s: String, ma: Meta): Meta =
  264. ma.map { u => u.copy(key = s + "." + u.key) }
  265.  
  266. def appendToKey(s: String, ma: Meta): Meta =
  267. ma.map { u => u.copy(key = u.key + "." + s) }
  268.  
  269. def empty: Meta = Nil
  270.  
  271. implicit val metaShow: Show[Meta] =
  272. Show.show(_.map(_.show).mkString("\n"))
  273. }
  274.  
  275.  
  276.  
  277. //////
Add Comment
Please, Sign In to add comment