Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Env visualization & sbt-microsites plugin #93

Merged
11 commits merged into from
Nov 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Changelog

## Version 0.4.1

Date: Unreleased

- Make `DataSource#name` mandatory to implement
- Add `fetch-debug` project with debugging facilities for Fetch

## Version 0.4.0

Date: 2016-11-14
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ Or, if using Scala.js (0.6.x):
```




## Remote data

Fetch is a library for making access to data both simple & efficient. Fetch is especially useful when querying data that
Expand Down
60 changes: 45 additions & 15 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import catext.Dependencies._
val dev = Seq(Dev("47 Degrees (twitter: @47deg)", "47 Degrees"))
val gh = GitHubSettings("com.fortysevendeg", "fetch", "47 Degrees", apache)

addCommandAlias("makeDocs", ";readme/tut;docs/tut;docs/makeSite")
addCommandAlias("makeDocs", ";docs/makeMicrosite")

pgpPassphrase := Some(sys.env.getOrElse("PGP_PASSPHRASE", "").toCharArray)
pgpPublicRing := file(s"${sys.env.getOrElse("PGP_FOLDER", ".")}/pubring.gpg")
Expand Down Expand Up @@ -75,30 +75,48 @@ lazy val root = project
.settings(allSettings)
.settings(noPublishSettings)

lazy val docsSettings = ghpages.settings ++ buildSettings ++ tutSettings ++ Seq(
git.remoteRepo := "git@github.com:47deg/fetch.git",
tutSourceDirectory := sourceDirectory.value / "tut",
tutTargetDirectory := sourceDirectory.value / "jekyll",
tutScalacOptions ~= (_.filterNot(
Set("-Ywarn-unused-import", "-Ywarn-dead-code"))),
tutScalacOptions += "-Xdivergence211",
aggregate in doc := true
)
lazy val micrositeSettings = Seq(
micrositeName := "Fetch",
micrositeDescription := "Simple & Efficient data access for Scala and Scala.js",
micrositeBaseUrl := "fetch",
micrositeDocumentationUrl := "/fetch/docs/",
micrositeGithubOwner := "47deg",
micrositeGithubRepo := "fetch",
micrositeHighlightTheme := "tomorrow",
micrositePalette := Map(
"brand-primary" -> "#FF518C",
"brand-secondary" -> "#2F2859",
"brand-tertiary" -> "#28224C",
"gray-dark" -> "#48474C",
"gray" -> "#8D8C92",
"gray-light" -> "#E3E2E3",
"gray-lighter" -> "#F4F3F9",
"white-color" -> "#FFFFFF"),
includeFilter in makeSite := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.js" | "*.swf" | "*.md"
)

lazy val docsSettings = buildSettings ++ micrositeSettings ++ Seq(
tutScalacOptions ~= (_.filterNot(Set("-Ywarn-unused-import", "-Ywarn-dead-code"))),
tutScalacOptions ++= (scalaBinaryVersion.value match {
case "2.10" => Seq("-Xdivergence211")
case _ => Nil
}),
aggregate in doc := true
)

lazy val docs = (project in file("docs"))
.dependsOn(fetchJVM, fetchMonixJVM, debugJVM)
.settings(
moduleName := "fetch-docs"
)
.dependsOn(fetchJVM, fetchMonixJVM)
.enablePlugins(JekyllPlugin)
.settings(docsSettings: _*)
.settings(noPublishSettings)
.enablePlugins(MicrositesPlugin)

lazy val readmeSettings = buildSettings ++ tutSettings ++ Seq(
tutSourceDirectory := baseDirectory.value,
tutTargetDirectory := baseDirectory.value.getParentFile,
tutScalacOptions ~= (_.filterNot(
Set("-Ywarn-unused-import", "-Ywarn-dead-code"))),
tutScalacOptions ~= (_.filterNot(Set("-Ywarn-unused-import", "-Ywarn-dead-code"))),
tutScalacOptions += "-Xdivergence211",
tutNameFilter := """README.md""".r
)
Expand Down Expand Up @@ -128,4 +146,16 @@ lazy val monix = crossProject
.enablePlugins(AutomateHeaderPlugin)

lazy val fetchMonixJVM = monix.jvm
lazy val fetchMonixJS = monix.js
lazy val fetchMonixJS = monix.js

lazy val debug = (crossProject in file("debug"))
.settings(
moduleName := "fetch-debug"
)
.dependsOn(fetch)
.settings(allSettings: _*)
.jsSettings(sharedJsSettings: _*)
.enablePlugins(AutomateHeaderPlugin)

lazy val debugJVM = debug.jvm
lazy val debugJS = debug.js
110 changes: 110 additions & 0 deletions debug/shared/src/main/scala/debug.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2016 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fetch

object debug {
import fetch.document.Document

def string(doc: Document): String = {
val writer = new java.io.StringWriter
doc.format(1, writer)
writer.toString
}

def pile(docs: Seq[Document]): Document =
docs.foldLeft(Document.empty: Document)(_ :/: _)

def showDuration(secs: Double): Document =
Document.text(f" took $secs%2f seconds")

def showEnv(env: Env): Document = env.rounds match {
case Nil => Document.empty
case _ => {
val result = for {
last <- env.rounds.lastOption
} yield last.response
val resultDoc =
result.fold(Document.empty: Document)((r) => Document.text(s"Fetching `${r}`"))

val duration = for {
first <- env.rounds.headOption
last <- env.rounds.lastOption
} yield last.end - first.start
val durationDoc =
duration.fold(Document.empty: Document)((d) =>
Document.text("Fetch execution") :: showDuration(d / 1e9))

durationDoc :/: Document.nest(2, pile(env.rounds.map(showRound)))
}
}

def showRound(r: Round): Document = r match {
case Round(cache, q @ FetchOne(id, ds), result, start, end) =>
showQuery(q) :: showDuration(r.duration / 1e6)
case Round(cache, q @ FetchMany(ids, ds), result, start, end) =>
showQuery(q) :: showDuration(r.duration / 1e6)
case Round(cache, Concurrent(queries), result, start, end) =>
if (queries.tail.isEmpty) {
showQuery(queries.head) :: showDuration(r.duration / 1e6)
} else {
Document.text("[Concurrent]") :: showDuration(r.duration / 1e6) :: Document
.nest(2, pile(queries.toList.map(showQuery)))
}
}

def showQuery(q: FetchQuery[_, _]): Document = q match {
case FetchOne(id, ds) => Document.text(s"[Fetch one] From `${ds.name}` with id ${id}")
case FetchMany(ids, ds) =>
Document.text(s"[Fetch many] From `${ds.name}` with ids ${ids.toList}")
}

def showMissing(ds: DataSourceName, ids: List[_]): Document = {
Document.text(s"`${ds}` missing identities ${ids}")
}

def showRoundCount(err: FetchException): Document = {
Document.text(s", fetch interrupted after ${err.env.rounds.size} rounds")
}

def showException(err: FetchException): Document = err match {
case NotFound(env, q @ FetchOne(id, ds)) =>
Document.text(s"[Error] Identity not found: ${id} in `${ds.name}`") :: showRoundCount(err)
case MissingIdentities(env, missing) =>
Document.text("[Error] Missing identities") :: showRoundCount(err) :/:
Document.nest(2, pile(missing.toSeq.map((kv) => showMissing(kv._1, kv._2))))
case UnhandledException(env, exc) =>
Document
.text(s"[Error] Unhandled `${exc.getClass.getName}`: '${exc.getMessage}'") :: showRoundCount(
err)
}

/* Given a [[fetch.env.Env]], describe it with a human-readable string. */
def describe(env: Env): String = {
string(showEnv(env))
}

/* Given a [[fetch.FetchException]], describe it with a human-readable string. */
def describe(err: FetchException): String = {
string(
showException(err) :/:
Document.nest(
2,
showEnv(err.env)
)
)
}
}
126 changes: 126 additions & 0 deletions debug/shared/src/main/scala/document.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2016 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fetch.document

import java.io.Writer

case object DocNil extends Document
case object DocBreak extends Document
case class DocText(txt: String) extends Document
case class DocGroup(doc: Document) extends Document
case class DocNest(indent: Int, doc: Document) extends Document
case class DocCons(hd: Document, tl: Document) extends Document

/**
* A basic pretty-printing library, based on Lindig's strict version
* of Wadler's adaptation of Hughes' pretty-printer.
*
* @author Michel Schinz
* @version 1.0
*/
abstract class Document {
def ::(hd: Document): Document = DocCons(hd, this)
def ::(hd: String): Document = DocCons(DocText(hd), this)
def :/:(hd: Document): Document = hd :: DocBreak :: this
def :/:(hd: String): Document = hd :: DocBreak :: this

/**
* Format this document on `writer` and try to set line
* breaks so that the result fits in `width` columns.
*/
def format(width: Int, writer: Writer) {
type FmtState = (Int, Boolean, Document)

def fits(w: Int, state: List[FmtState]): Boolean = state match {
case _ if w < 0 =>
false
case List() =>
true
case (_, _, DocNil) :: z =>
fits(w, z)
case (i, b, DocCons(h, t)) :: z =>
fits(w, (i, b, h) :: (i, b, t) :: z)
case (_, _, DocText(t)) :: z =>
fits(w - t.length(), z)
case (i, b, DocNest(ii, d)) :: z =>
fits(w, (i + ii, b, d) :: z)
case (_, false, DocBreak) :: z =>
fits(w - 1, z)
case (_, true, DocBreak) :: z =>
true
case (i, _, DocGroup(d)) :: z =>
fits(w, (i, false, d) :: z)
}

def spaces(n: Int) {
var rem = n
while (rem >= 16) { writer write " "; rem -= 16 }
if (rem >= 8) { writer write " "; rem -= 8 }
if (rem >= 4) { writer write " "; rem -= 4 }
if (rem >= 2) { writer write " "; rem -= 2 }
if (rem == 1) { writer write " " }
}

def fmt(k: Int, state: List[FmtState]): Unit = state match {
case List() => ()
case (_, _, DocNil) :: z =>
fmt(k, z)
case (i, b, DocCons(h, t)) :: z =>
fmt(k, (i, b, h) :: (i, b, t) :: z)
case (i, _, DocText(t)) :: z =>
writer write t
fmt(k + t.length(), z)
case (i, b, DocNest(ii, d)) :: z =>
fmt(k, (i + ii, b, d) :: z)
case (i, true, DocBreak) :: z =>
writer write "\n"
spaces(i)
fmt(i, z)
case (i, false, DocBreak) :: z =>
writer write " "
fmt(k + 1, z)
case (i, b, DocGroup(d)) :: z =>
val fitsFlat = fits(width - k, (i, false, d) :: z)
fmt(k, (i, !fitsFlat, d) :: z)
case _ =>
()
}

fmt(0, (0, false, DocGroup(this)) :: Nil)
}
}

object Document {

/** The empty document */
def empty = DocNil

/** A break, which will either be turned into a space or a line break */
def break = DocBreak

/** A document consisting of some text literal */
def text(s: String): Document = DocText(s)

/**
* A group, whose components will either be printed with all breaks
* rendered as spaces, or with all breaks rendered as line breaks.
*/
def group(d: Document): Document = DocGroup(d)

/** A nested document, which will be indented as specified. */
def nest(i: Int, d: Document): Document = DocNest(i, d)
}
13 changes: 0 additions & 13 deletions docs/src/jekyll/_config.yml

This file was deleted.

1 change: 0 additions & 1 deletion docs/src/jekyll/_layouts/docs.html

This file was deleted.

1 change: 0 additions & 1 deletion docs/src/jekyll/_layouts/home.html

This file was deleted.

Loading