Skip to content

Commit

Permalink
Convert CursorState to JsonLike
Browse files Browse the repository at this point in the history
  • Loading branch information
bmjames committed Nov 14, 2013
1 parent 0e0589b commit a00d95c
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 93 deletions.
110 changes: 53 additions & 57 deletions core/src/main/scala/com/gu/json/CursorState.scala
@@ -1,10 +1,7 @@
package com.gu.json

/*
import org.json4s.JsonAST._
import scalaz.Scalaz._
import scalaz.{ Kleisli, MonadState, MonadPlus, StateT }
import scalaz.{MonadState, MonadPlus, StateT}

/** Represents a transition, which may succeed or fail, from a cursor, to an updated cursor
* together with a value: JCursor => Option[(JCursor, A)]
Expand All @@ -22,105 +19,104 @@ import scalaz.{ Kleisli, MonadState, MonadPlus, StateT }
* field("uselessData") >> deleteGoUp
*
*/
object CursorState {

protected type OptionState[S, A] = StateT[Option, S, A]

type CursorState[A] = OptionState[Cursor, A]
type CursorState[J, A] = OptionState[Cursor[J], A]

val monadState = MonadState[OptionState, Cursor]
def monadState[J] = MonadState[OptionState, Cursor[J]]

def apply[A](f: Cursor => Option[(Cursor, A)]): CursorState[A] = StateT(f)
def apply[J, A](f: Cursor[J] => Option[(Cursor[J], A)]): CursorState[J, A] = StateT(f)

def replace(value: JValue): CursorState[Unit] =
def replace[J](value: J): CursorState[J, Unit] =
CursorState(cursor => Some(cursor.replace(value), ()))

def transform(pfn: PartialFunction[JValue, JValue]): CursorState[JValue] =
def transform[J](pfn: PartialFunction[J, J]): CursorState[J, J] =
returnFocus(_.transform(pfn).some)

def field(name: String): CursorState[JValue] = returnFocus(_.field(name))
def field[J](name: String): CursorState[J, J] = returnFocus(_.field(name))

def sibling(name: String): CursorState[JValue] = returnFocus(_.sibling(name))
def sibling[J](name: String): CursorState[J, J] = returnFocus(_.sibling(name))

def insertChildField(name: String, value: JValue): CursorState[JValue] =
def insertChildField[J](name: String, value: J): CursorState[J, J] =
returnFocus(_.insertField(name, value))

def insertFieldLeft(name: String, value: JValue): CursorState[JValue] =
def insertFieldLeft[J](name: String, value: J): CursorState[J, J] =
returnFocus(_.insertFieldLeft(name, value))

def insertFieldRight(name: String, value: JValue): CursorState[JValue] =
def insertFieldRight[J](name: String, value: J): CursorState[J, J] =
returnFocus(_.insertFieldRight(name, value))

def rename(name: String): CursorState[JValue] = returnFocus(_.rename(name))
def rename[J](name: String): CursorState[J, J] = returnFocus(_.rename(name))

def insertLeft(elem: JValue): CursorState[JValue] = returnFocus(_.insertLeft(elem))
def insertLeft[J](elem: J): CursorState[J, J] = returnFocus(_.insertLeft(elem))

def insertRight(elem: JValue): CursorState[JValue] = returnFocus(_.insertRight(elem))
def insertRight[J](elem: J): CursorState[J, J] = returnFocus(_.insertRight(elem))

def left: CursorState[JValue] = returnFocus(_.left)
def left[J]: CursorState[J, J] = returnFocus(_.left)

def leftN(n: Int): CursorState[JValue] =
left.replicateM_(n) >> getFocus
def leftN[J](n: Int): CursorState[J, J] =
left[J].replicateM_(n) >> getFocus

def right: CursorState[JValue] = returnFocus(_.right)
def right[J]: CursorState[J, J] = returnFocus(_.right)

def rightN(n: Int): CursorState[JValue] =
right.replicateM_(n) >> getFocus
def rightN[J](n: Int): CursorState[J, J] =
right[J].replicateM_(n) >> getFocus

def findLeft(pfn: PartialFunction[JValue, Boolean]) = returnFocus(_.findLeft(pfn))
def findLeft[J](pfn: PartialFunction[J, Boolean]): CursorState[J, J] = returnFocus(_.findLeft(pfn))

def head: CursorState[JValue] = returnFocus(_.firstElem)
def head[J]: CursorState[J, J] = returnFocus(_.firstElem)

def elem(n: Int): CursorState[JValue] = head >> rightN(n)
def elem[J](n: Int): CursorState[J, J] = head[J] >> rightN(n)

def up: CursorState[JValue] = returnFocus(_.up)
def up[J]: CursorState[J, J] = returnFocus(_.up)

def deleteGoUp: CursorState[JValue] = returnFocus(_.deleteGoUp)
def deleteGoUp[J]: CursorState[J, J] = returnFocus(_.deleteGoUp)

def removeField(name: String): CursorState[JValue] = field(name) >> deleteGoUp
def removeField[J](name: String): CursorState[J, J] = field[J](name) >> deleteGoUp

def root: CursorState[JValue] = returnFocus(_.root.some)
def root[J]: CursorState[J, J] = returnFocus(_.root.some)

def keySet: CursorState[Set[String]] =
CursorState(cursor => cursor.keySet map (cursor ->))
def keySet[J]: CursorState[J, Set[String]] =
CursorState(cursor => cursor.keySet map (cursor -> _))

/** Run the supplied function over the state value and return the resulting focus */
def returnFocus(f: Cursor => Option[Cursor]): CursorState[JValue] =
def returnFocus[J](f: Cursor[J] => Option[Cursor[J]]): CursorState[J, J] =
CursorState(f andThen (_ map (c => c -> c.focus)))

def getFocus: CursorState[JValue] = monadState.init map (_.focus)
def getFocus[J]: CursorState[J, J] = monadState.init map (_.focus)

def orElse[A](a: CursorState[A], b: => CursorState[A]): CursorState[A] =
def orElse[J, A](a: CursorState[J, A], b: => CursorState[J, A]): CursorState[J, A] =
CursorState(cursor => a(cursor) orElse b(cursor))

def having[A, B](ca: CursorState[A], cb: CursorState[B]): CursorState[A] =
def having[J, A, B](ca: CursorState[J, A], cb: CursorState[J, B]): CursorState[J, A] =
for {
a <- ca
s <- monadState.init
_ <- cb
_ <- monadState.put(s)
} yield a

import Cursor.jCursor
import Cursor.cursor

def foreach[A](cmd: CursorState[A]): CursorState[JArray] =
def foreach[J, A](cmd: CursorState[J, A])(implicit J: JsonLike[J]): CursorState[J, J] =
for {
JArray(children) <- getFocus
Some(cs) <- children.traverse(c => cmd.exec(jCursor(c)).map(_.toJson)).point[CursorState]
newFocus = JArray(cs)
Some(children) <- getFocus[J] map (J.asArray(_))
Some(cs) <- children.traverse(c => cmd.exec(cursor(c)).map(_.toJson)).point[({type λ[α]=CursorState[J, α]})#λ]
newFocus = J.array(cs)
_ <- replace(newFocus)
} yield newFocus

implicit class CursorStateOps[A](self: CursorState[A]) {
implicit class CursorStateOps[J, A](self: CursorState[J, A]) {

def orElse(onFailure: => CursorState[A]): CursorState[A] =
def orElse(onFailure: => CursorState[J, A]): CursorState[J, A] =
CursorState.orElse(self, onFailure)

def having[B](guard: CursorState[B]): CursorState[A] =
def having[B](guard: CursorState[J, B]): CursorState[J, A] =
CursorState.having(self, guard)

def foreach[A](cmd: CursorState[A]): CursorState[JArray] =
def foreach[B](cmd: CursorState[J, B])(implicit ev: JsonLike[J]): CursorState[J, J] =
self >> CursorState.foreach(cmd)
}

Expand All @@ -132,21 +128,21 @@ object CursorState {
* I've not thought about this too hard, but I think it probably satisfies enough laws to be reasonable.
* See http://en.wikibooks.org/wiki/Haskell/MonadPlus#The_MonadPlus_laws
*/
implicit val cursorStateMonadPlus: MonadPlus[CursorState] = new MonadPlus[CursorState] {
implicit def cursorStateMonadPlus[J]: MonadPlus[({type λ[α]=CursorState[J, α]})#λ] =
new MonadPlus[({type λ[α]=CursorState[J, α]})#λ] {

def plus[A](a: CursorState[A], b: => CursorState[A]) = orElse(a, b)
def plus[A](a: CursorState[J, A], b: => CursorState[J, A]) = orElse(a, b)

def empty[A] = CursorState(_ => None)
def empty[A] = CursorState(_ => None)

def point[A](a: => A) = CursorState(cursor => Some(cursor, a))
def point[A](a: => A) = CursorState(cursor => Some(cursor, a))

def bind[A, B](fa: CursorState[A])(f: A => CursorState[B]) =
CursorState { cursor =>
fa(cursor) flatMap {
case (c1, a) => f(a)(c1)
def bind[A, B](fa: CursorState[J, A])(f: A => CursorState[J, B]) =
CursorState { cursor =>
fa(cursor) flatMap {
case (c1, a) => f(a)(c1)
}
}
}
}
}

}
*/
38 changes: 21 additions & 17 deletions core/src/main/scala/com/gu/json/JValueSyntax.scala
@@ -1,46 +1,50 @@
package com.gu.json

import scalaz.\/
import scalaz.std.option._
import scalaz.syntax.bind._

import CursorArrowSyntax.CursorArrowBuilder
import com.gu.json.CursorState._


trait JValueSyntax {

implicit class JValueOps[J](value: J) {
implicit class JValueOps[J : JsonLike](value: J) {

def cursor(implicit ev: JsonLike[J]): Cursor[J] = Cursor.cursor(value)
def cursor: Cursor[J] = Cursor.cursor(value)

// def removeAt[A](command: CursorState[A]): JValue =
// execDefault(command >> deleteGoUp)
def removeAt[A](command: CursorState[J, A]): J =
execDefault(command >> deleteGoUp[J])

def delete(builder: CursorArrowBuilder[J])(implicit ev: JsonLike[J]): J =
def delete(builder: CursorArrowBuilder[J]): J =
runDefault(builder(CursorArrows.deleteGoUp))

def mod(builder: CursorArrowBuilder[J])(f: J => J)(implicit ev: JsonLike[J]): J =
def mod(builder: CursorArrowBuilder[J])(f: J => J): J =
runDefault(builder(CursorArrows.mod(f)))

def modp(builder: CursorArrowBuilder[J])(pfn: PartialFunction[J, J])(implicit ev: JsonLike[J]): J =
def modp(builder: CursorArrowBuilder[J])(pfn: PartialFunction[J, J]): J =
runDefault(builder(CursorArrows.transform(pfn)))

/* def eval[A](command: CursorState[A]): Option[A] =
command.eval(cursor)
def eval[A](command: CursorState[J, A]): Option[A] =
command.eval(cursor)

def exec(command: CursorState[_]): Option[JValue] =
command.exec(cursor) map (_.toJson)
def exec[A](command: CursorState[J, A]): Option[J] =
command.exec(cursor) map (_.toJson)

def execDefault(command: CursorState[_]): JValue =
exec(command) getOrElse value*/
def execDefault[A](command: CursorState[J, A]): J =
exec(command) getOrElse value

def run(arrow: CursorArrow[J])(implicit ev: JsonLike[J]): CursorFailure[J] \/ J =
def run(arrow: CursorArrow[J]): CursorFailure[J] \/ J =
arrow.run(cursor).map (_.toJson)

def runDefault(arrow: CursorArrow[J])(implicit ev: JsonLike[J]): J =
def runDefault(arrow: CursorArrow[J]): J =
run(arrow) getOrElse value

def stringValue(implicit ev: JsonLike[J]): Option[String] =
def stringValue: Option[String] =
Lenses.strVal.get(value)

def bigIntValue(implicit ev: JsonLike[J]): Option[BigInt] =
def bigIntValue: Option[BigInt] =
Lenses.intVal.get(value)

}
Expand Down
40 changes: 21 additions & 19 deletions test/src/test/scala/com.gu.json/CursorStateExamples.scala
@@ -1,19 +1,19 @@
package com.gu.json

/*
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.FunSuite
import org.json4s._
import org.json4s.native.JsonMethods._
import scalaz.Scalaz._

import scalaz.std.option._
import scalaz.syntax.monadPlus._

import com.gu.json.json4s._
import CursorState._
import JValueSyntax._

/** Contrived examples to demonstrate how to compose and run CursorState functions
*/
class CursorStateExamples extends FunSuite with ShouldMatchers {

val json = parse(
Expand All @@ -36,13 +36,13 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {
// Counts the number of assets and then uses that data to insert a new field, assetCount
val addAssetCount =
for {
JArray(assets) <- field("assets")
_ <- insertFieldLeft("assetCount", JInt(assets.length))
JArray(assets) <- field[JValue]("assets")
_ <- insertFieldLeft[JValue]("assetCount", JInt(assets.length))
} yield ()

test("exec a cursor state function to return an updated JValue if successful") {

json exec addAssetCount should equal (Some(parse(
json.exec(addAssetCount) should equal (Some(parse(
"""
{
"type":"image",
Expand All @@ -61,11 +61,12 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {
""")))
}

val deleteFirstAsset = field("assets") >> head >> deleteGoUp
val deleteFirstAsset =
field[JValue]("assets") >> head[JValue] >> deleteGoUp[JValue]

test("execDefault returns the updated JValue if the command succeeds") {

json execDefault deleteFirstAsset should equal (parse(
json.execDefault(deleteFirstAsset) should equal (parse(
"""
{
"type":"image",
Expand All @@ -79,14 +80,14 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {
"""))
}

val failingCommand = field("assets") >> head >> left
val failingCommand = field[JValue]("assets") >> head >> left

test("execDefault returns the initial JValue unchanged if the command fails") {

json execDefault failingCommand should equal (json)
}

val moveFocus = field("assets") >> head
val moveFocus = field[JValue]("assets") >> head

test("eval returns the final focus of the cursor if the command succeeds") {

Expand All @@ -99,7 +100,7 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {
""")))
}

val commandWithRetry = field("flibbles") orElse field("assets") >> elem(1)
val commandWithRetry = (field[JValue]("flibbles") orElse field("assets")) >> elem(1)

test ("orElse combines two commands, with the second command tried if the first fails") {

Expand All @@ -114,19 +115,20 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {

test("`having` passes if the predicate command on the r.h.s. succeeds, preserving l.h.s. state and value") {

json eval (field("type") having sibling("assets")) should be (Some(JString("image")))
json.eval(field[JValue]("type") having sibling("assets")) should be (Some(JString("image")))

json eval (field("type") having head) should be (None)
json.eval(field[JValue]("type") having head) should be (None)

json exec (field("assets") having sibling("type")) >> deleteGoUp should be (Some(parse("""{"type":"image"}""")))
json.exec { (field[JValue]("assets") having sibling("type")) >> deleteGoUp } should
be (Some(parse("""{"type":"image"}""")))

}

test("`foreach` execs the supplied command on each of an array's child elements") {

val removeFiles = field("assets") foreach removeField("file")
val removeFiles = field[JValue]("assets") foreach removeField("file")

json exec removeFiles should be (Some(parse("""
json.exec(removeFiles) should be (Some(parse("""
{
"type":"image",
"assets":[
Expand All @@ -144,7 +146,7 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {

test("`remove` syntax on a JValue removes the element reached by a series of cursor movements") {

json.removeAt(field("assets") >> elem(1) >> field("file")) should be (parse("""
json.removeAt(field[JValue]("assets") >> elem(1) >> field("file")) should be (parse("""
{
"type":"image",
"assets":[
Expand All @@ -162,4 +164,4 @@ class CursorStateExamples extends FunSuite with ShouldMatchers {
}

}
*/

0 comments on commit a00d95c

Please sign in to comment.