diff --git a/docs/book/ReleaseNotes.html b/docs/book/ReleaseNotes.html new file mode 100644 index 00000000..6ad4d7e7 --- /dev/null +++ b/docs/book/ReleaseNotes.html @@ -0,0 +1,429 @@ + + + + + Release notes · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ +
+ +
+ + + + + + + + +
+
+ +
+
+ +
+ +

Release notes

+

2.0:

+
    +
  • Replace play/functional with typelevel/cats (#38 1, 2, 3, 4, 5, 6, 7, 8, 9)

    +
  • +
  • Impove error reporting (#40 1, 2)

    +
  • +
  • Rework project structure: jsonplayjson, json4sjsonast, jsjson (#50 0 1, 2)

    +
  • +
  • Add Scala.js support (#42 1, 2, 3, 4, 5)

    +
  • +
+ + +
+ +
+
+
+ +

results matching ""

+
    + +
    +
    + +

    No results matching ""

    + +
    +
    +
    + +
    +
    + +
    + + + + + + + + + + +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaJsValidation.html b/docs/book/ScalaJsValidation.html new file mode 100644 index 00000000..d1108987 --- /dev/null +++ b/docs/book/ScalaJsValidation.html @@ -0,0 +1,703 @@ + + + + + Exporting Validations to Javascript using Scala.js · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + +
    + +
    + + + + + + + + +
    +
    + +
    +
    + +
    + +

    Exporting Validations to Javascript using Scala.js

    +

    Validation 2.0 supports Scala.js, which allows compiling validation logic for JavaScript to run it directly in the browser. Let's begin by playing with it. Try to change the tryMe variable in the following editor. The result is automatically outputted.

    +

    + + + + + + +
    + + +
    + + + + + + +

    Using validation from Scala.js is no different than any other Scala library. There is, however, some friction to integrate Scala.js into an existing Play + JavaScript, which we try to address in this document. Assuming no prior knowledge on Scala.js, we explain how to cross-compile and integrate validation logic into an existing Play/JavaScript application.

    +

    You will first need to add two SBT plugins, Scala.js itself and sbt-play-scalajs to make it Scala.js and Play coexist nicely:

    +
    scala> cat("project/plugins.sbt")
    +addSbtPlugin("com.typesafe.play" % "sbt-plugin"       % "2.5.2")
    +addSbtPlugin("org.scala-js"      % "sbt-scalajs"      % "0.6.9")
    +addSbtPlugin("com.vmunier"       % "sbt-play-scalajs" % "0.3.0")
    +
    +

    Scala.js uses a separate compilation pass to transform Scala sources to a single .js file. Specifying which part of a Scala codebase should be processed by Scala.js is done by splitting the code in different SBT projects. This is usually done with 3 projects, one targeting the JVM, another one targeting JS, and a third one for code shared between the two. In case of a Play application it could look like the following:

    +
    <project root>
    + +- build.sbt
    + +- jvm
    + |   +- app
    + |   +- conf
    + |   +- public
    + |   +- test
    + +- js
    + |   +- src/main/scala
    + +- shared
    +     +- src/main/scala
    +

    Now let's look at a minimal build.sbt reflecting this structure. Information on the sbt settings are available on the Scala.js documentation on cross build, and on sbt-play-scalajs documentation.

    +
    scala> cat("build.sbt")
    +val scalaV = "2.11.8"
    +
    +val validationVersion = "2.0"
    +
    +lazy val jvm = project
    +  .in(file("jvm"))
    +  .settings(
    +    scalaVersion := scalaV,
    +    scalaJSProjects := Seq(js),
    +    pipelineStages := Seq(scalaJSProd),
    +    libraryDependencies ++= Seq(
    +      "com.vmunier"   %% "play-scalajs-scripts" % "0.5.0",
    +      "io.github.jto" %% "validation-core"      % validationVersion,
    +      "io.github.jto" %% "validation-playjson"  % validationVersion,
    +      "io.github.jto" %% "validation-jsonast"   % validationVersion))
    +  .enablePlugins(PlayScala)
    +  .aggregate(js)
    +  .dependsOn(sharedJVM)
    +
    +lazy val js = project
    +  .in(file("js"))
    +  .settings(
    +    scalaVersion := scalaV,
    +    persistLauncher := true,
    +    libraryDependencies ++= Seq(
    +      "io.github.jto" %%% "validation-core"    % validationVersion,
    +      "io.github.jto" %%% "validation-jsjson"  % validationVersion,
    +      "io.github.jto" %%% "validation-jsonast" % validationVersion))
    +  .enablePlugins(ScalaJSPlugin, ScalaJSPlay)
    +  .dependsOn(sharedJS)
    +
    +lazy val sharedJVM = shared.jvm
    +lazy val sharedJS  = shared.js
    +lazy val shared    = crossProject.crossType(CrossType.Pure)
    +  .in(file("shared"))
    +  .settings(
    +    scalaVersion := scalaV,
    +    libraryDependencies ++= Seq(
    +      "io.github.jto" %%% "validation-core"    % validationVersion,
    +      "io.github.jto" %%% "validation-jsonast" % validationVersion))
    +  .jsConfigure(_.enablePlugins(ScalaJSPlay))
    +
    +onLoad in Global := (Command.process("project jvm", _: State)) compose (onLoad in Global).value
    +
    +

    In addition to the validation dependency, we also included play-scalajs-scripts, which provides a convenient way to link the output of Scala.js compilation from a Play template:

    +
    scala> cat("jvm/app/views/main.scala.html")
    +@(title: String)(content: Html)(implicit environment: play.api.Environment)
    +
    +<!DOCTYPE html>
    +<html>
    +  <head>
    +    <title>@title</title>
    +  </head>
    +  <body>
    +    @content
    +    @* Outputs a <script></script> tag to include the output of Scala.js compilation. *@
    +    @playscalajs.html.scripts(projectName = "js")
    +  </body>
    +</html>
    +
    +

    Let's define a simple case class for our example inside of the shared project to make it available to both JVM and JV platforms. We collocate a simple validation for this case class in its companion object:

    +
    scala> cat("shared/src/main/scala/User.scala")
    +package model
    +
    +import jto.validation._
    +import jto.validation.jsonast._
    +import scala.Function.unlift
    +
    +case class User(
    +  name: String,
    +  age: Int,
    +  email: Option[String],
    +  isAlive: Boolean
    +)
    +
    +object User {
    +  import Rules._, Writes._
    +  implicit val format: Format[JValue, JObject, User] =
    +    Formatting[JValue, JObject] { __ =>
    +      (
    +        (__ \ "name").format(notEmpty) ~
    +        (__ \ "age").format(min(0) |+| max(130)) ~
    +        (__ \ "email").format(optionR(email), optionW(stringW)) ~
    +        (__ \ "isAlive").format[Boolean]
    +      )(User.apply, unlift(User.unapply))
    +    }
    +}
    +
    +

    Note the use of jto.validation.jsonast here. This project implements in just a few lines of code an immutable version of the JSON specification based on Scala collections: (It might eventually be replaced with an external abstract syntax tree (AST), see discussion in https://github.com/scala/slip/pull/28)

    +
    scala> cat("../validation-jsonast/shared/src/main/scala/JValue.scala")
    +package jto.validation
    +package jsonast
    +
    +sealed trait JValue
    +case object JNull extends JValue
    +case class JObject (value: Map[String, JValue] = Map.empty) extends JValue
    +case class JArray  (value: Seq[JValue] = Seq.empty)         extends JValue
    +case class JBoolean(value: Boolean)                         extends JValue
    +case class JString (value: String)                          extends JValue
    +case class JNumber (value: String)                          extends JValue {
    +  require(JNumber.regex.matcher(value).matches)
    +}
    +
    +object JNumber {
    +  val regex = """-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?""".r.pattern
    +  def apply(i: Int): JNumber = JNumber(i.toString)
    +  def apply(l: Long): JNumber = JNumber(l.toString)
    +  def apply(d: Double): JNumber = JNumber(d.toString)
    +}
    +
    +

    This AST has the same capabilities than other JSON representations, but it does no provide a parser nor a pretty printer. The suggested approach here is to use conversions from this cross compiled AST to platform specific ones to take advantage of existing platform specific serialization. To do so, Validation provides the following Rules and Writes, defined in jto.validation.jsonast:

    +
      +
    • Ast.from: Rule[play.api.libs.json.JsValue, JValue]
    • +
    • Ast.to: Write[JValue, play.api.libs.json.JsValue]
    • +
    • Ast.from: Rule[scala.scalajs.jsDynamic, JValue]
    • +
    • Ast.to: Write[JValue, scala.scalajs.jsDynamic]
    • +
    +

    To use our previously defined validation, we could compose what we defined targeting the cross compiling JSON AST with the above Rules / Writes to finally obtain platform-specific validation.

    +

    One last technicality about Scala.js is the @JSExport annotation, which is used to explicitly expose Scala objects and methods to the javascript world. To complete our example, we define and expose a single method taking a JSON representation of our case class and returning the output of our validation, also a JSON:

    +
    scala> cat("js/src/main/scala/Validate.scala")
    +package client
    +
    +import jto.validation._
    +import jto.validation.jsonast.Ast
    +import jto.validation.jsjson._
    +import scala.scalajs.js
    +import js.annotation.JSExport
    +import model.User
    +import scala.Function.{unlift, const}
    +
    +@JSExport
    +object Validate {
    +  @JSExport
    +  def user(json: js.Dynamic): js.Dynamic = {
    +    import Writes._
    +
    +    implicit val format: Format[js.Dynamic, js.Dynamic, User] = Format(
    +      Ast.from andThen User.format,
    +      Write.toWrite(User.format) andThen Ast.to
    +    )
    +
    +    To[VA[User], js.Dynamic](format.validate(json))
    +  }
    +}
    +
    +

    Finally, we can create a simple view with a textarea which validates it's content on every keystroke:

    +
    scala> cat("jvm/app/views/index.scala.html")
    +@(json: String)(implicit environment: play.api.Environment)
    +
    +@main("Play Scala.js Validation") {
    +  <textarea id="json-form" rows="10" cols="40">@json</textarea>
    +  <pre id="validation-output"></pre>
    +}
    +
    +<script type="text/javascript">
    +  var validationOutputPre = document.getElementById("validation-output")
    +  var jsonFormTextarea = document.getElementById("json-form")
    +
    +  var demo = function() {
    +    try {
    +      var json = JSON.parse(jsonFormTextarea.value);
    +      validationOutputPre.innerHTML =
    +        JSON.stringify(client.Validate().user(json), null, 2);
    +    } catch(err) {
    +      validationOutputPre.innerHTML = err.message;
    +    }
    +  };
    +
    +  jsonFormTextarea.addEventListener('input', demo, false);
    +  demo();
    +</script>
    +
    +

    This complete code of this example is available in the play-scalajs-example subproject. The binary used to power the editor at the beginning of this page was generated by running Play in production mode, which fully optimizes the output of Scala.js compilation using the Google Closure Compiler to obtain a final .js file under 100KB once gzipped.

    + + +
    + +
    +
    +
    + +

    results matching ""

    +
      + +
      +
      + +

      No results matching ""

      + +
      +
      +
      + +
      +
      + +
      + + + + + + + + + + + + + + +
      + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationCookbook.html b/docs/book/ScalaValidationCookbook.html new file mode 100644 index 00000000..3cc6f6ee --- /dev/null +++ b/docs/book/ScalaValidationCookbook.html @@ -0,0 +1,661 @@ + + + + + Cookbook · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + + + + + + + + +
      + +
      + +
      + + + + + + + + +
      +
      + +
      +
      + +
      + +

      Cookbook

      +
      +

      All the examples below are validating Json objects. The API is not dedicated only to Json, it can be used on any type. Please refer to Validating Json, Validating Forms, and Supporting new types for more information.

      +
      +

      Rule

      +

      Typical case class validation

      +
      import jto.validation._
      +import play.api.libs.json._
      +
      +case class Creature(
      +  name: String,
      +  isDead: Boolean,
      +  weight: Float)
      +
      +implicit val creatureRule: Rule[JsValue, Creature] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +  ((__ \ "name").read[String] ~
      +   (__ \ "isDead").read[Boolean] ~
      +   (__ \ "weight").read[Float])(Creature.apply)
      +}
      +
      +
      scala> val js = Json.obj( "name" -> "gremlins", "isDead" -> false, "weight" -> 1.0f)
      +js: play.api.libs.json.JsObject = {"name":"gremlins","isDead":false,"weight":1}
      +
      +scala> From[JsValue, Creature](js)
      +res2: jto.validation.VA[Creature] = Valid(Creature(gremlins,false,1.0))
      +
      +scala> From[JsValue, Creature](Json.obj())
      +res3: jto.validation.VA[Creature] = Invalid(List((/name,List(ValidationError(List(error.required),WrappedArray()))), (/isDead,List(ValidationError(List(error.required),WrappedArray()))), (/weight,List(ValidationError(List(error.required),WrappedArray())))))
      +
      +

      Dependent values

      +

      A common example of this use case is the validation of password and password confirmation fields in a signup form.

      +
        +
      1. First, you need to validate that each field is valid independently
      2. +
      3. Then, given the two values, you need to validate that they are equals.
      4. +
      +
      import jto.validation._
      +import play.api.libs.json._
      +
      +val passRule: Rule[JsValue, String] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +  // This code creates a `Rule[JsValue, (String, String)]` each of of the String must be non-empty
      +  ((__ \ "password").read(notEmpty) ~
      +   (__ \ "verify").read(notEmpty)).tupled
      +       // We then create a `Rule[(String, String), String]` validating that given a `(String, String)`,
      +       // both strings are equals. Those rules are then composed together.
      +    .andThen(Rule.uncurry(equalTo[String])
      +      // In case of `Invalid`, we want to control the field holding the errors.
      +      // We change the `Path` of errors using `repath`
      +      .repath(_ => (Path \ "verify"))
      +    )
      +}
      +
      +

      Let's test it:

      +
      scala> passRule.validate(Json.obj("password" -> "foo", "verify" -> "foo"))
      +res5: jto.validation.VA[String] = Valid(foo)
      +
      +scala> passRule.validate(Json.obj("password" -> "", "verify" -> "foo"))
      +res6: jto.validation.VA[String] = Invalid(List((/password,List(ValidationError(List(error.required),WrappedArray())))))
      +
      +scala> passRule.validate(Json.obj("password" -> "foo", "verify" -> ""))
      +res7: jto.validation.VA[String] = Invalid(List((/verify,List(ValidationError(List(error.required),WrappedArray())))))
      +
      +scala> passRule.validate(Json.obj("password" -> "", "verify" -> ""))
      +res8: jto.validation.VA[String] = Invalid(List((/password,List(ValidationError(List(error.required),WrappedArray()))), (/verify,List(ValidationError(List(error.required),WrappedArray())))))
      +
      +scala> passRule.validate(Json.obj("password" -> "foo", "verify" -> "bar"))
      +res9: jto.validation.VA[String] = Invalid(List((/verify,List(ValidationError(List(error.equals),WrappedArray(foo))))))
      +
      +

      Recursive types

      +

      When validating recursive types:

      +
        +
      • Use the lazy keyword to allow forward reference.
      • +
      • As with any recursive definition, the type of the Rule must be explicitly given.
      • +
      +
      case class User(
      +  name: String,
      +  age: Int,
      +  email: Option[String],
      +  isAlive: Boolean,
      +  friend: Option[User])
      +
      +
      import jto.validation._
      +import play.api.libs.json._
      +
      +// Note the lazy keyword
      +implicit lazy val userRule: Rule[JsValue, User] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +
      +  ((__ \ "name").read[String] ~
      +   (__ \ "age").read[Int] ~
      +   (__ \ "email").read[Option[String]] ~
      +   (__ \ "isAlive").read[Boolean] ~
      +   (__ \ "friend").read[Option[User]])(User.apply)
      +}
      +
      +

      or using macros:

      +
      scala> import jto.validation._
      +import jto.validation._
      +
      +scala> import play.api.libs.json._
      +import play.api.libs.json._
      +
      +scala> import jto.validation.playjson.Rules._
      +import jto.validation.playjson.Rules._
      +
      +scala> // Note the lazy keyword, and the explicit typing
      +     | implicit lazy val userRule: Rule[JsValue, User] = Rule.gen[JsValue, User]
      +userRule: jto.validation.Rule[play.api.libs.json.JsValue,User] = <lazy>
      +
      +

      Read keys

      +
      import jto.validation._
      +import play.api.libs.json._
      +
      +val js = Json.parse("""
      +{
      +  "values": [
      +    { "foo": "bar" },
      +    { "bar": "baz" }
      +  ]
      +}
      +""")
      +
      +val r: Rule[JsValue, Seq[(String, String)]] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +
      +  val tupleR: Rule[JsValue, (String, String)] = Rule.fromMapping[JsValue, (String, String)] {
      +    case JsObject(Seq((key, JsString(value)))) => Valid(key.toString -> value)
      +    case _ => Invalid(Seq(ValidationError("BAAAM")))
      +  }
      +
      +  (__ \ "values").read(seqR(tupleR))
      +}
      +
      +
      scala> r.validate(js)
      +res15: jto.validation.VA[Seq[(String, String)]] = Invalid(List((/values[0],List(ValidationError(List(BAAAM),WrappedArray()))), (/values[1],List(ValidationError(List(BAAAM),WrappedArray())))))
      +
      +

      Validate subclasses (and parse the concrete class)

      +

      Consider the following class definitions:

      +
      trait A
      +case class B(foo: Int) extends A
      +case class C(bar: Int) extends A
      +
      +val b = Json.obj("name" -> "B", "foo" -> 4)
      +val c = Json.obj("name" -> "C", "bar" -> 6)
      +val e = Json.obj("name" -> "E", "eee" -> 6)
      +
      +

      Trying all the possible rules implementations

      +
      import cats.syntax.cartesian._
      +
      +val rb: Rule[JsValue, A] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +  (__ \ "name").read(equalTo("B")) *> (__ \ "foo").read[Int].map(B.apply)
      +}
      +
      +val rc: Rule[JsValue, A] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +  (__ \ "name").read(equalTo("C")) *> (__ \ "bar").read[Int].map(C.apply)
      +}
      +
      +val typeInvalid = Invalid(Seq(Path -> Seq(ValidationError("validation.unknownType"))))
      +val rule = rb orElse rc orElse Rule(_ => typeInvalid)
      +
      +
      scala> rule.validate(b)
      +res20: jto.validation.VA[A] = Valid(B(4))
      +
      +scala> rule.validate(c)
      +res21: jto.validation.VA[A] = Valid(C(6))
      +
      +scala> rule.validate(e)
      +res22: jto.validation.VA[A] = Invalid(List((/,List(ValidationError(List(validation.unknownType),WrappedArray())))))
      +
      +

      Using class discovery based on field discrimination

      +
      val typeInvalid = Invalid(Seq(Path -> Seq(ValidationError("validation.unknownType"))))
      +
      +val rule: Rule[JsValue, A] = From[JsValue] { __ =>
      +  import jto.validation.playjson.Rules._
      +  (__ \ "name").read[String].flatMap[A] {
      +    case "B" => (__ \ "foo").read[Int].map(B.apply _)
      +    case "C" => (__ \ "bar").read[Int].map(C.apply _)
      +    case _ => Rule(_ => typeInvalid)
      +  }
      +}
      +
      +
      scala> rule.validate(b)
      +res24: jto.validation.VA[A] = Valid(B(4))
      +
      +scala> rule.validate(c)
      +res25: jto.validation.VA[A] = Valid(C(6))
      +
      +scala> rule.validate(e)
      +res26: jto.validation.VA[A] = Invalid(List((/,List(ValidationError(List(validation.unknownType),WrappedArray())))))
      +
      +

      Write

      +

      typical case class Write

      +
      import jto.validation._
      +import play.api.libs.json._
      +import scala.Function.unlift
      +
      +case class Creature(
      +  name: String,
      +  isDead: Boolean,
      +  weight: Float)
      +
      +implicit val creatureWrite = To[JsObject] { __ =>
      +  import jto.validation.playjson.Writes._
      +  ((__ \ "name").write[String] ~
      +   (__ \ "isDead").write[Boolean] ~
      +   (__ \ "weight").write[Float])(unlift(Creature.unapply))
      +}
      +
      +
      scala> To[Creature, JsObject](Creature("gremlins", false, 1f))
      +res29: play.api.libs.json.JsObject = {"name":"gremlins","isDead":false,"weight":1}
      +
      +

      Adding static values to a Write

      +
      import jto.validation._
      +import play.api.libs.json._
      +
      +case class LatLong(lat: Float, long: Float)
      +
      +implicit val latLongWrite = {
      +  import jto.validation.playjson.Writes._
      +  To[JsObject] { __ =>
      +    ((__ \ "lat").write[Float] ~
      +     (__ \ "long").write[Float])(unlift(LatLong.unapply))
      +  }
      +}
      +
      +case class Point(coords: LatLong)
      +
      +implicit val pointWrite = {
      +  import jto.validation.playjson.Writes._
      +  To[JsObject] { __ =>
      +    ((__ \ "coords").write[LatLong] ~
      +     (__ \ "type").write[String]) ((_: Point).coords -> "point")
      +  }
      +}
      +
      +
      scala> val p = Point(LatLong(123.3F, 334.5F))
      +p: Point = Point(LatLong(123.3,334.5))
      +
      +scala> pointWrite.writes(p)
      +res34: play.api.libs.json.JsObject = {"coords":{"lat":123.3,"long":334.5},"type":"point"}
      +
      + + +
      + +
      +
      +
      + +

      results matching ""

      +
        + +
        +
        + +

        No results matching ""

        + +
        +
        +
        + +
        +
        + +
        + + + + + + + + + + +
        + + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationExtensions.html b/docs/book/ScalaValidationExtensions.html new file mode 100644 index 00000000..1abab182 --- /dev/null +++ b/docs/book/ScalaValidationExtensions.html @@ -0,0 +1,623 @@ + + + + + Extensions: Supporting new types · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + + + + + + + + +
        + +
        + +
        + + + + + + + + +
        +
        + +
        +
        + +
        + +

        Extensions: Supporting new types

        +

        The validation API is designed to be easily extensible. Supporting new types is just a matter of providing the appropriate set of Rules and Writes.

        +

        In this documentation, we'll study the implementation of the Json support. All extensions are to be defined in a similar fashion. The total amount of code needed is rather small, but there're best practices you need to follow.

        +

        Rules

        +

        The first step is to define what we call primitive rules. Primitive rules is a set of rules on which you could build any complex validation.

        +

        The base of all Rules is the capacity to extract a subset of some input data.

        +

        For the type JsValue, we need to be able to extract a JsValue at a given Path:

        +
        scala> import jto.validation._
        +import jto.validation._
        +
        +scala> import play.api.libs.json.{KeyPathNode => JSKeyPathNode, IdxPathNode => JIdxPathNode, _}
        +import play.api.libs.json.{KeyPathNode=>JSKeyPathNode, IdxPathNode=>JIdxPathNode, _}
        +
        +scala> object Ex1 {
        +     | 
        +     |   def pathToJsPath(p: Path) =
        +     |     play.api.libs.json.JsPath(p.path.map{
        +     |       case KeyPathNode(key) => JSKeyPathNode(key)
        +     |       case IdxPathNode(i) => JIdxPathNode(i)
        +     |     })
        +     | 
        +     |   implicit def pickInJson(p: Path): Rule[JsValue, JsValue] =
        +     |     Rule[JsValue, JsValue] { json =>
        +     |       pathToJsPath(p)(json) match {
        +     |         case Nil => Invalid(Seq(Path -> Seq(ValidationError("error.required"))))
        +     |         case js :: _ => Valid(js)
        +     |       }
        +     |     }
        +     | }
        +defined object Ex1
        +
        +

        Now we are able to do this:

        +
        scala> {
        +     |   import Ex1._
        +     | 
        +     |   val js = Json.obj(
        +     |   "field1" -> "alpha",
        +     |   "field2" -> 123L,
        +     |   "field3" -> Json.obj(
        +     |     "field31" -> "beta",
        +     |     "field32"-> 345))
        +     | 
        +     |   val pick: Rule[JsValue, JsValue] = From[JsValue] { __ =>
        +     |     (__ \ "field2").read[JsValue]
        +     |   }
        +     | 
        +     |   pick.validate(js)
        +     | }
        +res0: jto.validation.VA[play.api.libs.json.JsValue] = Valid(123)
        +
        +

        Which is nice, but is would be much more convenient if we could extract that value as an Int.

        +

        One solution is to write the following method:

        +
        implicit def pickIntInJson[O](p: Path): Rule[JsValue, JsValue] = ???
        +
        +

        But we would end up copying 90% of the code we already wrote. +Instead of doing so, we're going to make pickInJson a bit smarter by adding an implicit parameter:

        +
        implicit def pickInJson[O](p: Path)(implicit r: Rule[JsValue, O]): Rule[JsValue, O] =
        +    Rule[JsValue, JsValue] { json =>
        +      pathToJsPath(p)(json) match {
        +        case Nil => Invalid(Seq(Path -> Seq(ValidationError("error.required"))))
        +        case js :: _ => Valid(js)
        +      }
        +    }.andThen(r)
        +
        +

        The now all we have to do is to write a Rule[JsValue, O], and we automatically get the Path => Rule[JsValue, O] we're interested in. The rest is just a matter of defining all the primitives rules, for example:

        +
        scala> def jsonAs[T](f: PartialFunction[JsValue, Validated[Seq[ValidationError], T]])(args: Any*) =
        +     |   Rule.fromMapping[JsValue, T](
        +     |     f.orElse{ case j => Invalid(Seq(ValidationError("validation.invalid", args: _*)))
        +     |   })
        +jsonAs: [T](f: PartialFunction[play.api.libs.json.JsValue,jto.validation.Validated[Seq[jto.validation.ValidationError],T]])(args: Any*)jto.validation.Rule[play.api.libs.json.JsValue,T]
        +
        +scala> def stringRule = jsonAs[String] {
        +     |   case JsString(v) => Valid(v)
        +     | }("String")
        +stringRule: jto.validation.Rule[play.api.libs.json.JsValue,String]
        +
        +scala> def booleanRule = jsonAs[Boolean]{
        +     |   case JsBoolean(v) => Valid(v)
        +     | }("Boolean")
        +booleanRule: jto.validation.Rule[play.api.libs.json.JsValue,Boolean]
        +
        +

        The types you generally want to support natively are:

        +
          +
        • String
        • +
        • Boolean
        • +
        • Int
        • +
        • Short
        • +
        • Long
        • +
        • Float
        • +
        • Double
        • +
        • java BigDecimal
        • +
        • scala BigDecimal
        • +
        +

        Higher order Rules

        +

        Supporting primitives is nice, but not enough. Users are going to deal with Seq and Option. We need to support those types too.

        +

        Option

        +

        What we want to do is to implement a function that takes a Path => Rule[JsValue, O], an lift it into a Path => Rule[JsValue, Option[O]] for any type O. The reason we're working on the fully defined Path => Rule[JsValue, O] and not just Rule[JsValue, O] is because a non-existent Path must be validated as a Valid(None). If we were to use pickInJson on a Rule[JsValue, Option[O]], we would end up with an Invalid in the case of non-existing Path.

        +

        The play.api.data.mapping.DefaultRules[I] traits provides a helper for building the desired method. It's signature is:

        +
        protected def opt[J, O](r: => Rule[J, O], noneValues: Rule[I, I]*)(implicit pick: Path => Rule[I, I], coerce: Rule[I, J]): Path = Rule[I, O]
        +
        +
          +
        • noneValues is a List of all the values we should consider to be None. For Json that would be JsNull.
        • +
        • pick is an extractor. It's going to extract a subtree.
        • +
        • coerce is a type conversion Rule
        • +
        • r is a Rule to be applied to the data if they are found
        • +
        +

        All you have to do is to use this method to implement a specialized version for your type. +For example it's defined this way for Json:

        +
        def optionR[J, O](r: => Rule[J, O], noneValues: Rule[JsValue, JsValue]*)(implicit pick: Path => Rule[JsValue, JsValue], coerce: Rule[JsValue, J]): Path => Rule[JsValue, Option[O]]
        +    = super.opt[J, O](r, (jsNull.map(n => n: JsValue) +: noneValues):_*)
        +
        +

        Basically it's just the same, but we are now only supporting JsValue. We are also adding JsNull is the list of None-ish values.

        +

        Despite the type signature funkiness, this function is actually really simple to use:

        +
        val maybeEmail: Rule[JsValue, Option[String]] = From[JsValue] { __ =>
        +  import jto.validation.playjson.Rules._
        +  (__ \ "email").read(optionR(email))
        +}
        +
        +
        scala> maybeEmail.validate(Json.obj("email" -> "foo@bar.com"))
        +res1: jto.validation.VA[Option[String]] = Valid(Some(foo@bar.com))
        +
        +scala> maybeEmail.validate(Json.obj("email" -> "baam!"))
        +res2: jto.validation.VA[Option[String]] = Invalid(List((/email,List(ValidationError(List(error.email),WrappedArray())))))
        +
        +scala> maybeEmail.validate(Json.obj("email" -> JsNull))
        +res3: jto.validation.VA[Option[String]] = Valid(None)
        +
        +scala> maybeEmail.validate(Json.obj())
        +res4: jto.validation.VA[Option[String]] = Valid(None)
        +
        +

        Alright, so now we can explicitly define rules for optional data.

        +

        But what if we write (__ \ "age").read[Option[Int]]? It does not compile! +We need to define an implicit rule for that:

        +
        implicit def option[O](p: Path)(implicit pick: Path => Rule[JsValue, JsValue], coerce: Rule[JsValue, O]): Rule[JsValue, Option[O]] =
        +    option(Rule.zero[O])(pick, coerce)(p)
        +
        +
        val maybeAge: Rule[JsValue, Option[Int]] = From[JsValue] { __ =>
        +  import jto.validation.playjson.Rules._
        +  (__ \ "age").read[Option[Int]]
        +}
        +
        +

        Lazyness

        +

        It's very important that every Rule is completely lazily evaluated . The reason for that is that you may be validating recursive types:

        +
        scala> case class RecUser(name: String, friends: Seq[RecUser] = Nil)
        +defined class RecUser
        +
        +scala> val u = RecUser(
        +     |   "bob",
        +     |   Seq(RecUser("tom")))
        +u: RecUser = RecUser(bob,List(RecUser(tom,List())))
        +
        +scala> lazy val w: Rule[JsValue, RecUser] = From[JsValue] { __ =>
        +     |   import jto.validation.playjson.Rules._
        +     |   ((__ \ "name").read[String] ~
        +     |    (__ \ "friends").read(seqR(w)))(RecUser.apply) // !!! recursive rule definition
        +     | }
        +w: jto.validation.Rule[play.api.libs.json.JsValue,RecUser] = <lazy>
        +
        +

        Writes

        +

        Writes are implemented in a similar fashion, but a generally easier to implement. You start by defining a function for writing at a given path:

        +
        scala> {
        +     |   implicit def writeJson[I](path: Path)(implicit w: Write[I, JsValue]): Write[I, JsObject] = ???
        +     | }
        +
        +

        And you then define all the primitive writes:

        +
        scala> {
        +     |   implicit def anyval[T <: AnyVal] = ???
        +     | }
        +
        +

        Monoid

        +

        In order to be able to use writes combinators, you also need to create an implementation of Monoid for your output type. For example, to create a complex write of JsObject, we had to implement a Monoid[JsObject]:

        +
        scala> {
        +     |   import cats.Monoid
        +     |   implicit def jsonMonoid = new Monoid[JsObject] {
        +     |     def combine(a1: JsObject, a2: JsObject) = a1 deepMerge a2
        +     |     def empty = Json.obj()
        +     |   }
        +     | }
        +
        +

        from there you're able to create complex writes like:

        +
        import jto.validation._
        +import play.api.libs.json._
        +import scala.Function.unlift
        +
        +case class User(
        +  name: String,
        +  age: Int,
        +  email: Option[String],
        +  isAlive: Boolean)
        +
        +val userWrite = To[JsObject] { __ =>
        +  import jto.validation.playjson.Writes._
        +  ((__ \ "name").write[String] ~
        +   (__ \ "age").write[Int] ~
        +   (__ \ "email").write[Option[String]] ~
        +   (__ \ "isAlive").write[Boolean])(unlift(User.unapply))
        +}
        +
        +

        Testing

        +

        We highly recommend you to test your rules as much as possible. There're a few tricky cases you need to handle properly. You should port the tests in RulesSpec.scala and use them on your rules.

        + + +
        + +
        +
        +
        + +

        results matching ""

        +
          + +
          +
          + +

          No results matching ""

          + +
          +
          +
          + +
          +
          + +
          + + + + + + + + + + + + + + +
          + + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationJson.md b/docs/book/ScalaValidationJson.md new file mode 100644 index 00000000..802cf9a1 --- /dev/null +++ b/docs/book/ScalaValidationJson.md @@ -0,0 +1,134 @@ +## Validating Json + +Take this JSON example: + +```json +{ + "user": { + "name" : "toto", + "age" : 25, + "email" : "toto@jmail.com", + "isAlive" : true, + "friend" : { + "name" : "tata", + "age" : 20, + "email" : "tata@coldmail.com" + } + } +} +``` + +This can be seen as a tree structure using the 2 following structures: + +- **JSON object** contains a set of `name` / `value` pairs: + - `name` is a String + - `value` can be : + - string + - number + - another JSON object + - a JSON array + - true/false + - null +- **JSON array** is a sequence of values from the previously listed value types. + +> If you want to have more info about the exact JSON standard, please go to [json.org](http://json.org/) + +## Json Data Types + +`play.api.libs.json` package contains 7 JSON data types reflecting exactly the previous structure. +All types inherit from the generic JSON trait, ```JsValue```. As Stated in [the Json API documentation](https://www.playframework.com/documentation/2.3.x/ScalaJson), we can easily parse this String into a JsValue: + +```scala +scala> import play.api.libs.json._ +import play.api.libs.json._ + +scala> val json: JsValue = Json.parse(""" + | { + | "user": { + | "name" : "toto", + | "age" : 25, + | "email" : "toto@jmail.com", + | "isAlive" : true, + | "friend" : { + | "name" : "tata", + | "age" : 20, + | "email" : "tata@coldmail.com" + | } + | } + | } + | """) +json: play.api.libs.json.JsValue = {"user":{"name":"toto","age":25,"email":"toto@jmail.com","isAlive":true,"friend":{"name":"tata","age":20,"email":"tata@coldmail.com"}}} +``` + +This sample is used in all next samples. +The Validation API will work on the JsValue. + +## Accessing Path in a JSON tree + +The validation API defines a class named `Path`. A `Path` represents a location. Contrarely to `JsPath`, it's not related to any specific type, it's just a location in some data. Most of the time, a `Path` is our entry point into the Validation API. + +### Navigating in data using `Path` + +```scala +scala> import jto.validation.Path +import jto.validation.Path + +scala> val location: Path = Path \ "user" \ "friend" +location: jto.validation.Path = /user/friend +``` + +`Path` has a `read` method. Just as in the Json API, read will build a `Rule` looking for data of the given type, at that location. +`read` is a paramaterized method it takes two types parameter, `I` and `O`. `I` represent the input we're trying to parse, and `O` is the output type. + +For example, `(Path \ "foo").read[JsValue, Int]`, means the we want to parse a value located at path `foo`, in a JsValue, and parse it as an `Int`. + +But let's try something much much easier for now: + +```scala +scala> import jto.validation.Rule +import jto.validation.Rule + +scala> val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue] +:18: error: No implicit view available from jto.validation.Path => jto.validation.RuleLike[play.api.libs.json.JsValue,play.api.libs.json.JsValue]. + val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue] + ^ +``` + +`location.read[JsValue, JsValue]` means the we're trying lookup at `location` in a `JsValue`, and we expect to find a `JsValue` there. Effectivelly, we're just defining a `Rule` that is picking a subtree in a Json. + +As you can see, the compiler gives us an error! + +The scala compiler is complaining about not finding an implicit Function of type Path => Rule[JsValue, JsValue]. Indeed, unlike the Json API, you have to provide a method to "lookup" into the data you expect to validate. Fortunatelly, such method already exists and is provided for Json. All you have to do is import it: + +```scala +scala> import jto.validation.playjson.Rules._ +import jto.validation.playjson.Rules._ +``` + +By convention, all usefull validation methods for a given type are to be found in an object called `Rules`. That object contains a bunch of implicits defining how to lookup in the data, and how to coerce some of the possible values of those data into Scala types. + +With those implicits in scope, we can finally create our `Rule`. + +```scala +val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue] +``` + +Alright, so far we've defined a `Rule` looking for some data of type JsValue, in an object of type JsValue, at `/user/friend`. +Now we need to apply this `Rule` to our data. + +```scala +scala> findFriend.validate(json) +res0: jto.validation.VA[play.api.libs.json.JsValue] = Valid({"name":"tata","age":20,"email":"tata@coldmail.com"}) +``` + +When we apply a `Rule`, we have no guarantee whatsoever that it's going to succeed. There's various things that could fail, so instead of just returning some data of type `O`, `validate` returns an instance of `Validated`. +A `Validated` can only have two types: It's either a `Valid` containing the result we expect, or a `Invalid` containing all the errors along with their locations. + +Let's try something that we know will fail: We'll try to lookup for a JsValue at a non existing location: + +```scala +scala> (Path \ "somenonexistinglocation").read[JsValue, JsValue].validate(json) +res1: jto.validation.VA[play.api.libs.json.JsValue] = Invalid(List((/somenonexistinglocation,List(ValidationError(List(error.required),WrappedArray()))))) +``` + +This time `validate` returns `Invalid`. There's nothing at `somenonexistinglocation` and this failure tells us just that. We required a `JsValue` to be found at that Path, but our requirement was not fullfiled. Note that the `Invalid` does not just contain a `Path` and an error message. It contains a `List[(Path, List[ValidationError])]`. We'll see later that a single validation could find several errors at a given `Path`, AND find errors at different `Path` diff --git a/docs/book/ScalaValidationMacros.html b/docs/book/ScalaValidationMacros.html new file mode 100644 index 00000000..d9a2dd64 --- /dev/null +++ b/docs/book/ScalaValidationMacros.html @@ -0,0 +1,497 @@ + + + + + Validation Inception · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          +
          + + + + + + + + +
          + +
          + +
          + + + + + + + + +
          +
          + +
          +
          + +
          + +

          Validation Inception

          +

          Introduction

          +

          The validation API provides macro-based helpers to generate Rule and Write for case classes (or any class with a companion object providing apply / and unapply methods).

          +

          The generated code:

          +
            +
          • is completely typesafe
          • +
          • is compiled
          • +
          • does not rely on runtime introspection at all
          • +
          • is strictly equivalent to a hand-written definition
          • +
          +

          Example

          +

          Traditionally, for a given case class Person we would define a Rule like this:

          +
          scala> case class Person(name: String, age: Int, lovesChocolate: Boolean)
          +defined class Person
          +
          +
          import jto.validation._
          +import play.api.libs.json._
          +
          +implicit val personRule: Rule[JsValue, Person] = From[JsValue] { __ =>
          +  import jto.validation.playjson.Rules._
          +  ((__ \ "name").read[String] ~
          +   (__ \ "age").read[Int] ~
          +   (__ \ "lovesChocolate").read[Boolean])(Person.apply)
          +}
          +
          +

          Let's test it:

          +
          scala> val json = Json.parse("""{
          +     |   "name": "Julien",
          +     |   "age": 28,
          +     |   "lovesChocolate": true
          +     | }""")
          +json: play.api.libs.json.JsValue = {"name":"Julien","age":28,"lovesChocolate":true}
          +
          +scala> personRule.validate(json)
          +res1: jto.validation.VA[Person] = Valid(Person(Julien,28,true))
          +
          +

          The exact same Rule can be generated using Rule.gen:

          +
          import jto.validation._
          +import play.api.libs.json._
          +
          +implicit val personRule = {
          +  import jto.validation.playjson.Rules._ // let's not leak implicits everywhere
          +  Rule.gen[JsValue, Person]
          +}
          +
          +

          The validation result is identical :

          +
          scala> val json = Json.parse("""{
          +     |   "name": "Julien",
          +     |   "age": 28,
          +     |   "lovesChocolate": true
          +     | }""")
          +json: play.api.libs.json.JsValue = {"name":"Julien","age":28,"lovesChocolate":true}
          +
          +scala> personRule.validate(json)
          +res3: jto.validation.VA[Person] = Valid(Person(Julien,28,true))
          +
          +

          Similarly we can generate a Write:

          +
          import jto.validation._
          +import play.api.libs.json._
          +
          +implicit val personWrite = {
          +  import jto.validation.playjson.Writes._ // let's no leak implicits everywhere
          +  Write.gen[Person, JsObject]
          +}
          +
          +
          scala> personWrite.writes(Person("Julien", 28, true))
          +res5: play.api.libs.json.JsObject = {"name":"Julien","age":28,"lovesChocolate":true}
          +
          +

          Known limitations

          +
            +
          • Don’t override the apply method of the companion object. The macro inspects the apply method to generate Rule/Write. Overloading the apply method creates an ambiguity the compiler will complain about.
          • +
          • Macros only work when apply and unapply have corresponding input/output types. This is naturally true for case classes. However if you want to validate a trait, you must implement the same apply/unapply you would have in a case class.
          • +
          • Validation Macros accept Option/Seq/List/Set & Map[String, _]. For other generic types, you'll have to test and possibly write your Rule/Write if it's not working out of the box.
          • +
          + + +
          + +
          +
          +
          + +

          results matching ""

          +
            + +
            +
            + +

            No results matching ""

            + +
            +
            +
            + +
            +
            + +
            + + + + + + + + + + + + + + +
            + + +
            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationMigrationForm.html b/docs/book/ScalaValidationMigrationForm.html new file mode 100644 index 00000000..f9553999 --- /dev/null +++ b/docs/book/ScalaValidationMigrationForm.html @@ -0,0 +1,560 @@ + + + + + Play's Form API migration · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            +
            + + + + + + + + +
            + +
            + +
            + + + + + + + + +
            +
            + +
            +
            + +
            + +

            Form API migration

            +

            Although the new Validation API differs significantly from the Form API, migrating to new API is straightforward. +This example is a case study of the migration of one of play sample application: "computer database".

            +

            We'll consider Application.scala. This controller takes care of Computer creation, and edition. The models are defined in Models.scala

            +
            case class Company(id: Pk[Long] = NotAssigned, name: String)
            +case class Computer(id: Pk[Long] = NotAssigned, name: String, introduced: Option[Date], discontinued: Option[Date], companyId: Option[Long])
            +
            +

            Here's the Application controller, before migration:

            +
            package controllers
            +
            +import play.api._
            +import play.api.mvc._
            +import play.api.data._
            +import play.api.data.Forms._
            +import anorm._
            +import views._
            +import models._
            +
            +object Application extends Controller {
            +
            +  /** Describe the computer form (used in both edit and create screens). */
            +  val computerForm = Form(
            +    mapping(
            +      "id" -> ignored(NotAssigned:Pk[Long]),
            +      "name" -> nonEmptyText,
            +      "introduced" -> optional(date("yyyy-MM-dd")),
            +      "discontinued" -> optional(date("yyyy-MM-dd")),
            +      "company" -> optional(longNumber)
            +    )(Computer.apply)(Computer.unapply)
            +  )
            +
            +  def index = // ...
            +
            +  def list(page: Int, orderBy: Int, filter: String) = // ...
            +
            +  def edit(id: Long) = Action {
            +    Computer.findById(id).map { computer =>
            +      Ok(html.editForm(id, computerForm.fill(computer), Company.options))
            +    }.getOrElse(NotFound)
            +  }
            +
            +  def update(id: Long) = Action { implicit request =>
            +    computerForm.bindFromRequest.fold(
            +      formWithErrors => BadRequest(html.editForm(id, formWithErrors, Company.options)),
            +      computer => {
            +        Computer.update(id, computer)
            +        Home.flashing("success" -> "Computer %s has been updated".format(computer.name))
            +      }
            +    )
            +  }
            +
            +  def create = Action {
            +    Ok(html.createForm(computerForm, Company.options))
            +  }
            +
            +  def save = Action { implicit request =>
            +    computerForm.bindFromRequest.fold(
            +      formWithErrors => BadRequest(html.createForm(formWithErrors, Company.options)),
            +      computer => {
            +        Computer.insert(computer)
            +        Home.flashing("success" -> "Computer %s has been created".format(computer.name))
            +      }
            +    )
            +  }
            +
            +  def delete(id: Long) = // ...
            +
            +}
            +
            +

            Validation rules migration

            +

            The first thing we must change is the definition of the Computer validations. +Instead of using play.api.data.Form, we must define a Rule[UrlFormEncoded, Computer].

            +

            UrlFormEncoded is simply an alias for Map[String, Seq[String]], which is the type used by play for form encoded request bodies.

            +

            Even though the syntax looks different, the logic is basically the same.

            +
            import java.util.Date
            +
            +case class Computer(id: Option[Long] = None, name: String, introduced: Option[Date], discontinued: Option[Date], companyId: Option[Long])
            +
            +import jto.validation._
            +import jto.validation.forms.UrlFormEncoded
            +
            +implicit val computerValidated = From[UrlFormEncoded] { __ =>
            +  import jto.validation.forms.Rules._
            +  ((__ \ "id").read(ignored[UrlFormEncoded, Option[Long]](None)) ~
            +   (__ \ "name").read(notEmpty) ~
            +   (__ \ "introduced").read(optionR(dateR("yyyy-MM-dd"))) ~
            +   (__ \ "discontinued").read(optionR(dateR("yyyy-MM-dd"))) ~
            +   (__ \ "company").read[Option[Long]])(Computer.apply)
            +}
            +
            +

            You start by defining a simple validation for each field.

            +

            For example "name" -> nonEmptyText now becomes (__ \ "name").read(notEmpty) +The next step is to compose these validations together, to get a new validation.

            +

            The old api does that using a function called mapping, the validation api uses a method called ~ or and (and is an alias).

            +
            mapping(
            +  "name" -> nonEmptyText,
            +  "introduced" -> optional(date("yyyy-MM-dd"))
            +
            +

            now becomes

            +
            (__ \ "name").read(notEmpty) ~
            +(__ \ "introduced").read(optionR(dateR("yyyy-MM-dd")))
            +
            +

            A few built-in validations have a slightly different name than in the Form api, like optional that became option. You can find all the built-in rules in the scaladoc.

            +
            +

            Be careful with your imports. Some rules have the same names than form mapping, which could make the implicit parameters resolution fail silently.

            +
            +

            Filling a Form with an object

            +

            The new validation API comes with a Form class. This class is fully compatible with the existing form input helpers. +You can use the Form.fill method to create a Form from a class.

            +

            Form.fill needs an instance of Write[T, UrlFormEncoded], where T is your class type.

            +
            import scala.Function.unlift
            +
            +implicit val computerW = To[UrlFormEncoded] { __ =>
            +  import jto.validation.forms.Writes._
            +  ((__ \ "id").write[Option[Long]] ~
            +   (__ \ "name").write[String] ~
            +   (__ \ "introduced").write(optionW(dateW("yyyy-MM-dd"))) ~
            +   (__ \ "discontinued").write(optionW(dateW("yyyy-MM-dd"))) ~
            +   (__ \ "company").write[Option[Long]])(unlift(Computer.unapply))
            +}
            +
            +
            +

            Note that this Write takes care of formatting.

            +
            +

            Validating the submitted form

            +

            Handling validation errors is vastly similar to the old api, the main difference is that bindFromRequest does not exist anymore.

            +
            def save = Action(parse.urlFormEncoded) { implicit request =>
            +  val r = computerValidated.validate(request.body)
            +  r.fold(
            +    err => BadRequest(html.createForm((request.body, r), Company.options)),
            +    computer => {
            +      Computer.insert(computer)
            +      Home.flashing("success" -> "Computer %s has been updated".format(computer.name))
            +    }
            +  )
            +}
            +
            + + +
            + +
            +
            +
            + +

            results matching ""

            +
              + +
              +
              + +

              No results matching ""

              + +
              +
              +
              + +
              +
              + +
              + + + + + + + + + + + + + + +
              + + +
              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationMigrationJson.html b/docs/book/ScalaValidationMigrationJson.html new file mode 100644 index 00000000..f2c5f342 --- /dev/null +++ b/docs/book/ScalaValidationMigrationJson.html @@ -0,0 +1,659 @@ + + + + + Play's Json API migration · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
              +
              + + + + + + + + +
              + +
              + +
              + + + + + + + + +
              +
              + +
              +
              + +
              + +

              Migration from the Json API

              +

              The Json API and the new validation API are really similar. One could see the new Validation API as just an evolution of the Json API.

              +
              +

              The json validation API still works just fine but we recommend you use the new validation API for new code, and to port your old code whenever it's possible.

              +
              +

              Reads migration

              +

              The equivalent of a Json Reads is a Rule. The key difference is that Reads assumes Json input, while Rule is more generic, and therefore has one more type parameter.

              +

              Basically Reads[String] == Rule[JsValue, String].

              +

              Migrating a Json Reads to a Rule is just a matter of modifying imports and specifying the input type.

              +

              Let's take a typical example from the Json API documentation:

              +
              case class Creature(
              +  name: String,
              +  isDead: Boolean,
              +  weight: Float)
              +
              +

              Using the json API, you would have defined something like:

              +
              scala> {
              +     |   import play.api.libs.json._
              +     |   import play.api.libs.functional.syntax._
              +     | 
              +     |   implicit val creatureReads = (
              +     |     (__ \ "name").read[String] and
              +     |     (__ \ "isDead").read[Boolean] and
              +     |     (__ \ "weight").read[Float]
              +     |   )(Creature.apply _)
              +     | 
              +     |   val js = Json.obj( "name" -> "gremlins", "isDead" -> false, "weight" -> 1.0F)
              +     |   Json.fromJson[Creature](js)
              +     | }
              +res0: play.api.libs.json.JsResult[Creature] = JsSuccess(Creature(gremlins,false,1.0),)
              +
              +

              Using the new API, this code becomes:

              +
              import jto.validation._
              +import play.api.libs.json._
              +
              +implicit val creatureRule: Rule[JsValue, Creature] = From[JsValue] { __ =>
              +  import jto.validation.playjson.Rules._
              +  ((__ \ "name").read[String] ~
              +   (__ \ "isDead").read[Boolean] ~
              +   (__ \ "weight").read[Float])(Creature.apply)
              +}
              +
              +
              scala> val js = Json.obj( "name" -> "gremlins", "isDead" -> false, "weight" -> 1.0F)
              +js: play.api.libs.json.JsObject = {"name":"gremlins","isDead":false,"weight":1}
              +
              +scala> From[JsValue, Creature](js)
              +res2: jto.validation.VA[Creature] = Valid(Creature(gremlins,false,1.0))
              +
              +

              Which apart from the extra imports is very similar. Notice the From[JsValue]{...} block, that's one of the nice features of the new validation API. Not only it avoids type repetition, but it also scopes the implicits.

              +
              +

              Important: Note that we're importing Rules._ inside the From[JsValue]{...} block. +It is recommended to always follow this pattern, as it nicely scopes the implicits, avoiding conflicts and accidental shadowing.

              +
              +

              readNullable

              +

              The readNullable method does not exists anymore. Just use a Rule[JsValue, Option[T]] instead. null and non existing Path will be handled correctly and give you a None:

              +
              val nullableStringRule: Rule[JsValue, Option[String]] = From[JsValue] { __ =>
              +  import jto.validation.playjson.Rules._
              +  (__ \ "foo").read[Option[String]]
              +}
              +
              +val js1 = Json.obj("foo" -> "bar")
              +val js2 = Json.obj("foo" -> JsNull)
              +val js3 = Json.obj()
              +
              +
              scala> nullableStringRule.validate(js1)
              +res4: jto.validation.VA[Option[String]] = Valid(Some(bar))
              +
              +scala> nullableStringRule.validate(js2)
              +res5: jto.validation.VA[Option[String]] = Valid(None)
              +
              +scala> nullableStringRule.validate(js3)
              +res6: jto.validation.VA[Option[String]] = Valid(None)
              +
              +

              keepAnd

              +

              The general use for keepAnd is to apply two validation on the same JsValue, for example:

              +
              {
              +  import play.api.libs.json._
              +  import Reads._
              +  import play.api.libs.functional.syntax._
              +  (JsPath \ "key1").read[String](email keepAnd minLength[String](5))
              +}
              +
              +

              You can achieve the same thing in the Validation API using Rules composition

              +
              From[JsValue] { __ =>
              +  import jto.validation.playjson.Rules._
              +  (__ \ "key1").read(email |+| minLength(5))
              +}
              +
              +

              lazy reads

              +

              Reads are always lazy in the new validation API, therefore, you don't need to use any specific function, even for recursive types:

              +
              import play.api.libs.json._
              +import play.api.libs.functional.syntax._
              +
              +case class User(id: Long, name: String, friend: Option[User] = None)
              +
              +implicit lazy val UserReads: Reads[User] = (
              +  (__ \ 'id).read[Long] and
              +  (__ \ 'name).read[String] and
              +  (__ \ 'friend).lazyReadNullable(UserReads)
              +)(User.apply _)
              +
              +val js = Json.obj(
              +  "id" -> 123L,
              +  "name" -> "bob",
              +  "friend" -> Json.obj("id" -> 124L, "name" -> "john", "friend" -> JsNull))
              +
              +
              scala> Json.fromJson[User](js)
              +res12: play.api.libs.json.JsResult[User] = JsSuccess(User(123,bob,Some(User(124,john,None))),)
              +
              +

              becomes:

              +
              case class User(id: Long, name: String, friend: Option[User] = None)
              +
              +implicit lazy val userRule: Rule[JsValue, User] = From[JsValue] { __ =>
              +  import jto.validation.playjson.Rules._
              +  ((__ \ "id").read[Long] ~
              +   (__ \ "name").read[String] ~
              +   (__ \ "friend").read(optionR(userRule)))(User.apply)
              +}
              +
              +val js = Json.obj(
              +  "id" -> 123L,
              +  "name" -> "bob",
              +  "friend" -> Json.obj("id" -> 124L, "name" -> "john", "friend" -> JsNull))
              +
              +
              scala> From[JsValue, User](js)
              +res15: jto.validation.VA[User] = Valid(User(123,bob,Some(User(124,john,None))))
              +
              +

              Numeric types

              +

              You should be aware that numeric type coercion is a bit stricter in the validation API.

              +

              For example:

              +
              scala> val js = Json.obj("n" -> 42.5f)
              +js: play.api.libs.json.JsObject = {"n":42.5}
              +
              +scala> js.validate((__ \ "n").read[Int]) // JsSuccess(42, /n)
              +res16: play.api.libs.json.JsResult[Int] = JsError(List((/n,List(ValidationError(List(error.expected.int),WrappedArray())))))
              +
              +

              whereas with the validation API, an Int must really be an Int:

              +
              import json.Rules._
              +val js = Json.obj("n" -> 42.5f)
              +(Path \ "n").read[JsValue, Int].validate(js)
              +
              +

              json.apply and path.as[T]

              +

              Those methods do not exist in the validation API. Even in the json API, it is generally recommended not to use them as they are "unsafe".

              +

              The preferred solution is to use path.read[T] and to handle failure properly.

              +
              {
              +  val js = Json.obj("foo" -> "bar")
              +  (js \ "foo").as[String]
              +}
              +
              +

              becomes

              +
              {
              +  import jto.validation.playjson.Rules._
              +  (Path \ "foo").read[JsValue, String]
              +}
              +
              +

              pickBranch

              +

              JsPath has a prickBranch method, that creates a Reads extracting a subtree in a Json object:

              +

              For example, given the following json object, we can extract a sub tree:

              +
              {
              +  import play.api.libs.json._
              +
              +  val js = Json.obj(
              +    "field1" -> "alpha",
              +    "field2" -> 123L,
              +    "field3" -> Json.obj(
              +      "field31" -> "beta",
              +      "field32"-> 345
              +    ))
              +
              +  val pick = (__ \ "field3").json.pickBranch
              +  pick.reads(js) // Valid({"field3":{"field31":"beta","field32":345}})
              +}
              +
              +

              In the validation API, you simply use read to create a rule picking a branch:

              +
              import jto.validation._
              +import play.api.libs.json._
              +
              +val js = Json.obj(
              +  "field1" -> "alpha",
              +  "field2" -> 123L,
              +  "field3" -> Json.obj(
              +    "field31" -> "beta",
              +    "field32"-> 345
              +  ))
              +
              +val pick: Rule[JsValue, JsValue] = From[JsValue] { __ =>
              +  import jto.validation.playjson.Rules._
              +  (__ \ "field3").read[JsValue]
              +}
              +
              +
              scala> pick.validate(js)
              +res22: jto.validation.VA[play.api.libs.json.JsValue] = Valid({"field31":"beta","field32":345})
              +
              +

              Writes migration

              +

              Writes are really easy to port. Just like Reads, it's basically a matter of adding imports.

              +

              For example, you would have defined a Writes for the Creature case class this way:

              +
              import play.api.libs.json._
              +import scala.Function.unlift
              +
              +case class Creature(
              +  name: String,
              +  isDead: Boolean,
              +  weight: Float)
              +
              +implicit val creatureWrite = (
              +  (__ \ "name").write[String] and
              +  (__ \ "isDead").write[Boolean] and
              +  (__ \ "weight").write[Float]
              +)(unlift(Creature.unapply))
              +
              +Json.toJson(Creature("gremlins", false, 1f))
              +
              +

              With the validation API:

              +
              import jto.validation._
              +import play.api.libs.json._
              +import scala.Function.unlift
              +
              +case class Creature(
              +  name: String,
              +  isDead: Boolean,
              +  weight: Float)
              +
              +implicit val creatureWrite = To[JsObject]{ __ =>
              +  import jto.validation.playjson.Writes._
              +  ((__ \ "name").write[String] ~
              +   (__ \ "isDead").write[Boolean] ~
              +   (__ \ "weight").write[Float])(unlift(Creature.unapply))
              +}
              +
              +
              scala> val c = To[Creature, JsObject](Creature("gremlins", false, 1f))
              +c: play.api.libs.json.JsObject = {"name":"gremlins","isDead":false,"weight":1}
              +
              +

              Format migration

              +

              The validation API does not have an equivalent for Format. We find that generally Format is not really convenient since validation and serialization are rarely symmetrical, and you quite often end up having multiple Reads for a given type, making Format rather unsettling.

              +

              Json Inception (macro)

              +

              Macros are also available for the validation API. See Validation Inception.

              + + +
              + +
              +
              +
              + +

              results matching ""

              +
                + +
                +
                + +

                No results matching ""

                + +
                +
                +
                + +
                +
                + +
                + + + + + + + + + + + + + + +
                + + +
                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationRule.html b/docs/book/ScalaValidationRule.html new file mode 100644 index 00000000..d1a7c47b --- /dev/null +++ b/docs/book/ScalaValidationRule.html @@ -0,0 +1,581 @@ + + + + + Validating and transforming data · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                +
                + + + + + + + + +
                + +
                + +
                + + + + + + + + +
                +
                + +
                +
                + +
                + +

                Validating and transforming data

                +

                Introduction

                +

                The API is designed around the concept of Rule. A Rule[I, O] defines a way to validate and coerce data, from type I to type O. It's basically a function I => Validated[O], where I is the type of the input to validate, and O is the expected output type.

                +

                A simple example

                +

                Let's say you want to coerce a String into an Float. +All you need to do is to define a Rule from String to Float:

                +
                import jto.validation._
                +def isFloat: Rule[String, Float] = ???
                +
                +

                When a String is parsed into an Float, two scenarios are possible, either:

                +
                  +
                • The String can be parsed as a Float.
                • +
                • The String can NOT be parsed as a Float
                • +
                +

                In a typical Scala application, you would use Float.parseFloat to parse a String. On an "invalid" value, this method throws a NumberFormatException.

                +

                When validating data, we'd certainly prefer to avoid exceptions, as the failure case is expected to happen quite often.

                +

                Furthermore, your application should handle it properly, for example by sending a nice error message to the end user. The execution flow of the application should not be altered by a parsing failure, but rather be part of the process. Exceptions are definitely not the appropriate tool for the job.

                +

                Back, to our Rule. For now we'll not implement isFloat, actually, the validation API comes with a number of built-in Rules, including the Float parsing Rule[String, Float].

                +

                All you have to do is import the default Rules.

                +
                import jto.validation._
                +object Rules extends GenericRules with ParsingRules
                +Rules.floatR
                +
                +

                Let's now test it against different String values:

                +
                scala> Rules.floatR.validate("1")
                +res1: jto.validation.VA[Float] = Valid(1.0)
                +
                +scala> Rules.floatR.validate("-13.7")
                +res2: jto.validation.VA[Float] = Valid(-13.7)
                +
                +scala> Rules.floatR.validate("abc")
                +res3: jto.validation.VA[Float] = Invalid(List((/,List(ValidationError(List(error.number),WrappedArray(Float))))))
                +
                +
                +

                Rule is typesafe. You can't apply a Rule on an unsupported type, the compiler won't let you:

                +
                scala> Rules.floatR.validate(Seq(32))
                +<console>:20: error: type mismatch;
                + found   : Seq[Int]
                + required: String
                +       Rules.floatR.validate(Seq(32))
                +                                ^
                +
                +
                +

                "abc" is not a valid Float but no exception was thrown. Instead of relying on exceptions, validate is returning an object of type Validated (here VA is just a fancy alias for a special kind of validation).

                +

                Validated represents possible outcomes of Rule application, it can be either :

                +
                  +
                • A Valid, holding the value being validated +When we use Rule.float on "1", since "1" is a valid representation of a Float, it returns Valid(1.0)
                • +
                • A Invalid, containing all the errors. +When we use Rule.float on "abc", since "abc" is not a valid representation of a Float, it returns Invalid(List((/,List(ValidationError(validation.type-mismatch,WrappedArray(Float)))))). That Invalid tells us all there is to know: it give us a nice message explaining what has failed, and even gives us a parameter "Float", indicating which type the Rule expected to find.
                • +
                +
                +

                Note that Validated is a parameterized type. Just like Rule, it keeps track of the input and output types. +The method validate of a Rule[I, O] always return a VA[I, O]

                +
                +

                Defining your own Rules

                +

                Creating a new Rule is almost as simple as creating a new function. +All there is to do is to pass a function I => Validated[I, O] to Rule.fromMapping.

                +

                This example creates a new Rule trying to get the first element of a List[Int]. +In case of an empty List[Int], the rule should return a Invalid.

                +
                val headInt: Rule[List[Int], Int] = Rule.fromMapping {
                +  case Nil => Invalid(Seq(ValidationError("error.emptyList")))
                +  case head :: _ => Valid(head)
                +}
                +
                +
                scala> headInt.validate(List(1, 2, 3, 4, 5))
                +res5: jto.validation.VA[Int] = Valid(1)
                +
                +scala> headInt.validate(Nil)
                +res6: jto.validation.VA[Int] = Invalid(List((/,List(ValidationError(List(error.emptyList),WrappedArray())))))
                +
                +

                We can make this rule a bit more generic:

                +
                def head[T]: Rule[List[T], T] = Rule.fromMapping {
                +  case Nil => Invalid(Seq(ValidationError("error.emptyList")))
                +  case head :: _ => Valid(head)
                +}
                +
                +
                scala> head.validate(List('a', 'b', 'c', 'd'))
                +res7: jto.validation.VA[Char] = Valid(a)
                +
                +scala> head.validate(List[Char]())
                +res8: jto.validation.VA[Char] = Invalid(List((/,List(ValidationError(List(error.emptyList),WrappedArray())))))
                +
                +

                Composing Rules

                +

                Rules composition is very important in this API. Rule composition means that given two Rule a and b, we can easily create a new Rule c.

                +

                There two different types of composition

                +

                "Sequential" composition

                +

                Sequential composition means that given two rules a: Rule[I, J] and b: Rule[J, O], we can create a new rule c: Rule[I, O].

                +

                Consider the following example: We want to write a Rule that given a List[String], takes the first String in that List, and try to parse it as a Float.

                +

                We already have defined:

                +
                  +
                1. head: Rule[List[T], T] returns the first element of a List
                2. +
                3. float: Rule[String, Float] parses a String into a Float
                4. +
                +

                We've done almost all the work already. We just have to create a new Rule the applies the first Rule and if it returns a Valid, apply the second Rule.

                +

                It would be fairly easy to create such a Rule "manually", but we don't have to. A method doing just that is already available:

                +
                val firstFloat: Rule[List[String], Float] = head.andThen(Rules.floatR)
                +
                +
                scala> firstFloat.validate(List("1", "2"))
                +res9: jto.validation.VA[Float] = Valid(1.0)
                +
                +scala> firstFloat.validate(List("1.2", "foo"))
                +res10: jto.validation.VA[Float] = Valid(1.2)
                +
                +

                If the list is empty, we get the error from head

                +
                scala> firstFloat.validate(List())
                +res11: jto.validation.VA[Float] = Invalid(List((/,List(ValidationError(List(error.emptyList),WrappedArray())))))
                +
                +

                If the first element is not parseable, we get the error from Rules.float.

                +
                scala> firstFloat.validate(List("foo", "2"))
                +res12: jto.validation.VA[Float] = Invalid(List((/,List(ValidationError(List(error.number),WrappedArray(Float))))))
                +
                +

                Of course everything is still typesafe:

                +
                scala> firstFloat.validate(List(1, 2, 3))
                +<console>:22: error: type mismatch;
                + found   : Int(1)
                + required: String
                +       firstFloat.validate(List(1, 2, 3))
                +                                ^
                +<console>:22: error: type mismatch;
                + found   : Int(2)
                + required: String
                +       firstFloat.validate(List(1, 2, 3))
                +                                   ^
                +<console>:22: error: type mismatch;
                + found   : Int(3)
                + required: String
                +       firstFloat.validate(List(1, 2, 3))
                +                                      ^
                +
                +

                Improving reporting.

                +

                All is fine with our new Rule but the error reporting when we parse an element is not perfect yet. +When a parsing error happens, the Invalid does not tell us that it happened on the first element of the List.

                +

                To fix that, we can pass an additionnal parameter to andThen:

                +
                val firstFloat2: Rule[List[String],Float] = head.andThen(Path \ 0)(Rules.floatR)
                +
                +
                scala> firstFloat2.validate(List("foo", "2"))
                +res14: jto.validation.VA[Float] = Invalid(List(([0],List(ValidationError(List(error.number),WrappedArray(Float))))))
                +
                +

                "Parallel" composition

                +

                Parallel composition means that given two rules a: Rule[I, O] and b: Rule[I, O], we can create a new rule c: Rule[I, O].

                +

                This form of composition is almost exclusively used for the particular case of rules that are purely constraints, that is, a Rule[I, I] checking a value of type I satisfies a predicate, but does not transform that value.

                +

                Consider the following example: We want to write a Rule that given an Int, check that this Int is positive and even. +The validation API already provides Rules.min, we have to define even ourselves:

                +
                val positive: Rule[Int,Int] = Rules.min(0)
                +val even: Rule[Int,Int] = Rules.validateWith[Int]("error.even"){ _ % 2 == 0 }
                +
                +

                Now we can compose those rules using |+|

                +
                val positiveAndEven: Rule[Int,Int] = positive |+| even
                +
                +

                Let's test our new Rule:

                +
                scala> positiveAndEven.validate(12)
                +res15: jto.validation.VA[Int] = Valid(12)
                +
                +scala> positiveAndEven.validate(-12)
                +res16: jto.validation.VA[Int] = Invalid(ArrayBuffer((/,List(ValidationError(List(error.min),WrappedArray(0))))))
                +
                +scala> positiveAndEven.validate(13)
                +res17: jto.validation.VA[Int] = Invalid(ArrayBuffer((/,List(ValidationError(List(error.even),WrappedArray())))))
                +
                +scala> positiveAndEven.validate(-13)
                +res18: jto.validation.VA[Int] = Invalid(ArrayBuffer((/,List(ValidationError(List(error.min),WrappedArray(0)), ValidationError(List(error.even),WrappedArray())))))
                +
                +

                Note that both rules are applied. If both fail, we get two ValidationError.

                + + +
                + +
                +
                +
                + +

                results matching ""

                +
                  + +
                  +
                  + +

                  No results matching ""

                  + +
                  +
                  +
                  + +
                  +
                  + +
                  + + + + + + + + + + +
                  + + +
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationRuleCombinators.html b/docs/book/ScalaValidationRuleCombinators.html new file mode 100644 index 00000000..44149d9c --- /dev/null +++ b/docs/book/ScalaValidationRuleCombinators.html @@ -0,0 +1,645 @@ + + + + + Combining Rules · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  +
                  + + + + + + + + +
                  + +
                  + +
                  + + + + + + + + +
                  +
                  + +
                  +
                  + +
                  + +

                  Combining Rules

                  +

                  Introduction

                  +

                  We've already explained what a Rule is in the previous chapter. +Those examples were only covering simple rules. However most of the time, rules are used to validate and transform complex hierarchical objects, like Json, or Forms.

                  +

                  The validation API allows complex object rules creation by combining simple rules together. This chapter explains how to create complex rules.

                  +
                  +

                  Despite examples below are validating Json objects, the API is not dedicated only to Json and can be used on any type. +Please refer to Validating Json, Validating Forms, and Supporting new types for more information.

                  +
                  +

                  Path

                  +

                  The validation API defines a class named Path. A Path represents the location of a data among a complex object. +Unlike JsPath it is not related to any specific type. It's just a location in some data. +Most of the time, a Path is our entry point into the Validation API.

                  +

                  A Path is declared using this syntax:

                  +
                  scala> import jto.validation.Path
                  +import jto.validation.Path
                  +
                  +scala> val path = Path \ "foo" \ "bar"
                  +path: jto.validation.Path = /foo/bar
                  +
                  +

                  Path here is the empty Path object. One may call it the root path.

                  +

                  A path can also reference indexed data, such as a Seq

                  +
                  scala> val pi = Path \ "foo" \ 0
                  +pi: jto.validation.Path = /foo[0]
                  +
                  +

                  Extracting data using Path

                  +

                  Consider the following json:

                  +
                  scala> import play.api.libs.json._
                  +import play.api.libs.json._
                  +
                  +scala> val js: JsValue = Json.parse("""{
                  +     |   "user": {
                  +     |     "name" : "toto",
                  +     |     "age" : 25,
                  +     |     "email" : "toto@jmail.com",
                  +     |     "isAlive" : true,
                  +     |     "friend" : {
                  +     |       "name" : "tata",
                  +     |       "age" : 20,
                  +     |       "email" : "tata@coldmail.com"
                  +     |     }
                  +     |   }
                  +     | }""")
                  +js: play.api.libs.json.JsValue = {"user":{"name":"toto","age":25,"email":"toto@jmail.com","isAlive":true,"friend":{"name":"tata","age":20,"email":"tata@coldmail.com"}}}
                  +
                  +

                  The first step before validating anything is to be able to access a fragment of the complex object.

                  +

                  Assuming you'd like to validate that friend exists and is valid in this json, you first need to access the object located at user.friend (Javascript notation).

                  +

                  The read method

                  +

                  We start by creating a Path representing the location of the data we're interested in:

                  +
                  scala> import jto.validation._
                  +import jto.validation._
                  +
                  +scala> val location: Path = Path \ "user" \ "friend"
                  +location: jto.validation.Path = /user/friend
                  +
                  +

                  Path has a read[I, O] method, where I represents the input we're trying to parse, and O the output type. For example, (Path \ "foo").read[JsValue, Int], will try to read a value located at path /foo in a JsValue as an Int.

                  +

                  But let's try something much easier for now:

                  +
                  scala> import jto.validation._
                  +import jto.validation._
                  +
                  +scala> import play.api.libs.json._
                  +import play.api.libs.json._
                  +
                  +scala> val location: Path = Path \ "user" \ "friend"
                  +location: jto.validation.Path = /user/friend
                  +
                  +scala> val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue]
                  +<console>:26: error: No implicit view available from jto.validation.Path => jto.validation.RuleLike[play.api.libs.json.JsValue,play.api.libs.json.JsValue].
                  +       val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue]
                  +                                                             ^
                  +
                  +

                  location.read[JsValue, JsValue] means we're trying to lookup at location in a JsValue, and we expect to find a JsValue there. +In fact, we're defining a Rule that is picking a subtree in a JsValue.

                  +

                  If you try to run that code, the compiler gives you the following error:

                  +
                  scala> val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue]
                  +<console>:26: error: No implicit view available from jto.validation.Path => jto.validation.RuleLike[play.api.libs.json.JsValue,play.api.libs.json.JsValue].
                  +       val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue]
                  +                                                             ^
                  +
                  +

                  The Scala compiler is complaining about not finding an implicit function of type Path => Rule[JsValue, JsValue]. Indeed, unlike the Json API, you have to provide a method to lookup into the data you expect to validate.

                  +

                  Fortunately, such method already exists. All you have to do is to import it:

                  +
                  scala> import jto.validation.playjson.Rules._
                  +import jto.validation.playjson.Rules._
                  +
                  +
                  +

                  By convention, all useful validation methods for a given type are to be found in an object called Rules. That object contains a bunch of implicits defining how to lookup in the data, and how to coerce some of the possible values of those data into Scala types.

                  +
                  +

                  With those implicits in scope, we can finally create our Rule:

                  +
                  val findFriend: Rule[JsValue, JsValue] = location.read[JsValue, JsValue]
                  +
                  +

                  Alright, so far we've defined a Rule looking for some data of type JsValue, located at /user/friend in an object of type JsValue.

                  +

                  Now we need to apply this Rule to our data:

                  +
                  scala> findFriend.validate(js)
                  +res0: jto.validation.VA[play.api.libs.json.JsValue] = Valid({"name":"tata","age":20,"email":"tata@coldmail.com"})
                  +
                  +

                  If we can't find anything, applying a Rule leads to a Invalid:

                  +
                  scala> (Path \ "foobar").read[JsValue, JsValue].validate(js)
                  +res1: jto.validation.VA[play.api.libs.json.JsValue] = Invalid(List((/foobar,List(ValidationError(List(error.required),WrappedArray())))))
                  +
                  +

                  Type coercion

                  +

                  We now are capable of extracting data at a given Path. Let's do it again on a different sub-tree:

                  +
                  val age = (Path \ "user" \ "age").read[JsValue, JsValue]
                  +
                  +

                  Let's apply this new Rule:

                  +
                  scala> age.validate(js)
                  +res2: jto.validation.VA[play.api.libs.json.JsValue] = Valid(25)
                  +
                  +

                  Again, if the json is invalid:

                  +
                  scala> age.validate(Json.obj())
                  +res3: jto.validation.VA[play.api.libs.json.JsValue] = Invalid(List((/user/age,List(ValidationError(List(error.required),WrappedArray())))))
                  +
                  +

                  The Invalid informs us that it could not find /user/age in that JsValue.

                  +

                  That example is nice, but we'd certainly prefer to extract age as an Int rather than a JsValue. +All we have to do is to change the output type in our Rule definition:

                  +
                  val age = (Path \ "user" \ "age").read[JsValue, Int]
                  +
                  +

                  And apply it:

                  +
                  scala> age.validate(js)
                  +res4: jto.validation.VA[Int] = Valid(25)
                  +
                  +

                  If we try to parse something that is not an Int, we get a Invalid with the appropriate Path and error:

                  +
                  scala> (Path \ "user" \ "name").read[JsValue, Int].validate(js)
                  +res5: jto.validation.VA[Int] = Invalid(List((/user/name,List(ValidationError(List(error.number),WrappedArray(Int))))))
                  +
                  +

                  So scala automagically figures out how to transform a JsValue into an Int. How does this happen?

                  +

                  It's fairly simple. The definition of read looks like this:

                  +
                  {
                  +  def read[I, O](implicit r: Path => Rule[I, O]): Rule[I, O] = ???
                  +}
                  +
                  +

                  So when use (Path \ "user" \ "age").read[JsValue, Int], the compiler looks for an implicit Path => Rule[JsValue, Int], which happens to exist in play.api.data.mapping.json.Rules.

                  +

                  Validated

                  +

                  So far we've managed to lookup for a JsValue and transform that JsValue into an Int. Problem is: not every Int is a valid age. An age should always be a positive Int.

                  +
                  val js = Json.parse("""{
                  +  "user": {
                  +    "age" : -33
                  +  }
                  +}""")
                  +
                  +val age = (Path \ "user" \ "age").read[JsValue, Int]
                  +
                  +

                  Our current implementation of age is rather unsatisfying...

                  +
                  scala> age.validate(js)
                  +res8: jto.validation.VA[Int] = Valid(-33)
                  +
                  +

                  We can fix that very simply using from, and a built-in Rule:

                  +
                  val positiveAge = (Path \ "user" \ "age").from[JsValue](min(0))
                  +
                  +

                  Let's try that again:

                  +
                  scala> positiveAge.validate(js)
                  +res9: jto.validation.VA[Int] = Invalid(List((/user/age,List(ValidationError(List(error.min),WrappedArray(0))))))
                  +
                  +

                  That's better, but still not perfect: 8765 is considered valid:

                  +
                  scala> val js2 = Json.parse("""{ "user": { "age" : 8765 } }""")
                  +js2: play.api.libs.json.JsValue = {"user":{"age":8765}}
                  +
                  +scala> positiveAge.validate(js2)
                  +res10: jto.validation.VA[Int] = Valid(8765)
                  +
                  +

                  Let's fix our age Rule:

                  +
                  val properAge = (Path \ "user" \ "age").from[JsValue](min(0) |+| max(130))
                  +
                  +

                  and test it:

                  +
                  val jsBig = Json.parse("""{ "user": { "age" : 8765 } }""")
                  +properAge.validate(jsBig)
                  +
                  +

                  Full example

                  +
                  import jto.validation._
                  +import jto.validation.playjson.Rules._
                  +import play.api.libs.json._
                  +
                  +val js = Json.parse("""{
                  +  "user": {
                  +    "name" : "toto",
                  +    "age" : 25,
                  +    "email" : "toto@jmail.com",
                  +    "isAlive" : true,
                  +    "friend" : {
                  +      "name" : "tata",
                  +      "age" : 20,
                  +      "email" : "tata@coldmail.com"
                  +    }
                  +  }
                  +}""")
                  +
                  +val age = (Path \ "user" \ "age").from[JsValue](min(0) |+| max(130))
                  +
                  +
                  scala> age.validate(js)
                  +res14: jto.validation.VA[Int] = Valid(25)
                  +
                  +

                  Combining Rules

                  +

                  So far we've validated only fragments of our json object. +Now we'd like to validate the entire object, and turn it into an instance of the User class defined below:

                  +
                  scala> case class User(
                  +     |   name: String,
                  +     |   age: Int,
                  +     |   email: Option[String],
                  +     |   isAlive: Boolean)
                  +defined class User
                  +
                  +

                  We need to create a Rule[JsValue, User]. Creating this Rule is simply a matter of combining together the rules parsing each field of the json.

                  +
                  import jto.validation._
                  +import play.api.libs.json._
                  +
                  +val userRule: Rule[JsValue, User] = From[JsValue] { __ =>
                  +  import jto.validation.playjson.Rules._
                  +  ((__ \ "name").read[String] ~
                  +   (__ \ "age").read[Int] ~
                  +   (__ \ "email").read[Option[String]] ~
                  +   (__ \ "isAlive").read[Boolean])(User.apply)
                  +}
                  +
                  +
                  +

                  Important: Note that we're importing Rules._ inside the From[I]{...} block. +It is recommended to always follow this pattern, as it nicely scopes the implicits, avoiding conflicts and accidental shadowing.

                  +
                  +

                  From[JsValue] defines the I type of the rules we're combining. We could have written:

                  +
                  (Path \ "name").read[JsValue, String] ~
                  +(Path \ "age").read[JsValue, Int] ~
                  +//...
                  +
                  +

                  but repeating JsValue all over the place is just not very DRY.

                  + + +
                  + +
                  +
                  +
                  + +

                  results matching ""

                  +
                    + +
                    +
                    + +

                    No results matching ""

                    + +
                    +
                    +
                    + +
                    +
                    + +
                    + + + + + + + + + + + + + + +
                    + + +
                    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationWrite.html b/docs/book/ScalaValidationWrite.html new file mode 100644 index 00000000..ba5eede5 --- /dev/null +++ b/docs/book/ScalaValidationWrite.html @@ -0,0 +1,477 @@ + + + + + Serializing data with Write · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                    +
                    + + + + + + + + +
                    + +
                    + +
                    + + + + + + + + +
                    +
                    + +
                    +
                    + +
                    + +

                    Serializing data

                    +

                    Introduction

                    +

                    To serialize data, the validation API provides the Write type. A Write[I, O] defines a way to transform data, from type I to type O. It's basically a function I => O, where I is the type of the input to serialize, and O is the expected output type.

                    +

                    A simple example

                    +

                    Let's say you want to serialize a Float to String. +All you need to do is to define a Write from Float to String:

                    +
                    import jto.validation._
                    +def floatToString: Write[Float, String] = ???
                    +
                    +

                    For now we'll not implement floatToString, actually, the validation API comes with a number of built-in Writes, including Writes.floatW[T].

                    +

                    All you have to do is import the default Writes.

                    +
                    object Writes extends NumericTypes2StringWrites
                    +Writes.floatW
                    +
                    +

                    Let's now test it against different Float values:

                    +
                    scala> Writes.floatW.writes(12.8F)
                    +res1: String = 12.8
                    +
                    +scala> Writes.floatW.writes(12F)
                    +res2: String = 12.0
                    +
                    +

                    Defining your own Write

                    +

                    Creating a new Write is almost as simple as creating a new function. +This example creates a new Write serializing a Float with a custom format.

                    +
                    val currency = Write[Double, String]{ money =>
                    +  import java.text.NumberFormat
                    +  import java.util.Locale
                    +  val f = NumberFormat.getCurrencyInstance(Locale.FRANCE)
                    +  f.format(money)
                    +}
                    +
                    +

                    Testing it:

                    +
                    scala> currency.writes(9.99)
                    +res3: String = 9,99 €
                    +
                    +

                    Composing Writes

                    +

                    Writes composition is very important in this API. Write composition means that given two writes a: Write[I, J] and b: Write[J, O], we can create a new write c: Write[I, O].

                    +

                    Example

                    +

                    Let's see we're working on an e-commerce website. We have defined a Product class. +Each product has a name and a price:

                    +
                    case class Product(name: String, price: Double)
                    +
                    +

                    Now we'd like to create a Write[Product, String] that serializes a product to a String of it price: Product("demo", 123) becomes 123,00 €

                    +

                    We have already defined currency: Write[Double, String], so we'd like to reuse that. +First, we'll create a Write[Product, Double] extracting the price of the product:

                    +
                    val productPrice: Write[Product, Double] = Write[Product, Double](_.price)
                    +
                    +

                    Now we just have to compose it with currency:

                    +
                    val productAsPrice: Write[Product,String] = productPrice andThen currency
                    +
                    +

                    Let's test our new Write:

                    +
                    scala> productAsPrice.writes(Product("Awesome product", 9.99))
                    +res4: String = 9,99 €
                    +
                    + + +
                    + +
                    +
                    +
                    + +

                    results matching ""

                    +
                      + +
                      +
                      + +

                      No results matching ""

                      + +
                      +
                      +
                      + +
                      +
                      + +
                      + + + + + + + + + + + + + + +
                      + + +
                      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/ScalaValidationWriteCombinators.html b/docs/book/ScalaValidationWriteCombinators.html new file mode 100644 index 00000000..0a2cdddf --- /dev/null +++ b/docs/book/ScalaValidationWriteCombinators.html @@ -0,0 +1,544 @@ + + + + + Combining Writes · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                      +
                      + + + + + + + + +
                      + +
                      + +
                      + + + + + + + + +
                      +
                      + +
                      +
                      + +
                      + +

                      Combining Writes

                      +

                      Introduction

                      +

                      We've already explained what a Write is in the previous chapter. Those examples were only covering simple writes. Most of the time, writes are used to transform complex hierarchical objects.

                      +

                      In the validation API, we create complex object writes by combining simple writes. This chapter details the creation of those complex writes.

                      +
                      +

                      All the examples below are transforming classes to Json objects. The API is not dedicated only to Json, it can be used on any type. Please refer to Serializing Json, Serializing Forms, and Supporting new types for more information.

                      +
                      +

                      Path

                      +

                      Serializing data using Path

                      +

                      The write method

                      +

                      We start by creating a Path representing the location at which we'd like to serialize our data:

                      +
                      import jto.validation._
                      +val location: Path = Path \ "user" \ "friend"
                      +
                      +

                      Path has a write[I, O] method, where I represents the input we’re trying to serialize, and O is the output type. For example, (Path \ "foo").write[Int, JsObject], means we want to try to serialize a value of type Int into a JsObject at /foo.

                      +

                      But let's try something much easier for now:

                      +
                      scala> import jto.validation._
                      +import jto.validation._
                      +
                      +scala> import play.api.libs.json._
                      +import play.api.libs.json._
                      +
                      +scala> val location: Path = Path \ "user" \ "friend"
                      +location: jto.validation.Path = /user/friend
                      +
                      +scala> val serializeFriend: Write[JsValue, JsObject] = location.write[JsValue, JsObject]
                      +<console>:22: error: No implicit view available from jto.validation.Path => jto.validation.WriteLike[play.api.libs.json.JsValue,play.api.libs.json.JsObject].
                      +       val serializeFriend: Write[JsValue, JsObject] = location.write[JsValue, JsObject]
                      +                                                                     ^
                      +
                      +

                      location.write[JsValue, JsObject] means the we're trying to serialize a JsValue to location in a JsObject. Effectively, we're just defining a Write that is putting a JsValue into a JsObject at the given location.

                      +

                      If you try to run that code, the compiler gives you the following error:

                      +
                      scala> val serializeFriend: Write[JsValue, JsObject] = location.write[JsValue, JsObject]
                      +<console>:22: error: No implicit view available from jto.validation.Path => jto.validation.WriteLike[play.api.libs.json.JsValue,play.api.libs.json.JsObject].
                      +       val serializeFriend: Write[JsValue, JsObject] = location.write[JsValue, JsObject]
                      +                                                                     ^
                      +
                      +

                      The Scala compiler is complaining about not finding an implicit function of type Path => Write[JsValue, JsObject]. Indeed, unlike the Json API, you have to provide a method to transform the input type into the output type.

                      +

                      Fortunately, such method already exists. All you have to do is import it:

                      +
                      import jto.validation.playjson.Writes._
                      +
                      +
                      +

                      By convention, all useful serialization methods for a given type are to be found in an object called Writes. That object contains a bunch of implicits defining how to serialize primitives Scala types into the expected output types.

                      +
                      +

                      With those implicits in scope, we can finally create our Write:

                      +
                      val serializeFriend: Write[JsValue, JsObject] = location.write[JsValue, JsObject]
                      +
                      +

                      Alright, so far we've defined a Write looking for some data of type JsValue, located at /user/friend in a JsObject.

                      +

                      Now we need to apply this Write on our data:

                      +
                      scala> serializeFriend.writes(JsString("Julien"))
                      +res0: play.api.libs.json.JsObject = {"user":{"friend":"Julien"}}
                      +
                      +

                      Type coercion

                      +

                      We now are capable of serializing data to a given Path. Let's do it again on a different sub-tree:

                      +
                      val agejs: Write[JsValue, JsObject] = (Path \ "user" \ "age").write[JsValue, JsObject]
                      +
                      +

                      And if we apply this new Write:

                      +
                      scala> agejs.writes(JsNumber(28))
                      +res1: play.api.libs.json.JsObject = {"user":{"age":28}}
                      +
                      +

                      That example is nice, but chances are age in not a JsNumber, but an Int. +All we have to do is to change the input type in our Write definition:

                      +
                      val age: Write[Int, JsObject] = (Path \ "user" \ "age").write[Int, JsObject]
                      +
                      +

                      And apply it:

                      +
                      scala> age.writes(28)
                      +res2: play.api.libs.json.JsObject = {"user":{"age":28}}
                      +
                      +

                      So scala automagically figures out how to transform a Int into a JsObject. How does this happen?

                      +

                      It's fairly simple. The definition of write looks like this:

                      +
                      def write[I, O](implicit w: Path => Write[I, O]): Write[I, O] = ???
                      +
                      +

                      So when you use (Path \ "user" \ "age").write[Int, JsObject], the compiler looks for an implicit Path => Write[Int, JsObject], which happens to exist in play.api.data.mapping.json.Writes.

                      +

                      Full example

                      +
                      import jto.validation._
                      +import jto.validation.playjson.Writes._
                      +import play.api.libs.json._
                      +
                      +val age: Write[Int, JsObject] = (Path \ "user" \ "age").write[Int, JsObject]
                      +
                      +
                      scala> age.writes(28)
                      +res4: play.api.libs.json.JsObject = {"user":{"age":28}}
                      +
                      +

                      Combining Writes

                      +

                      So far we've serialized only primitives types. +Now we'd like to serialize an entire User object defined below, and transform it into a JsObject:

                      +
                      case class User(
                      +  name: String,
                      +  age: Int,
                      +  email: Option[String],
                      +  isAlive: Boolean
                      +)
                      +
                      +

                      We need to create a Write[User, JsValue]. Creating this Write is simply a matter of combining together the writes serializing each field of the class.

                      +
                      import jto.validation._
                      +import jto.validation.playjson.Writes._
                      +import play.api.libs.json._
                      +import scala.Function.unlift
                      +
                      +val userWrite: Write[User, JsObject] = To[JsObject] { __ =>
                      +  import jto.validation.playjson.Writes._
                      +  ((__ \ "name").write[String] ~
                      +   (__ \ "age").write[Int] ~
                      +   (__ \ "email").write[Option[String]] ~
                      +   (__ \ "isAlive").write[Boolean])(unlift(User.unapply))
                      +}
                      +
                      +
                      +

                      Important: Note that we're importing Writes._ inside the To[I]{...} block. +It is recommended to always follow this pattern, as it nicely scopes the implicits, avoiding conflicts and accidental shadowing.

                      +
                      +

                      To[JsObject] defines the O type of the writes we're combining. We could have written:

                      +
                      (Path \ "name").write[String, JsObject] ~
                      +(Path \ "age").write[Int, JsObject] ~
                      +//...
                      +
                      +

                      but repeating JsObject all over the place is just not very DRY.

                      +

                      Let's test it now:

                      +
                      scala> userWrite.writes(User("Julien", 28, None, true))
                      +res6: play.api.libs.json.JsObject = {"name":"Julien","age":28,"isAlive":true}
                      +
                      + + +
                      + +
                      +
                      +
                      + +

                      results matching ""

                      +
                        + +
                        +
                        + +

                        No results matching ""

                        + +
                        +
                        +
                        + +
                        +
                        + +
                        + + + + + + + + + + + + + + +
                        + + +
                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/V2MigrationGuide.html b/docs/book/V2MigrationGuide.html new file mode 100644 index 00000000..059a7707 --- /dev/null +++ b/docs/book/V2MigrationGuide.html @@ -0,0 +1,443 @@ + + + + + v2.0 Migration guide · GitBook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        +
                        + + + + + + + + +
                        + +
                        + +
                        + + + + + + + + +
                        +
                        + +
                        +
                        + +
                        + +

                        v2.0 Migration guide

                        +

                        Version 2.x breaks back compatibility with the 1.x version. The migration has been tested on production code making heavy use of validation for json (based on play-json) and xml. Even for big projects, migrating to 2.x should not take more than 30 min.

                        +

                        The best method is just to update validation in your dependencies, and let the compiler figure out what's broken. The following changes list should cover everything needed.

                        +

                        Build file.

                        +

                        The project name for play-json based validation has changed.

                        +
                        "io.github.jto" %% "validation-json" % validationVersion
                        +
                        +

                        becomes

                        +
                        "io.github.jto" %% "validation-playjson" % validationVersion
                        +
                        +

                        Package name

                        +
                          +
                        • Since the library does not depend on Play anymore and is not planned to be integrated into Play, the package names have changed. Basically play.api.mapping now becomes jto.validation. A simple search and replace in your project should work.
                        • +
                        • The validation api support several json representations. Therefore, the package name for play json changes. play.api.mapping.json becomes play.api.mapping.playjson
                        • +
                        +

                        Rule renaming

                        +

                        The following Rule and Write were renamed to better match the naming convention in all subprojects.

                        +
                          +
                        • Rules.jodaDate becomes Rules.jodaDateR
                        • +
                        • Writes.jodaDate becomes Writes.jodaDateW
                        • +
                        +

                        If you encounter implicit resolution problem, you probably have a name clash. Make sure none of your Rule / Write uses those names.

                        +

                        unlift

                        +

                        Since validation does not uses play-functional anymore, unlift should be imported directly as scala.Function.unlift instead of play.api.libs.functional.unlift.

                        +

                        ValidationError

                        +

                        Since we removed all the dependencies on Play, play.api.mapping.ValidationError is re-defined in validation. If you're using this class, make sure to replace it by jto.validation.ValidationError.

                        + + +
                        + +
                        +
                        +
                        + +

                        results matching ""

                        +
                          + +
                          +
                          + +

                          No results matching ""

                          + +
                          +
                          +
                          + +
                          +
                          + +
                          + + + + + + + + + + +
                          + + +
                          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/gitbook/fonts/fontawesome/FontAwesome.otf b/docs/book/gitbook/fonts/fontawesome/FontAwesome.otf new file mode 100644 index 00000000..3ed7f8b4 Binary files /dev/null and b/docs/book/gitbook/fonts/fontawesome/FontAwesome.otf differ diff --git a/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.eot b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.eot new file mode 100644 index 00000000..9b6afaed Binary files /dev/null and b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.eot differ diff --git a/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.svg b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.svg new file mode 100644 index 00000000..d05688e9 --- /dev/null +++ b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.svg @@ -0,0 +1,655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf new file mode 100644 index 00000000..26dea795 Binary files /dev/null and b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf differ diff --git a/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff new file mode 100644 index 00000000..dc35ce3c Binary files /dev/null and b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff differ diff --git a/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 new file mode 100644 index 00000000..500e5172 Binary files /dev/null and b/docs/book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 differ diff --git a/docs/book/gitbook/gitbook-plugin-fontsettings/buttons.js b/docs/book/gitbook/gitbook-plugin-fontsettings/buttons.js new file mode 100644 index 00000000..8e952f67 --- /dev/null +++ b/docs/book/gitbook/gitbook-plugin-fontsettings/buttons.js @@ -0,0 +1,145 @@ +require(['gitbook', 'jquery'], function(gitbook, $) { + var fontState; + + var THEMES = { + 'white': 0, + 'sepia': 1, + 'night': 2 + }; + + var FAMILY = { + 'serif': 0, + 'sans': 1 + }; + + // Save current font settings + function saveFontSettings() { + gitbook.storage.set('fontState', fontState); + update(); + } + + // Increase font size + function enlargeFontSize(e) { + e.preventDefault(); + if (fontState.size >= 4) return; + + fontState.size++; + saveFontSettings(); + } + + // Decrease font size + function reduceFontSize(e) { + e.preventDefault(); + if (fontState.size <= 0) return; + + fontState.size--; + saveFontSettings(); + } + + // Change font family + function changeFontFamily(index, e) { + e.preventDefault(); + + fontState.family = index; + saveFontSettings(); + } + + // Change type of color + function changeColorTheme(index, e) { + e.preventDefault(); + + var $book = $('.book'); + + if (fontState.theme !== 0) + $book.removeClass('color-theme-'+fontState.theme); + + fontState.theme = index; + if (fontState.theme !== 0) + $book.addClass('color-theme-'+fontState.theme); + + saveFontSettings(); + } + + function update() { + var $book = gitbook.state.$book; + + $('.font-settings .font-family-list li').removeClass('active'); + $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active'); + + $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); + $book.addClass('font-size-'+fontState.size); + $book.addClass('font-family-'+fontState.family); + + if(fontState.theme !== 0) { + $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); + $book.addClass('color-theme-'+fontState.theme); + } + } + + function init(config) { + // Instantiate font state object + fontState = gitbook.storage.get('fontState', { + size: config.size || 2, + family: FAMILY[config.family || 'sans'], + theme: THEMES[config.theme || 'white'] + }); + + update(); + } + + + gitbook.events.bind('start', function(e, config) { + var opts = config.fontsettings; + + // Create buttons in toolbar + gitbook.toolbar.createButton({ + icon: 'fa fa-font', + label: 'Font Settings', + className: 'font-settings', + dropdown: [ + [ + { + text: 'A', + className: 'font-reduce', + onClick: reduceFontSize + }, + { + text: 'A', + className: 'font-enlarge', + onClick: enlargeFontSize + } + ], + [ + { + text: 'Serif', + onClick: function(e) { return changeFontFamily(0, e); } + }, + { + text: 'Sans', + onClick: function(e) { return changeFontFamily(1, e); } + } + ], + [ + { + text: 'White', + onClick: function(e) { return changeColorTheme(0, e); } + }, + { + text: 'Sepia', + onClick: function(e) { return changeColorTheme(1, e); } + }, + { + text: 'Night', + onClick: function(e) { return changeColorTheme(2, e); } + } + ] + ] + }); + + + // Init current settings + init(opts); + }); +}); + + diff --git a/docs/book/gitbook/gitbook-plugin-fontsettings/website.css b/docs/book/gitbook/gitbook-plugin-fontsettings/website.css new file mode 100644 index 00000000..26591fe8 --- /dev/null +++ b/docs/book/gitbook/gitbook-plugin-fontsettings/website.css @@ -0,0 +1,291 @@ +/* + * Theme 1 + */ +.color-theme-1 .dropdown-menu { + background-color: #111111; + border-color: #7e888b; +} +.color-theme-1 .dropdown-menu .dropdown-caret .caret-inner { + border-bottom: 9px solid #111111; +} +.color-theme-1 .dropdown-menu .buttons { + border-color: #7e888b; +} +.color-theme-1 .dropdown-menu .button { + color: #afa790; +} +.color-theme-1 .dropdown-menu .button:hover { + color: #73553c; +} +/* + * Theme 2 + */ +.color-theme-2 .dropdown-menu { + background-color: #2d3143; + border-color: #272a3a; +} +.color-theme-2 .dropdown-menu .dropdown-caret .caret-inner { + border-bottom: 9px solid #2d3143; +} +.color-theme-2 .dropdown-menu .buttons { + border-color: #272a3a; +} +.color-theme-2 .dropdown-menu .button { + color: #62677f; +} +.color-theme-2 .dropdown-menu .button:hover { + color: #f4f4f5; +} +.book .book-header .font-settings .font-enlarge { + line-height: 30px; + font-size: 1.4em; +} +.book .book-header .font-settings .font-reduce { + line-height: 30px; + font-size: 1em; +} +.book.color-theme-1 .book-body { + color: #704214; + background: #f3eacb; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section { + background: #f3eacb; +} +.book.color-theme-2 .book-body { + color: #bdcadb; + background: #1c1f2b; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section { + background: #1c1f2b; +} +.book.font-size-0 .book-body .page-inner section { + font-size: 1.2rem; +} +.book.font-size-1 .book-body .page-inner section { + font-size: 1.4rem; +} +.book.font-size-2 .book-body .page-inner section { + font-size: 1.6rem; +} +.book.font-size-3 .book-body .page-inner section { + font-size: 2.2rem; +} +.book.font-size-4 .book-body .page-inner section { + font-size: 4rem; +} +.book.font-family-0 { + font-family: Georgia, serif; +} +.book.font-family-1 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal { + color: #704214; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a { + color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { + color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 { + border-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { + color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr { + background-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote { + border-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code { + background: #fdf6e3; + color: #657b83; + border-color: #f8df9c; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight { + background-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td { + border-color: #f5d06c; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr { + color: inherit; + background-color: #fdf6e3; + border-color: #444444; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { + background-color: #fbeecb; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal { + color: #bdcadb; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a { + color: #3eb1d0; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { + color: #fffffa; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 { + border-color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { + color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr { + background-color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote { + border-color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code { + color: #9dbed8; + background: #2d3143; + border-color: #2d3143; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight { + background-color: #282a39; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td { + border-color: #3b3f54; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr { + color: #b6c2d2; + background-color: #2d3143; + border-color: #3b3f54; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { + background-color: #35394b; +} +.book.color-theme-1 .book-header { + color: #afa790; + background: transparent; +} +.book.color-theme-1 .book-header .btn { + color: #afa790; +} +.book.color-theme-1 .book-header .btn:hover { + color: #73553c; + background: none; +} +.book.color-theme-1 .book-header h1 { + color: #704214; +} +.book.color-theme-2 .book-header { + color: #7e888b; + background: transparent; +} +.book.color-theme-2 .book-header .btn { + color: #3b3f54; +} +.book.color-theme-2 .book-header .btn:hover { + color: #fffff5; + background: none; +} +.book.color-theme-2 .book-header h1 { + color: #bdcadb; +} +.book.color-theme-1 .book-body .navigation { + color: #afa790; +} +.book.color-theme-1 .book-body .navigation:hover { + color: #73553c; +} +.book.color-theme-2 .book-body .navigation { + color: #383f52; +} +.book.color-theme-2 .book-body .navigation:hover { + color: #fffff5; +} +/* + * Theme 1 + */ +.book.color-theme-1 .book-summary { + color: #afa790; + background: #111111; + border-right: 1px solid rgba(0, 0, 0, 0.07); +} +.book.color-theme-1 .book-summary .book-search { + background: transparent; +} +.book.color-theme-1 .book-summary .book-search input, +.book.color-theme-1 .book-summary .book-search input:focus { + border: 1px solid transparent; +} +.book.color-theme-1 .book-summary ul.summary li.divider { + background: #7e888b; + box-shadow: none; +} +.book.color-theme-1 .book-summary ul.summary li i.fa-check { + color: #33cc33; +} +.book.color-theme-1 .book-summary ul.summary li.done > a { + color: #877f6a; +} +.book.color-theme-1 .book-summary ul.summary li a, +.book.color-theme-1 .book-summary ul.summary li span { + color: #877f6a; + background: transparent; + font-weight: normal; +} +.book.color-theme-1 .book-summary ul.summary li.active > a, +.book.color-theme-1 .book-summary ul.summary li a:hover { + color: #704214; + background: transparent; + font-weight: normal; +} +/* + * Theme 2 + */ +.book.color-theme-2 .book-summary { + color: #bcc1d2; + background: #2d3143; + border-right: none; +} +.book.color-theme-2 .book-summary .book-search { + background: transparent; +} +.book.color-theme-2 .book-summary .book-search input, +.book.color-theme-2 .book-summary .book-search input:focus { + border: 1px solid transparent; +} +.book.color-theme-2 .book-summary ul.summary li.divider { + background: #272a3a; + box-shadow: none; +} +.book.color-theme-2 .book-summary ul.summary li i.fa-check { + color: #33cc33; +} +.book.color-theme-2 .book-summary ul.summary li.done > a { + color: #62687f; +} +.book.color-theme-2 .book-summary ul.summary li a, +.book.color-theme-2 .book-summary ul.summary li span { + color: #c1c6d7; + background: transparent; + font-weight: 600; +} +.book.color-theme-2 .book-summary ul.summary li.active > a, +.book.color-theme-2 .book-summary ul.summary li a:hover { + color: #f4f4f5; + background: #252737; + font-weight: 600; +} diff --git a/docs/book/gitbook/gitbook-plugin-highlight/ebook.css b/docs/book/gitbook/gitbook-plugin-highlight/ebook.css new file mode 100644 index 00000000..cecaaab5 --- /dev/null +++ b/docs/book/gitbook/gitbook-plugin-highlight/ebook.css @@ -0,0 +1,135 @@ +pre, +code { + /* http://jmblog.github.io/color-themes-for-highlightjs */ + /* Tomorrow Comment */ + /* Tomorrow Red */ + /* Tomorrow Orange */ + /* Tomorrow Yellow */ + /* Tomorrow Green */ + /* Tomorrow Aqua */ + /* Tomorrow Blue */ + /* Tomorrow Purple */ +} +pre .hljs-comment, +code .hljs-comment, +pre .hljs-title, +code .hljs-title { + color: #8e908c; +} +pre .hljs-variable, +code .hljs-variable, +pre .hljs-attribute, +code .hljs-attribute, +pre .hljs-tag, +code .hljs-tag, +pre .hljs-regexp, +code .hljs-regexp, +pre .hljs-deletion, +code .hljs-deletion, +pre .ruby .hljs-constant, +code .ruby .hljs-constant, +pre .xml .hljs-tag .hljs-title, +code .xml .hljs-tag .hljs-title, +pre .xml .hljs-pi, +code .xml .hljs-pi, +pre .xml .hljs-doctype, +code .xml .hljs-doctype, +pre .html .hljs-doctype, +code .html .hljs-doctype, +pre .css .hljs-id, +code .css .hljs-id, +pre .css .hljs-class, +code .css .hljs-class, +pre .css .hljs-pseudo, +code .css .hljs-pseudo { + color: #c82829; +} +pre .hljs-number, +code .hljs-number, +pre .hljs-preprocessor, +code .hljs-preprocessor, +pre .hljs-pragma, +code .hljs-pragma, +pre .hljs-built_in, +code .hljs-built_in, +pre .hljs-literal, +code .hljs-literal, +pre .hljs-params, +code .hljs-params, +pre .hljs-constant, +code .hljs-constant { + color: #f5871f; +} +pre .ruby .hljs-class .hljs-title, +code .ruby .hljs-class .hljs-title, +pre .css .hljs-rules .hljs-attribute, +code .css .hljs-rules .hljs-attribute { + color: #eab700; +} +pre .hljs-string, +code .hljs-string, +pre .hljs-value, +code .hljs-value, +pre .hljs-inheritance, +code .hljs-inheritance, +pre .hljs-header, +code .hljs-header, +pre .hljs-addition, +code .hljs-addition, +pre .ruby .hljs-symbol, +code .ruby .hljs-symbol, +pre .xml .hljs-cdata, +code .xml .hljs-cdata { + color: #718c00; +} +pre .css .hljs-hexcolor, +code .css .hljs-hexcolor { + color: #3e999f; +} +pre .hljs-function, +code .hljs-function, +pre .python .hljs-decorator, +code .python .hljs-decorator, +pre .python .hljs-title, +code .python .hljs-title, +pre .ruby .hljs-function .hljs-title, +code .ruby .hljs-function .hljs-title, +pre .ruby .hljs-title .hljs-keyword, +code .ruby .hljs-title .hljs-keyword, +pre .perl .hljs-sub, +code .perl .hljs-sub, +pre .javascript .hljs-title, +code .javascript .hljs-title, +pre .coffeescript .hljs-title, +code .coffeescript .hljs-title { + color: #4271ae; +} +pre .hljs-keyword, +code .hljs-keyword, +pre .javascript .hljs-function, +code .javascript .hljs-function { + color: #8959a8; +} +pre .hljs, +code .hljs { + display: block; + background: white; + color: #4d4d4c; + padding: 0.5em; +} +pre .coffeescript .javascript, +code .coffeescript .javascript, +pre .javascript .xml, +code .javascript .xml, +pre .tex .hljs-formula, +code .tex .hljs-formula, +pre .xml .javascript, +code .xml .javascript, +pre .xml .vbscript, +code .xml .vbscript, +pre .xml .css, +code .xml .css, +pre .xml .hljs-cdata, +code .xml .hljs-cdata { + opacity: 0.5; +} diff --git a/docs/book/gitbook/gitbook-plugin-highlight/website.css b/docs/book/gitbook/gitbook-plugin-highlight/website.css new file mode 100644 index 00000000..6674448f --- /dev/null +++ b/docs/book/gitbook/gitbook-plugin-highlight/website.css @@ -0,0 +1,434 @@ +.book .book-body .page-wrapper .page-inner section.normal pre, +.book .book-body .page-wrapper .page-inner section.normal code { + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + /* Tomorrow Comment */ + /* Tomorrow Red */ + /* Tomorrow Orange */ + /* Tomorrow Yellow */ + /* Tomorrow Green */ + /* Tomorrow Aqua */ + /* Tomorrow Blue */ + /* Tomorrow Purple */ +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-comment, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-comment, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-title { + color: #8e908c; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-variable, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-variable, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-attribute, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-attribute, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-tag, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-tag, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-regexp, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-regexp, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-deletion, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-deletion, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-constant, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-constant, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-tag .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-tag .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-pi, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-pi, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal pre .html .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal code .html .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-id, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-id, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-class, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-class, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-pseudo, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-pseudo { + color: #c82829; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-number, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-number, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-pragma, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-pragma, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-built_in, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-built_in, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-literal, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-literal, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-params, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-params, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-constant, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-constant { + color: #f5871f; +} +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-class .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-class .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-rules .hljs-attribute, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-rules .hljs-attribute { + color: #eab700; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-string, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-string, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-value, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-value, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-inheritance, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-inheritance, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-header, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-header, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-addition, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-addition, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-symbol, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-symbol, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + color: #718c00; +} +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-hexcolor, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-hexcolor { + color: #3e999f; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-function, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-function, +.book .book-body .page-wrapper .page-inner section.normal pre .python .hljs-decorator, +.book .book-body .page-wrapper .page-inner section.normal code .python .hljs-decorator, +.book .book-body .page-wrapper .page-inner section.normal pre .python .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .python .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-function .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-function .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-title .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-title .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal pre .perl .hljs-sub, +.book .book-body .page-wrapper .page-inner section.normal code .perl .hljs-sub, +.book .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .coffeescript .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .coffeescript .hljs-title { + color: #4271ae; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-function, +.book .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-function { + color: #8959a8; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs, +.book .book-body .page-wrapper .page-inner section.normal code .hljs { + display: block; + background: white; + color: #4d4d4c; + padding: 0.5em; +} +.book .book-body .page-wrapper .page-inner section.normal pre .coffeescript .javascript, +.book .book-body .page-wrapper .page-inner section.normal code .coffeescript .javascript, +.book .book-body .page-wrapper .page-inner section.normal pre .javascript .xml, +.book .book-body .page-wrapper .page-inner section.normal code .javascript .xml, +.book .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .javascript, +.book .book-body .page-wrapper .page-inner section.normal code .xml .javascript, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .vbscript, +.book .book-body .page-wrapper .page-inner section.normal code .xml .vbscript, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .css, +.book .book-body .page-wrapper .page-inner section.normal code .xml .css, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + opacity: 0.5; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code { + /* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + /* Solarized Green */ + /* Solarized Cyan */ + /* Solarized Blue */ + /* Solarized Yellow */ + /* Solarized Orange */ + /* Solarized Red */ + /* Solarized Violet */ +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs { + display: block; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-template_comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-template_comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .diff .hljs-header, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .diff .hljs-header, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-doctype, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-doctype, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-pi, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-pi, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .lisp .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .lisp .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-javadoc, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-javadoc { + color: #93a1a1; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-winutils, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-winutils, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .method, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .method, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-addition, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-addition, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-tag, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .css .hljs-tag, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-request, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-request, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-status, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-status, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .nginx .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .nginx .hljs-title { + color: #859900; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-command, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-command, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-tag .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-tag .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-rules .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-rules .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-phpdoc, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-phpdoc, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-regexp, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-regexp, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-hexcolor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-hexcolor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-link_url, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-link_url { + color: #2aa198; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-localvars, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-localvars, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-chunk, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-chunk, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-decorator, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-decorator, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-built_in, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-built_in, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-identifier, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-identifier, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .vhdl .hljs-literal, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .vhdl .hljs-literal, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-id, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-id, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-function, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .css .hljs-function { + color: #268bd2; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-attribute, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-attribute, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-variable, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-variable, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .lisp .hljs-body, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .lisp .hljs-body, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .smalltalk .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .smalltalk .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-constant, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-constant, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-class .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-class .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-parent, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-parent, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .haskell .hljs-type, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .haskell .hljs-type, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-link_reference, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-link_reference { + color: #b58900; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-pragma, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-pragma, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-shebang, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-shebang, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-symbol, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-symbol, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-symbol .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-symbol .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .diff .hljs-change, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .diff .hljs-change, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-special, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-special, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-attr_selector, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-attr_selector, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-subst, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-subst, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-cdata, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-cdata, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .clojure .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .clojure .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-pseudo, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .css .hljs-pseudo, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-header, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-header { + color: #cb4b16; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-deletion, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-deletion, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-important, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-important { + color: #dc322f; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-link_label, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-link_label { + color: #6c71c4; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula { + background: #eee8d5; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code { + /* Tomorrow Night Bright Theme */ + /* Original theme - https://github.com/chriskempson/tomorrow-theme */ + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + /* Tomorrow Comment */ + /* Tomorrow Red */ + /* Tomorrow Orange */ + /* Tomorrow Yellow */ + /* Tomorrow Green */ + /* Tomorrow Aqua */ + /* Tomorrow Blue */ + /* Tomorrow Purple */ +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-comment, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-comment, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-title { + color: #969896; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-variable, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-variable, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-attribute, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-attribute, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-tag, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-tag, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-regexp, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-regexp, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-deletion, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-deletion, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-constant, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-constant, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-tag .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-tag .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-pi, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-pi, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .html .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .html .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-id, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-id, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-class, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-class, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-pseudo, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-pseudo { + color: #d54e53; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-number, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-number, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-pragma, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-pragma, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-built_in, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-built_in, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-literal, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-literal, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-params, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-params, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-constant, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-constant { + color: #e78c45; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-class .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-class .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-rules .hljs-attribute, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-rules .hljs-attribute { + color: #e7c547; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-string, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-string, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-value, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-value, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-inheritance, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-inheritance, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-header, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-header, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-addition, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-addition, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-symbol, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-symbol, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + color: #b9ca4a; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-hexcolor, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-hexcolor { + color: #70c0b1; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-function, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-function, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .python .hljs-decorator, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .python .hljs-decorator, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .python .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .python .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-function .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-function .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-title .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-title .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .perl .hljs-sub, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .perl .hljs-sub, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .coffeescript .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .coffeescript .hljs-title { + color: #7aa6da; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-function, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-function { + color: #c397d8; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs { + display: block; + background: black; + color: #eaeaea; + padding: 0.5em; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .coffeescript .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .coffeescript .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .javascript .xml, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .javascript .xml, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .vbscript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .vbscript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .css, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .css, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + opacity: 0.5; +} diff --git a/docs/book/gitbook/gitbook-plugin-lunr/lunr.min.js b/docs/book/gitbook/gitbook-plugin-lunr/lunr.min.js new file mode 100644 index 00000000..6aa6bc7d --- /dev/null +++ b/docs/book/gitbook/gitbook-plugin-lunr/lunr.min.js @@ -0,0 +1,7 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.12 + * Copyright (C) 2015 Oliver Nightingale + * MIT Licensed + * @license + */ +!function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.5.12",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(t){return arguments.length&&null!=t&&void 0!=t?Array.isArray(t)?t.map(function(t){return t.toLowerCase()}):t.toString().trim().toLowerCase().split(/[\s\-]+/):[]},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,o=0;n>o;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;no;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n element for each result + res.results.forEach(function(res) { + var $li = $('
                        • ', { + 'class': 'search-results-item' + }); + + var $title = $('

                          '); + + var $link = $('', { + 'href': gitbook.state.basePath + '/' + res.url, + 'text': res.title + }); + + var content = res.body.trim(); + if (content.length > MAX_DESCRIPTION_SIZE) { + content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; + } + var $content = $('

                          ').html(content); + + $link.appendTo($title); + $title.appendTo($li); + $content.appendTo($li); + $li.appendTo($searchList); + }); + } + + function launchSearch(q) { + // Add class for loading + $body.addClass('with-search'); + $body.addClass('search-loading'); + + // Launch search query + throttle(gitbook.search.query(q, 0, MAX_RESULTS) + .then(function(results) { + displayResults(results); + }) + .always(function() { + $body.removeClass('search-loading'); + }), 1000); + } + + function closeSearch() { + $body.removeClass('with-search'); + $bookSearchResults.removeClass('open'); + } + + function launchSearchFromQueryString() { + var q = getParameterByName('q'); + if (q && q.length > 0) { + // Update search input + $searchInput.val(q); + + // Launch search + launchSearch(q); + } + } + + function bindSearch() { + // Bind DOM + $searchInput = $('#book-search-input input'); + $bookSearchResults = $('#book-search-results'); + $searchList = $bookSearchResults.find('.search-results-list'); + $searchTitle = $bookSearchResults.find('.search-results-title'); + $searchResultsCount = $searchTitle.find('.search-results-count'); + $searchQuery = $searchTitle.find('.search-query'); + + // Launch query based on input content + function handleUpdate() { + var q = $searchInput.val(); + + if (q.length == 0) { + closeSearch(); + } + else { + launchSearch(q); + } + } + + // Detect true content change in search input + // Workaround for IE < 9 + var propertyChangeUnbound = false; + $searchInput.on('propertychange', function(e) { + if (e.originalEvent.propertyName == 'value') { + handleUpdate(); + } + }); + + // HTML5 (IE9 & others) + $searchInput.on('input', function(e) { + // Unbind propertychange event for IE9+ + if (!propertyChangeUnbound) { + $(this).unbind('propertychange'); + propertyChangeUnbound = true; + } + + handleUpdate(); + }); + + // Push to history on blur + $searchInput.on('blur', function(e) { + // Update history state + if (usePushState) { + var uri = updateQueryString('q', $(this).val()); + history.pushState({ path: uri }, null, uri); + } + }); + } + + gitbook.events.on('page.change', function() { + bindSearch(); + closeSearch(); + + // Launch search based on query parameter + if (gitbook.search.isInitialized()) { + launchSearchFromQueryString(); + } + }); + + gitbook.events.on('search.ready', function() { + bindSearch(); + + // Launch search from query param at start + launchSearchFromQueryString(); + }); + + function getParameterByName(name) { + var url = window.location.href; + name = name.replace(/[\[\]]/g, '\\$&'); + var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i'), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + } + + function updateQueryString(key, value) { + value = encodeURIComponent(value); + + var url = window.location.href; + var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), + hash; + + if (re.test(url)) { + if (typeof value !== 'undefined' && value !== null) + return url.replace(re, '$1' + key + '=' + value + '$2$3'); + else { + hash = url.split('#'); + url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); + if (typeof hash[1] !== 'undefined' && hash[1] !== null) + url += '#' + hash[1]; + return url; + } + } + else { + if (typeof value !== 'undefined' && value !== null) { + var separator = url.indexOf('?') !== -1 ? '&' : '?'; + hash = url.split('#'); + url = hash[0] + separator + key + '=' + value; + if (typeof hash[1] !== 'undefined' && hash[1] !== null) + url += '#' + hash[1]; + return url; + } + else + return url; + } + } +}); diff --git a/docs/book/gitbook/gitbook-plugin-sharing/buttons.js b/docs/book/gitbook/gitbook-plugin-sharing/buttons.js new file mode 100644 index 00000000..709a4e4c --- /dev/null +++ b/docs/book/gitbook/gitbook-plugin-sharing/buttons.js @@ -0,0 +1,90 @@ +require(['gitbook', 'jquery'], function(gitbook, $) { + var SITES = { + 'facebook': { + 'label': 'Facebook', + 'icon': 'fa fa-facebook', + 'onClick': function(e) { + e.preventDefault(); + window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); + } + }, + 'twitter': { + 'label': 'Twitter', + 'icon': 'fa fa-twitter', + 'onClick': function(e) { + e.preventDefault(); + window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); + } + }, + 'google': { + 'label': 'Google+', + 'icon': 'fa fa-google-plus', + 'onClick': function(e) { + e.preventDefault(); + window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); + } + }, + 'weibo': { + 'label': 'Weibo', + 'icon': 'fa fa-weibo', + 'onClick': function(e) { + e.preventDefault(); + window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); + } + }, + 'instapaper': { + 'label': 'Instapaper', + 'icon': 'fa fa-instapaper', + 'onClick': function(e) { + e.preventDefault(); + window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); + } + }, + 'vk': { + 'label': 'VK', + 'icon': 'fa fa-vk', + 'onClick': function(e) { + e.preventDefault(); + window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); + } + } + }; + + + + gitbook.events.bind('start', function(e, config) { + var opts = config.sharing; + + // Create dropdown menu + var menu = $.map(opts.all, function(id) { + var site = SITES[id]; + + return { + text: site.label, + onClick: site.onClick + }; + }); + + // Create main button with dropdown + if (menu.length > 0) { + gitbook.toolbar.createButton({ + icon: 'fa fa-share-alt', + label: 'Share', + position: 'right', + dropdown: [menu] + }); + } + + // Direct actions to share + $.each(SITES, function(sideId, site) { + if (!opts[sideId]) return; + + gitbook.toolbar.createButton({ + icon: site.icon, + label: site.text, + position: 'right', + onClick: site.onClick + }); + }); + }); +}); diff --git a/docs/book/gitbook/gitbook.js b/docs/book/gitbook/gitbook.js new file mode 100644 index 00000000..e22cf4e9 --- /dev/null +++ b/docs/book/gitbook/gitbook.js @@ -0,0 +1,4 @@ +!function e(t,n,r){function o(s,a){if(!n[s]){if(!t[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(i)return i(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var c=n[s]={exports:{}};t[s][0].call(c.exports,function(e){var n=t[s][1][e];return o(n?n:e)},c,c.exports,e,t,n,r)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;s0&&t-1 in e}function r(e,t,n){if(Z.isFunction(t))return Z.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return Z.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(ae.test(t))return Z.filter(t,e,n);t=Z.filter(t,e)}return Z.grep(e,function(e){return X.call(t,e)>=0!==n})}function o(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function i(e){var t=de[e]={};return Z.each(e.match(he)||[],function(e,n){t[n]=!0}),t}function s(){Q.removeEventListener("DOMContentLoaded",s,!1),e.removeEventListener("load",s,!1),Z.ready()}function a(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=Z.expando+a.uid++}function u(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(be,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:xe.test(n)?Z.parseJSON(n):n}catch(o){}ye.set(e,t,n)}else n=void 0;return n}function l(){return!0}function c(){return!1}function f(){try{return Q.activeElement}catch(e){}}function p(e,t){return Z.nodeName(e,"table")&&Z.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function h(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function d(e){var t=Re.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function g(e,t){for(var n=0,r=e.length;r>n;n++)ve.set(e[n],"globalEval",!t||ve.get(t[n],"globalEval"))}function m(e,t){var n,r,o,i,s,a,u,l;if(1===t.nodeType){if(ve.hasData(e)&&(i=ve.access(e),s=ve.set(t,i),l=i.events)){delete s.handle,s.events={};for(o in l)for(n=0,r=l[o].length;r>n;n++)Z.event.add(t,o,l[o][n])}ye.hasData(e)&&(a=ye.access(e),u=Z.extend({},a),ye.set(t,u))}}function v(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&Z.nodeName(e,t)?Z.merge([e],n):n}function y(e,t){var n=t.nodeName.toLowerCase();"input"===n&&je.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}function x(t,n){var r,o=Z(n.createElement(t)).appendTo(n.body),i=e.getDefaultComputedStyle&&(r=e.getDefaultComputedStyle(o[0]))?r.display:Z.css(o[0],"display");return o.detach(),i}function b(e){var t=Q,n=$e[e];return n||(n=x(e,t),"none"!==n&&n||(Me=(Me||Z("