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

teststate.domzipper.DomZipperBase.scala Maven / Gradle / Ivy

The newest version!
package teststate.domzipper

import teststate.domzipper.DomZipper._
import teststate.domzipper.DomZipperBase._
import teststate.domzipper.ErrorHandler.Id

trait DomZipperBase[F[_], Dom, A, Self[G[_], B] <: DomZipperBase[G, Dom, B, Self]] extends DomZipper[F, Dom, A, Self] {
  import DomCollection.Container

  // ====================
  // Self & configuration
  // ====================

  final override def describe: String =
    s"DESC: ${layers.all.iterator.map(_.display) mkString " -> "}\nHTML: $outerHTML"

  protected def copySelf[G[_]](h: HtmlScrub, g: ErrorHandler[G]): Self[G, A]

  protected implicit val $: CssSelEngine[Dom, Dom]

  /** oldest to newest; furthest to closest. */
  protected val layers: DomZipperBase.Layers[Dom]

  protected[domzipper] def addLayer(nextLayer: Layer[Dom]): Self[F, A]

  final override def scrubHtml(f: HtmlScrub): Self[F, A] =
    copySelf(htmlScrub >> f, F)

  final def failBy[G[_]](g: ErrorHandler[G]): Self[G, A] =
    copySelf(htmlScrub, g)

  final def failToOption: Self[Option           , A] = failBy(ErrorHandler.ReturnOption)
  final def failToEither: Self[Either[String, *], A] = failBy(ErrorHandler.ReturnEither)
  final def throwErrors : Self[Id               , A] = failBy(ErrorHandler.Throw)

  override def enrichErr(msg: String): String =
    msg + "\n" + describe

  // =======
  // Descent
  // =======

  final protected def runCssQuery(sel: String): CssSelResult[Dom] =
    $.run(sel, dom)

  final override def apply(name: String, sel: String, which: MofN): F[Self[F, A]] = {
    val results = runCssQuery(sel)
    if (results.length != which.n)
      F fail {
        val q = Option(name).filter(_.nonEmpty).fold("Q")(_ + " q")
        enrichErr(s"${q}uery failed: [$sel]. Expected ${which.n} results, not ${results.length}.")
      }
    else
      F pass {
        val nextDom = results(which.m - 1)
        val nextLayer = Layer(name, sel, nextDom)
        addLayer(nextLayer)
      }
  }

  final override def child(name: String, sel: String, which: MofN): F[Self[F, A]] = {
    val results = if (sel.isEmpty) children1n else children1n(sel)
    val desc = if (sel.isEmpty) ">*" else cssPrepend_>(sel)
    F.flatMap(results.zippers) { zippers =>
      if (zippers.length != which.n)
        F fail {
          val q = Option(name).filter(_.nonEmpty).fold("Q")(_ + " q")
          enrichErr(s"${q}uery failed: [$desc]. Expected ${which.n} results, not ${zippers.length}.")
        }
      else
        F pass zippers(which.m - 1)
    }
  }

  // ====================
  // DOM & DOM inspection
  // ====================

  protected def _parent: F[Dom]

  final lazy val parent: F[Self[F, A]] =
    F.map(_parent)(dom => addLayer(Layer("parent", ":parent", dom)))

  final override def dom: Dom = layers.latest.dom

  protected def _outerHTML: String
  protected def _innerHTML: String

  protected def collect        [C[_]](sel: String, C: Container[F, C])              : DomCollection[Self, F, C, Dom, A]
  protected def collectChildren[C[_]](desc: String, C: Container[F, C])             : DomCollection[Self, F, C, Dom, A]
  protected def collectChildren[C[_]](desc: String, sel: String, C: Container[F, C]): DomCollection[Self, F, C, Dom, A]

  final override def collect01(sel: String): DomCollection[Self, F, Option, Dom, A] = collect(sel, F.C01)
  final override def collect0n(sel: String): DomCollection[Self, F, Vector, Dom, A] = collect(sel, F.C0N)
  final override def collect1n(sel: String): DomCollection[Self, F, Vector, Dom, A] = collect(sel, F.C1N)

  final override def children01: DomCollection[Self, F, Option, Dom, A] = collectChildren(">*", F.C01)
  final override def children0n: DomCollection[Self, F, Vector, Dom, A] = collectChildren(">*", F.C0N)
  final override def children1n: DomCollection[Self, F, Vector, Dom, A] = collectChildren(">*", F.C1N)

  final override def children01(sel: String): DomCollection[Self, F, Option, Dom, A] = collectChildren(cssPrepend_>(sel), sel, F.C01)
  final override def children0n(sel: String): DomCollection[Self, F, Vector, Dom, A] = collectChildren(cssPrepend_>(sel), sel, F.C0N)
  final override def children1n(sel: String): DomCollection[Self, F, Vector, Dom, A] = collectChildren(cssPrepend_>(sel), sel, F.C1N)
}

object DomZipperBase {
  private val cssCondStart = "(^|, *)".r
  private def cssPrepend_>(a: String) = cssCondStart.replaceAllIn(a, "$1> ")

  trait Store[F[_], Dom, A, Self[G[_], B] <: Store[G, Dom, B, Self]] extends DomZipper[F, Dom, A, Self] {
    protected type Pos
    protected def pos: Pos
    protected def peek: Peek[A]
    protected def newStore[B](pos: Pos, peek: Peek[B]): Self[F, B]

    protected final type Peek[B] = Pos => B

    protected final def newStore[B](peek: Peek[B]): Self[F, B] =
      newStore(pos, peek)

    // =================
    // Comonad and focus
    // =================

    override final def extract: A =
      peek(pos)

    override final def map[B](f: A => B): Self[F, B] =
      newStore(f compose peek)

    override final def duplicate: Self[F, Self[F, A]] =
      newStore(newStore(_, peek))

    override final def extend[B](f: Self[F, A] => B): Self[F, B] =
      duplicate.map(f)
  }

  trait WithStore[F[_], Dom, A, Self[G[_], B] <: WithStore[G, Dom, B, Self]] extends DomZipperBase[F, Dom, A, Self]
      with Store[F, Dom, A, Self] {
    override final protected type Pos = Layers[Dom]
    override final protected def pos = layers
    override final protected[domzipper] def addLayer(n: Layer[Dom]) = newStore(layers add n, peek)
  }

  final case class Layers[Dom](init: Vector[Layer[Dom]], last: Layer[Dom]) {
    def all = init :+ last
    @inline def latest = last
    def add(l: Layer[Dom]) = Layers(all, l)
  }
  object Layers {
    def init[Dom](l: Layer[Dom]) = Layers(Vector.empty, l)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy