Skip to content

Commit

Permalink
implement Snippet mechanism like Paradox
Browse files Browse the repository at this point in the history
This implements low-tech solution for compiled documentation, inspired by Lightbend Paradox's snippet feature.
It's low tech because this feature itself just grabs the referenced file as code block and includes it, and asusmes the compiles validation is done elsewhere. This approach is nice because it's fast.

```
// This includes the entire file as Scala code snippet
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) {}

or

// This includes snippet between a line containing #example another line with #example
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) { #example }

or

// This specifies syntax highlight
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) { #example type=text }
```

There's a hard-coded treatment for `$root$`, which is interpretted to be the root of the build. Otherwise, the path is treated to the relative path from the markdown file.
  • Loading branch information
eed3si9n committed Oct 2, 2017
1 parent 0bcc84a commit e01a35d
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ before_script:
- export JVM_OPTS="-Dfile.encoding=UTF-8 -Xmx1G -Xms1G -server -XX:ReservedCodeCacheSize=128M"

script:
- /usr/bin/sbt scalafmtTest makeSite
- /usr/bin/sbt scalafmtTest scripted makeSite
# Tricks to avoid unnecessary cache updates
- find $HOME/.sbt -name "*.lock" | xargs rm
- find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,23 @@ To push site, from sbt shell:
```

Beware of https://github.com/sbt/sbt-ghpages/issues/25

## Including code examples

To include a validated code examples, create a scripted test under `src/sbt-test`,
and in the markdown include as:

```
// This includes the entire file as Scala code snippet
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) {}
or
// This includes snippet between a line containing #example another line with #example
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) { #example }
or
// This specifies syntax highlight
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) { #example type=text }
```
7 changes: 6 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ lazy val tutorialSubDirName = settingKey[String]("subdir name for old tutorial")
lazy val fileEncoding = settingKey[String]("check the file encoding")

lazy val root = (project in file("."))
.enablePlugins(NanocPlugin, PamfletPlugin)
.enablePlugins(NanocPlugin, LowTechSnippetPamfletPlugin)
.settings(
organization := "org.scala-sbt",
name := "website",
Expand Down Expand Up @@ -34,5 +34,10 @@ lazy val root = (project in file("."))
case x => sys.error(s"Unexpected encoding $x")
}
},
scriptedLaunchOpts := {
scriptedLaunchOpts.value ++
Seq("-Xmx1024M", "-Dplugin.version=" + version.value)
},
scriptedBufferLog := false,
isGenerateSiteMap := true
)
35 changes: 35 additions & 0 deletions project/LowTechSnippletPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sbt._
import Keys._

object LowTechSnippetPamfletPlugin extends AutoPlugin {
import com.typesafe.sbt.site.pamflet._
import PamfletPlugin.autoImport._
import pamflet._

override def requires = PamfletPlugin
override def trigger = noTrigger
override def projectSettings = pamfletSettings

def pamfletSettings: Seq[Setting[_]] =
Seq(
mappings in Pamflet := generate(
(sourceDirectory in Pamflet).value,
(target.value / "lowtech_generated"),
(target in Pamflet).value,
(includeFilter in Pamflet).value,
(pamfletFencePlugins in Pamflet).value
)
)

def generate(input: File,
generated: File,
output: File,
includeFilter: FileFilter,
fencePlugins: Seq[FencePlugin]): Seq[(File, String)] = {
// this is the added step
Snippet.processDirectory(input, generated)
val storage = FileStorage(generated, fencePlugins.toList)
Produce(storage.globalized, output)
output ** includeFilter --- output pair Path.relativeTo(output)
}
}
79 changes: 79 additions & 0 deletions project/Snippet.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import sbt._
import Keys._
import Path.rebase

object Snippet {

/**
* Processes all files under base directory, and outputs to newBase.
* Markdown files are snippet-processed, and the rest are just copied.
*/
def processDirectory(base: File, newBase: File): File = {
IO.createDirectory(newBase)
val files: Seq[File] = (base ** (-DirectoryFilter)).get
val isMarkdown = Set("markdown", "md")
(files pair rebase(base :: Nil, newBase)) foreach {
case (x, newFile) if isMarkdown(x.ext) => processFile(x, newFile)
case (x, newFile) => IO.copyFile(x, newFile)
}
newBase
}

def processFile(baseFile: File, newFile: File): File = {
val xs0 = IO.readLines(baseFile)
val xs: List[String] = xs0 flatMap {
case x if x.trim.startsWith("@@snip") => snippet(x, baseFile)
case x => List(x)
}
IO.writeLines(newFile, xs)
newFile
}

/**
* Use the Lightbend Paradox syntax for snippet inclusion.
* `@@snip [example.log](example.log) { #example-log type=text }`
*/
lazy val Snippet = ("""\@\@snip\s*\[[^\]]+\]\(([^)]*)\)\s*"""
+ """\{\s*(\#[\w-]+)?(\s+type\=[\w-]+)?.*\}\s*""").r
def snippet(line: String, baseFile: File): List[String] = {
def readFromRef(ref: File, tagOpt: Option[String], ty: String): List[String] = {
tagOpt match {
case Some(tag) =>
val xs0 = IO.readLines(ref)
val xs = xs0
.dropWhile({ x =>
!x.contains(tag)
})
.drop(1)
.takeWhile({ x =>
!x.contains(tag)
})
if (xs.isEmpty) {
sys.error(s"@@snip was detected for $ref with tag $tag, but the code was empty!")
}
List(s"```$ty") ::: xs ::: List("```")
case None => List(s"```$ty") ::: IO.readLines(ref) ::: List("```")
}
}
line match {
case Snippet(path0, tag0, ty0) =>
val tag = Option(tag0).map(_.trim)
val ty = Option(ty0).map(_.trim).getOrElse("scala")
val ref = resolvePath(path0, baseFile)
if (!ref.exists) {
sys.error(s"@@snip was detected, but $ref was not found!")
}
readFromRef(ref, tag, ty)
case _ => sys.error(s"Invalid snippet notation: $line")
}
}

/**
* If the path starts from `$root$/` then use the path as is from root.
* Otherwise, treat it as a relative path from the markdown file.
*/
def resolvePath(p: String, baseFile: File): File = {
if (p.startsWith("$root$/")) file(p.drop(7))
else new File(uri(baseFile.getParentFile.toURI.toString + p))
}
}
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.0.0
sbt.version=1.0.2
3 changes: 1 addition & 2 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.3.0")

addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2")

addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.2.0")
libraryDependencies += { "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value }
16 changes: 2 additions & 14 deletions src/reference/00-Getting-Started/05-Basic-Def.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,15 @@ we often call it a *subproject* in this guide.
For instance, in `build.sbt` you define
the subproject located in the current directory like this:

```scala
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "$example_scala_version$"
)
```
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) {}

Each subproject is configured by key-value pairs.

For example, one key is `name` and it maps to a string value, the name of
your subproject.
The key-value pairs are listed under the `.settings(...)` method as follows:

```scala
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "$example_scala_version$"
)
```
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) {}

### How build.sbt defines settings

Expand Down
5 changes: 5 additions & 0 deletions src/reference/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "$example_scala_version$"
)
16 changes: 2 additions & 14 deletions src/reference/ja/00-Getting-Started/05-Basic-Def.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,14 @@ sbt.version=$app_version$

例えば、カレントディレクトリにあるサブプロジェクトは `build.sbt` に以下のように定義できる:

```scala
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "$example_scala_version$"
)
```
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) {}

それぞれのサブプロジェクトは、キーと値のペアによって詳細が設定される。

例えば、`name` というキーがあるが、それはサブプロジェクト名という文字列の値に関連付けられる。
キーと値のペア列は `.settings(...)` メソッド内に列挙される:

```scala
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "$example_scala_version$"
)
```
@@snip [build.sbt]($root$/src/sbt-test/ref/basic/build.sbt) {}

### `build.sbt` はどのように settings を定義するか

Expand Down
5 changes: 5 additions & 0 deletions src/sbt-test/ref/basic/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.3"
)
1 change: 1 addition & 0 deletions src/sbt-test/ref/basic/test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
> name

0 comments on commit e01a35d

Please sign in to comment.