All Downloads are FREE. Search and download functionalities are using the official Maven repository.

teststate.core.ActionOps.scala Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
package teststate.core

import japgolly.microlibs.name_fn._
import teststate.core.Action.{Actions => _, _}
import teststate.core.CoreExports._
import teststate.data._
import teststate.typeclass.Profunctor.ToOps._
import teststate.typeclass._

// Applies to: Inner, Outer, Sack
trait ActionOps[A[_[_], _, _, _, _]] {

  def trans[F[_], R, O, S, E, G[_]](a: A[F, R, O, S, E])(t: F ~~> G): A[G, R, O, S, E]

  def mapR[F[_], R, O, S, E, X](x: A[F, R, O, S, E])(f: X => R): A[F, X, O, S, E]

  def mapOS[F[_], R, O, S, E, X, Y](a: A[F, R, O, S, E])(f: X => O, g: Y => S)(h: (Y, S) => Y)(implicit em: ExecutionModel[F]): A[F, R, X, Y, E]

  def mapE[F[_], R, O, S, E, X](x: A[F, R, O, S, E])(f: E => X)(implicit em: ExecutionModel[F]): A[F, R, O, S, X]

  def pmapR[F[_], R, O, S, E, X](x: A[F, R, O, S, E])(f: X => E Or R)(implicit em: ExecutionModel[F]): A[F, X, O, S, E]

  def pmapO[F[_], R, O, S, E, X](x: A[F, R, O, S, E])(f: X => E Or O)(implicit em: ExecutionModel[F]): A[F, R, X, S, E]

  /**
    * @param name When defined and the underlying action is empty, a no-op one with the given name will be created.
    *             Otherwise, an empty action will be returned.
    */
  def modS[F[_], R, O, S, E](x: A[F, R, O, S, E])(name: Option[String], f: O => S => E Or S)(implicit em: ExecutionModel[F]): A[F, R, O, S, E]
}

// Applies to: Outer, Sack
trait ActionOps2[A[_[_], _, _, _, _]] {

  def times[F[_], R, O, S, E](a: A[F, R, O, S, E])(n: Int): A[F, R, O, S, E]

  def addCheck[F[_], R, O, S, E](a: A[F, R, O, S, E])(c: Arounds[O, S, E]): A[F, R, O, S, E]

  def topLevelNames[F[_], R, O, S, E](a: A[F, R, O, S, E]): Vector[String]

  def nameTree[F[_], R, O, S, E](a: A[F, R, O, S, E]): VectorTree[String]
}

object ActionOps {

  // Applies to: Inner, Outer, Sack
  final class Ops[A[_[_], _, _, _, _], F[_], R, O, S, E](private val a: A[F, R, O, S, E])(implicit tc: ActionOps[A]) {

    def trans[G[_]](t: F ~~> G) =
      tc.trans(a)(t)

    def mapR[X](f: X => R) =
      tc.mapR(a)(f)

    def mapOS[X, Y](f: X => O, g: Y => S)(h: (Y, S) => Y)(implicit em: ExecutionModel[F]) =
      tc.mapOS(a)(f, g)(h)

    def mapO[X](f: X => O)(implicit em: ExecutionModel[F]) =
      mapOS[X, S](f, identity)((_, s) => s)

    def mapS[X](g: X => S)(h: (X, S) => X)(implicit em: ExecutionModel[F]) =
      mapOS[O, X](identity, g)(h)

    def mapE[X](f: E => X)(implicit em: ExecutionModel[F]) =
      tc.mapE(a)(f)

    def pmapR[X](f: X => E Or R)(implicit em: ExecutionModel[F]) =
      tc.pmapR(a)(f)

    def pmapO[X](f: X => E Or O)(implicit em: ExecutionModel[F]) =
      tc.pmapO(a)(f)

    /**
      * @param name When defined and the underlying action is empty, a no-op one with the given name will be created.
      *             Otherwise, an empty action will be returned.
      */
    private[core] def modS(name: Option[String], f: O => S => E Or S)(implicit em: ExecutionModel[F]) =
      tc.modS(a)(name, f)

    def updateState(f: S => S)(implicit em: ExecutionModel[F]) =
      updateStateAttempt(s => Right(f(s)))

    def updateStateAttempt(f: S => E Or S)(implicit em: ExecutionModel[F]) =
      modS(stateUpdateName, _ => f)

    def updateStateBy(f: OS[O, S] => S)(implicit em: ExecutionModel[F]) =
      updateStateAttemptBy(i => Right(f(i)))

    def updateStateAttemptBy(f: OS[O, S] => E Or S)(implicit em: ExecutionModel[F]) =
      modS(stateUpdateName, o => s => f(OS(o, s)))
  }

  private val stateUpdateName = Some("Update state.")

  // Applies to: Outer, Sack
  final class Ops2[A[_[_], _, _, _, _], F[_], R, O, S, E](private val a: A[F, R, O, S, E])(implicit tc: ActionOps2[A]) {

    def times(n: Int) = {
      require(n >= 0, "n in action.times(n) cannot be negative.")
      tc.times(a)(n)
    }

    def addCheck(c: Arounds[O, S, E]) =
      tc.addCheck(a)(c)

    def topLevelNames: Vector[String] =
      tc.topLevelNames(a)

    def nameTree: VectorTree[String] =
      tc.nameTree(a)

    import teststate.run._

    /** All steps will be marked as skipped. */
    def toReport: Report[Nothing] = {
      val steps = nameTree.ana[History.Step[Nothing]]((name, children) =>
        History.Step(Name.now(name), Result.Skip, History(children, Result.Skip)))
      Report(None, History(steps), Stats.empty)
    }
  }

  // Applies to: Sack
  final class Ops3[F[_], R, O, S, E](private val self: Actions[F, R, O, S, E]) extends AnyVal {
    def group(name: NameFn[ROS[R, O, S]]): Actions[F, R, O, S, E] = {
      val i = Group.lift(self)
      val o = Outer(name, i, Sack.empty)
      o.lift
    }

    import Sack._

    private def groupIfMultipleBy(name: Vector[Actions[F, R, O, S, E]] => NameFn[ROS[R, O, S]]): Actions[F, R, O, S, E] =
      self match {
        case Product(ss) if ss.length != 1 => _group(name(ss), ss).lift
        case _                             => self
        // Allow 0-actions be grouped for clearer UX.
      }

    def groupIfMultiple(name: NameFn[ROS[R, O, S]]): Actions[F, R, O, S, E] =
      groupIfMultipleBy(_ => name)

    def groupIfMultipleByLen(f: Int => NameFn[ROS[R, O, S]]): Actions[F, R, O, S, E] =
      groupIfMultipleBy(ss => f(ss.length))

    def grouped: Actions[F, R, O, S, E] =
      groupIfMultipleBy(_anonGroupName)
  }

  private def _anonGroupName[F[_], R, O, S, E](ss: Vector[Actions[F, R, O, S, E]]): NameFn[ROS[R, O, S]] = {
    val l = ss.length
    s"$l actions."
  }

  private def _group[F[_], R, O, S, E](name: NameFn[ROS[R, O, S]], ss: Vector[Actions[F, R, O, S, E]]) = {
    val i = Group.lift(Sack.Product(ss))
    val o = Outer(name, i, Sack.empty)
    o
  }

  private def _timesName[F[_], R, O, S, E](n: Int, name: NameFn[ROS[R, O, S]]) =
    NameFn[ROS[R, O, S]](i => s"${name(i).value} ($n times)")

  private def _timesBody[F[_], R, O, S, E](n: Int, make: (Name => Name) => Actions[F,R,O,S,E]) =
    Sack.Product(
      (1 to n).iterator
        .map(i => make(s => s"[$i/$n] ${s.value}"))
        .toVector)

  private def _times[F[_], R, O, S, E](n: Int, name: NameFn[ROS[R, O, S]], make: (Name => Name) => Actions[F,R,O,S,E]) = {
    val name2 = _timesName(n, name)
    val body = _timesBody(n, make)
    val g = Group.lift(body)
    Outer(name2, g, Sack.empty)
  }

  @inline implicit class ActionOuterExt[F[_], R, O, S, E](private val self: Outer[F, R, O, S, E]) extends AnyVal {
    @inline def lift: Actions[F, R, O, S, E] =
      Action.liftOuter(self)
  }

  @inline implicit def NameFnRosExt[R, O, S](n: NameFn[ROS[R, O, S]]): NameFnRosExt[R, O, S] = new NameFnRosExt(n.fn)
  class NameFnRosExt[R, O, S](private val _n: Option[ROS[R, O, S]] => Name) extends AnyVal {
    def pmapO[X](f: X => Any Or O): NameFn[ROS[R, X, S]] = NameFn(_n).comap(_.emapO(f).toOption)
    def pmapR[X](f: X => Any Or R): NameFn[ROS[X, O, S]] = NameFn(_n).comap(_.emapR(f).toOption)
  }

  private def preparedFail[F[_], O, S, E](err: E)(implicit em: ExecutionModel[F]): Prepared[F, O, S, E] =
    Some(() => em.pure(Left(err)))

  private def tryPrepare[F[_]: ExecutionModel, O, S, E, A](e: E Or A)(f: A => Prepared[F, O, S, E]): Prepared[F, O, S, E] =
    e match {
      case Right(a) => f(a)
      case Left(err) => preparedFail(err)
    }

  trait ImplicitsLowPri {
    implicit def actionsInstanceActionOps: ActionOps[Actions] with ActionOps2[Actions]

    // Scalac doesn't realise shapes are Actions

    import Types.SackE

    implicit def actionSackToActionOps[F[_], R, O, S, E](a: SackE[ROS[R, O, S], Outer[F, R, O, S, E], E]): Ops[Actions, F, R, O, S, E] =
      new Ops[Actions, F, R, O, S, E](a)

    implicit def actionSackToActionOps2[F[_], R, O, S, E](a: SackE[ROS[R, O, S], Outer[F, R, O, S, E], E]): Ops2[Actions, F, R, O, S, E] =
      new Ops2[Actions, F, R, O, S, E](a)
  }

  trait Implicits extends ImplicitsLowPri {

    implicit val actionInnerInstanceActionOps: ActionOps[Inner] =
      new ActionOps[Inner] {

        override def trans[F[_], R, O, S, E, G[_]](x: Inner[F, R, O, S, E])(t: F ~~> G) =
          x match {
            case a: Single [F, R, O, S, E] => a.copy(run = a.run(_).map(f => () => t(f())))
            case a: Group  [F, R, O, S, E] => a.copy(actions = a.actions trans t)
            case a: SubTest[F, R, O, S, E] => a.copy(action = a.action trans t)
          }

        override def mapR[F[_], R, O, S, E, X](x: Inner[F, R, O, S, E])(f: X => R) =
          x match {
            case Single (r)    => Single (i => r(i mapR f))
            case Group  (a, c) => Group  (a mapR f, c.mapR(f))
            case SubTest(a, i) => SubTest(a mapR f, i)
          }

        override def mapOS[F[_], R, O, S, E, X, Y](action: Inner[F, R, O, S, E])(f: X => O, g: Y => S)(h: (Y, S) => Y)(implicit em: ExecutionModel[F]) =
          action match {

            case Single(r) =>
              Single(i =>
                r(i.mapOS(f, g)).map(fn => () =>
                  em.map(fn())(_.map(j => (x: X) => j(f(x)).map(s => h(i.state, s))))))

            case Group(a, c) =>
              Group(a.mapOS(f, g)(h), c.mapOS(f, g))

            case SubTest(a, i) =>
              SubTest(a.mapOS(f, g)(h), i.mapOS(f, g))
          }

        override def mapE[F[_], R, O, S, E, X](action: Inner[F, R, O, S, E])(f: E => X)(implicit em: ExecutionModel[F]) =
          action match {
            case Single (r)    => Single(r(_).map(fn => () => em.map(fn())(_.bimap(f, _.andThen(_ leftMap f)))))
            case Group  (a, c) => Group(a mapE f, c mapE f)
            case SubTest(a, i) => SubTest(a mapE f, i mapE f)
          }

        override def pmapR[F[_], R, O, S, E, X](x: Inner[F, R, O, S, E])(f: X => E Or R)(implicit em: ExecutionModel[F]) =
          x match {
            case Single(run) =>
              Single(ros => tryPrepare(f(ros.ref))(r => run(ros.withRef(r))))

            case Group(a, c) =>
              Group(a pmapR f, c pmapR f)

            case SubTest(a, i) =>
              SubTest(a pmapR f, i)
          }

        override def pmapO[F[_], R, O, S, E, X](action: Inner[F, R, O, S, E])(f: X => E Or O)(implicit em: ExecutionModel[F]) =
          action match {
            case Single(run) =>
              Single(
                ros => tryPrepare(f(ros.obs))(o =>
                  run(ros.withObs(o)).map(fn => () => em.map(fn())(_.map(g => (x: X) => f(x) flatMap g)))))

            case Group(a, c) =>
              Group(a pmapO f, c pmapO f)

            case SubTest(a, i) =>
              SubTest(a pmapO f, i pmapO f)
          }

        override def modS[F[_], R, O, S, E](x: Inner[F, R, O, S, E])(name: Option[String], m: O => S => E Or S)(implicit em: ExecutionModel[F]) =
          x match {
            case Single(run) =>
              Single[F, R, O, S, E](
                run(_).map(f => () => em.map(f())(
                  _.map(g => (o: O) => g(o) flatMap m(o))
                )))
            case Group(a, c)   => Group(a.modS(name, m), c.modS(m))
            case SubTest(a, i) => SubTest(a.modS(name, m), i)
          }
      }

    implicit val actionOuterInstanceActionOps: ActionOps[Outer] with ActionOps2[Outer] =
      new ActionOps[Outer] with ActionOps2[Outer] {

        override def trans[F[_], R, O, S, E, G[_]](x: Outer[F, R, O, S, E])(t: F ~~> G) =
          Outer(x.name, x.inner trans t, x.check)

        override def mapR[F[_], R, O, S, E, X](x: Outer[F, R, O, S, E])(f: X => R) =
          Outer(x.name.cmap(_ mapR f), x.inner mapR f, x.check)

        override def mapOS[F[_], R, O, S, E, X, Y](x: Outer[F, R, O, S, E])(f: X => O, g: Y => S)(h: (Y, S) => Y)(implicit em: ExecutionModel[F]) =
          Outer(x.name.cmap(_.mapOS(f, g)), x.inner.mapOS(f, g)(h), x.check.mapOS(f, g))

        override def mapE[F[_], R, O, S, E, X](x: Outer[F, R, O, S, E])(f: E => X)(implicit em: ExecutionModel[F]) =
          Outer(x.name, x.inner mapE f, x.check mapE f)

        override def pmapR[F[_], R, O, S, E, X](x: Outer[F, R, O, S, E])(f: X => E Or R)(implicit em: ExecutionModel[F]) =
          Outer(x.name pmapR f, x.inner pmapR f, x.check)

        override def pmapO[F[_], R, O, S, E, X](x: Outer[F, R, O, S, E])(f: X => E Or O)(implicit em: ExecutionModel[F]) =
          Outer(x.name pmapO f, x.inner pmapO f, x.check pmapO f)

        override def modS[F[_], R, O, S, E](x: Outer[F, R, O, S, E])(name: Option[String], m: O => S => E Or S)(implicit em: ExecutionModel[F]) =
          Outer(x.name, x.inner.modS(name, m), x.check)

        override def times[F[_], R, O, S, E](a: Outer[F, R, O, S, E])(n: Int) =
          _times(n, a.name, a.nameMod(_).lift)

        override def addCheck[F[_], R, O, S, E](x: Outer[F, R, O, S, E])(c: Arounds[O, S, E]) =
          Outer(x.name, x.inner, x.check & c)

        override def topLevelNames[F[_], R, O, S, E](a: Outer[F, R, O, S, E]): Vector[String] =
          Vector.empty[String] :+ a.name(None).value

        override def nameTree[F[_], R, O, S, E](a: Outer[F, R, O, S, E]): VectorTree[String] = {
          val name = a.name(None).value
          a.inner match {
            case Action.Single(_)      => VectorTree.one(name)
            case Action.Group(a2, _)   => VectorTree.one(name, a2.nameTree)
            case Action.SubTest(a2, _) => VectorTree.one(name, a2.nameTree)
          }
        }
      }

    implicit lazy val actionsInstanceActionOps: ActionOps[Actions] with ActionOps2[Actions] =
      new ActionOps[Actions] with ActionOps2[Actions] {
        import Sack._

        override def trans[F[_], R, O, S, E, G[_]](a: Actions[F, R, O, S, E])(t: F ~~> G) =
          a.rmap(_.map(_ trans t))

        override def mapR[F[_], R, O, S, E, X](x: Actions[F, R, O, S, E])(f: X => R) =
          x.dimap(_ mapR f, _ map (_ mapR f))

        override def mapOS[F[_], R, O, S, E, X, Y](x: Actions[F, R, O, S, E])(f: X => O, g: Y => S)(h: (Y, S) => Y)(implicit em: ExecutionModel[F]) =
          x.dimap(_.mapOS(f, g), _ map (_.mapOS(f, g)(h)))

        override def mapE[F[_], R, O, S, E, X](x: Actions[F, R, O, S, E])(f: E => X)(implicit em: ExecutionModel[F]) =
          x.rmap(_.bimap(_ map (_ map f), _ mapE f))

        override def pmapR[F[_], R, O, S, E, X](x: Actions[F, R, O, S, E])(f: X => E Or R)(implicit em: ExecutionModel[F]) =
          x match {
            case Value(v)        => Value(v map(_ pmapR f))
            case Product(ss)     => Product(ss map (_ pmapR f))
            case CoProduct(n, p) =>
              CoProduct(n pmapR f,
                _.emapR(f).fold(e => Sack Value Left(NamedError(n(None), Failure NoCause e)), p(_) pmapR f))
          }

        override def pmapO[F[_], R, O, S, E, X](x: Actions[F, R, O, S, E])(f: X => E Or O)(implicit em: ExecutionModel[F]) =
          x match {
            case Value(v)        => Value(v map(_ pmapO f))
            case Product(ss)     => Product(ss map (_ pmapO f))
            case CoProduct(n, p) =>
              CoProduct(n pmapO f,
                _.emapO(f).fold(e => Sack Value Left(NamedError(n(None), Failure NoCause e)), p(_) pmapO f))
          }

        override def modS[F[_], R, O, S, E](actions: Actions[F, R, O, S, E])(name: Option[String], f: O => S => E Or S)(implicit em: ExecutionModel[F]) =
          actions match {
            case Value(v)         => Value(v.map(_.modS(name, f)))
            case CoProduct(nf, p) => CoProduct(nf, ros => p(ros).modS(name, f))
            case Product(ss) =>
              if (ss.isEmpty)
                name match {
                  case Some(n) =>
                    val inner = Action.Single[F, R, O, S, E](ros => Some(() => em.point(Right((o: O) => f(o)(ros.state)))))
                    val action = Action.liftInner(inner)(n)
                    Product(Vector.empty :+ action)
                  case None =>
                    Product(Vector.empty)
                }
              else
                Product(ss.init :+ ss.last.modS(name, f))
          }

        override def times[F[_], R, O, S, E](actions: Actions[F, R, O, S, E])(n: Int) =
          actions match {
            case Value(v)         => Value(v map (_ times n))
            case CoProduct(nf, p) => _times(n, nf, f => CoProduct(nf map f, p)).lift
            case Product(ss)      =>
              if (ss.length == 1)
                ss.head times n
              else
                _group(_anonGroupName(ss), ss).times(n).lift
          }

        override def addCheck[F[_], R, O, S, E](x: Actions[F, R, O, S, E])(c: Arounds[O, S, E]) =
          x match {
            case Product(ss) if ss.length != 1 =>
              _group(_anonGroupName(ss), ss).addCheck(c).lift
            case _ =>
              x.rmap(_ map (_ addCheck c))
          }

        override def topLevelNames[F[_], R, O, S, E](s: Actions[F, R, O, S, E]): Vector[String] =
          s match {
            case Sack.Value(Right(a))    => a.topLevelNames
            case Sack.Value(Left(e))     => Vector.empty[String] :+ e.name.value
            case Sack.CoProduct(name, _) => Vector.empty[String] :+ name(None).value
            case Sack.Product(as)        => as.flatMap(topLevelNames)
          }

        override def nameTree[F[_], R, O, S, E](s: Actions[F, R, O, S, E]): VectorTree[String] =
          s match {
            case Sack.Value(Right(a))    => a.nameTree
            case Sack.Value(Left(e))     => VectorTree.one(e.name.value)
            case Sack.Product(as)        => VectorTree(as.iterator.map(_.nameTree).flatMap(_.elements).toVector)
            case Sack.CoProduct(name, _) => VectorTree.one(name(None).value)
          }
      }

    implicit def toActionOps[A[_[_], _, _, _, _], F[_], R, O, S, E](a: A[F, R, O, S, E])(implicit tc: ActionOps[A]): Ops[A, F, R, O, S, E] =
      new Ops(a)(tc)

    implicit def toActionOps2[A[_[_], _, _, _, _], F[_], R, O, S, E](a: A[F, R, O, S, E])(implicit tc: ActionOps2[A]): Ops2[A, F, R, O, S, E] =
      new Ops2(a)(tc)

    implicit def toActionOps3[F[_], R, O, S, E](a: Actions[F, R, O, S, E]): Ops3[F, R, O, S, E] =
      new Ops3(a)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy