summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-12 22:42:47 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-12 22:55:53 +0800
commitc0f39d743fbdf07544a6f5b6284d7123e5c36296 (patch)
tree64470286acb80c61e711299eded0b67fd516a8b8 /docs
parentfd9c399db8c1c0d86cc65d5e1c41968b42a813d1 (diff)
downloadcask-c0f39d743fbdf07544a6f5b6284d7123e5c36296.tar.gz
cask-c0f39d743fbdf07544a6f5b6284d7123e5c36296.tar.bz2
cask-c0f39d743fbdf07544a6f5b6284d7123e5c36296.zip
auto publishing of docs w/ example downloads works
Diffstat (limited to 'docs')
-rw-r--r--docs/build.sc198
-rw-r--r--docs/favicon.pngbin0 -> 206 bytes
-rw-r--r--docs/logo-white.svg1
-rw-r--r--docs/pageStyles.sc130
-rw-r--r--docs/pages.sc192
-rw-r--r--docs/pages/1 - Cask: a Scala HTTP micro-framework .md (renamed from docs/readme.md)91
6 files changed, 582 insertions, 30 deletions
diff --git a/docs/build.sc b/docs/build.sc
new file mode 100644
index 0000000..48fd3e7
--- /dev/null
+++ b/docs/build.sc
@@ -0,0 +1,198 @@
+// Load dependencies
+import $ivy.{`org.pegdown:pegdown:1.6.0`, `com.lihaoyi::scalatags:0.6.5`}
+import $file.pageStyles, pageStyles._
+import $file.pages, pages._
+import scalatags.Text.all._
+import $file.^.version
+import ammonite.ops._
+import collection.JavaConverters._
+import org.pegdown.{PegDownProcessor, ToHtmlSerializer, LinkRenderer, Extensions}
+import org.pegdown.ast.{VerbatimNode, ExpImageNode, HeaderNode, TextNode, SimpleNode, TableNode}
+
+val (releaseTag, label) = version.publishVersion
+
+val postsFolder = cwd/'pages
+
+interp.watch(postsFolder)
+
+val targetFolder = cwd/'target
+
+
+val (markdownFiles, otherFiles) = ls! postsFolder partition (_.ext == "md")
+markdownFiles.foreach(println)
+// Walk the posts/ folder and parse out the name, full- and first-paragraph-
+// HTML of each post to be used on their respective pages and on the index
+
+val posts = {
+ val split = for(path <- markdownFiles) yield {
+ val Array(number, name) = path.last.split(" - ", 2)
+ (number, name.stripSuffix(".md"), path)
+ }
+ for ((index, name, path) <- split.sortBy(_._1.toInt)) yield {
+ val processor = new PegDownProcessor(
+ Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.AUTOLINKS
+ )
+
+ val txt = read(path)
+ .replaceAll(
+ """\$\$\$([a-zA-Z_0-9]+)""",
+ s"[example project](https://github.com/lihaoyi/cask/releases/download/$releaseTag/$label.$$1)"
+ )
+
+ val ast = processor.parseMarkdown(txt.toArray)
+ val headers = collection.mutable.Buffer.empty[(String, Int)]
+ class Serializer extends ToHtmlSerializer(new LinkRenderer){
+ override def printImageTag(rendering: LinkRenderer.Rendering) {
+ printer.print("<div style=\"text-align: center\"><img")
+ printAttribute("src", rendering.href)
+ // shouldn't include the alt attribute if its empty
+ if(!rendering.text.equals("")){
+ printAttribute("alt", rendering.text)
+ }
+ import collection.JavaConversions._
+ for (attr <- rendering.attributes) {
+ printAttribute(attr.name, attr.value)
+ }
+ printer.print(" style=\"max-width: 100%; max-height: 500px\"")
+ printer.print(" /></div>")
+ }
+ override def visit(node: HeaderNode) = {
+ val tag = "h" + node.getLevel()
+
+
+ val id =
+ node
+ .getChildren
+ .asScala
+ .collect{case t: TextNode => t.getText}
+ .mkString
+
+ headers.append(id -> node.getLevel())
+
+
+ val setId = s"id=${'"'+sanitize(id)+'"'}"
+ printer.print(s"""<$tag $setId class="${Styles.hoverBox.name}">""")
+ visitChildren(node)
+ printer.print(
+ a(href := ("#" + sanitize(id)), Styles.hoverLink)(
+ i(cls := "fa fa-link", aria.hidden := true)
+ ).render
+ )
+ printer.print(s"</$tag>")
+ }
+
+ override def visit(node: VerbatimNode) = {
+ printer.println().print(
+ """<pre style="background-color: #f8f8f8">""" +
+ s"""<code style="white-space:pre; background-color: #f8f8f8" class="${node.getType()}">"""
+ )
+
+ var text = node.getText()
+ // print HTML breaks for all initial newlines
+ while(text.charAt(0) == '\n') {
+ printer.print("\n")
+ text = text.substring(1)
+ }
+ printer.printEncoded(text)
+ printer.print("</code></pre>")
+ }
+ override def visit(node: TableNode) = {
+ currentTableNode = node
+ printer.print("<table class=\"table table-bordered\">")
+ visitChildren(node)
+ printer.print("</table>")
+ currentTableNode = null
+ }
+ }
+
+ val postlude = Seq[Frag](
+ hr,
+
+ p(
+ b("About the Author:"),
+ i(
+ " Haoyi is a software engineer, an early contributor to ",
+ a(href:="http://www.scala-js.org/")("Scala.js"),
+ ", and the author of many open-source Scala tools such as Mill, the ",
+ a(href:="lihaoyi.com/Ammonite", "Ammonite REPL"), " and ",
+ a(href:="https://github.com/lihaoyi/fastparse", "FastParse"), ". "
+ )
+ ),
+ p(
+ i(
+ "If you've enjoy using Mill, or enjoyed using Haoyi's other open ",
+ "source libraries, please chip in (or get your Company to chip in!) via ",
+ a(href:="https://www.patreon.com/lihaoyi", "Patreon"), " so he can ", "continue his open-source work"
+ )
+ ),
+ hr
+ ).render
+
+ val rawHtmlContent = new Serializer().toHtml(ast) + postlude
+ PostInfo(name, headers, rawHtmlContent)
+ }
+}
+
+def formatRssDate(date: java.time.LocalDate) = {
+ date
+ .atTime(0, 0)
+ .atZone(java.time.ZoneId.of("UTC"))
+ .format(java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME)
+}
+
+@main
+def main(publish: Boolean = false) = {
+
+ rm! targetFolder
+
+ mkdir! targetFolder/'page
+ for(otherFile <- otherFiles){
+ cp(otherFile, targetFolder/'page/(otherFile relativeTo postsFolder))
+ }
+
+ cp(pwd/"favicon.png", targetFolder/"favicon.ico")
+ cp(pwd/"logo-white.svg", targetFolder/"logo-white.svg")
+
+ for(i <- posts.indices){
+ val post = posts(i)
+
+ val adjacentLinks = div(display.flex, flexDirection.row, justifyContent.spaceBetween)(
+ for((j, isNext) <- Seq(i-1 -> false, i+1 -> true))
+ yield posts.lift(j) match{
+ case None => div()
+ case Some(dest) =>
+ renderAdjacentLink(
+ isNext,
+ dest.name,
+ (i == 0, j == 0) match {
+ case (true, true) => s"index.html"
+ case (true, false) => s"page/${sanitize(dest.name)}.html"
+ case (false, true) => s"../index.html"
+ case (false, false) => s"${sanitize(dest.name)}.html"
+ }
+ )
+ }
+ )
+
+
+ write(
+ if (i == 0) targetFolder / "index.html"
+ else targetFolder/'page/s"${sanitize(post.name)}.html",
+ postContent(
+ i == 0,
+ post,
+ adjacentLinks,
+ posts.map(_.name)
+ )
+ )
+ }
+
+ if (publish){
+ implicit val wd = cwd/'target
+ %git 'init
+ %git('add, "-A", ".")
+ %git('commit, "-am", "first commit")
+ %git('remote, 'add, 'origin, "git@github.com:lihaoyi/cask.git")
+ %git('push, "-uf", 'origin, "master:gh-pages")
+ }
+} \ No newline at end of file
diff --git a/docs/favicon.png b/docs/favicon.png
new file mode 100644
index 0000000..82430a7
--- /dev/null
+++ b/docs/favicon.png
Binary files differ
diff --git a/docs/logo-white.svg b/docs/logo-white.svg
new file mode 100644
index 0000000..a681aa9
--- /dev/null
+++ b/docs/logo-white.svg
@@ -0,0 +1 @@
+<!DOCTYPE html><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24"><polyline points="24,-44.0 0,4.0 0,20.0 24,-28.0" fill="#f8f8f8"></polyline><polyline points="24,-20.0 0,28.0 0,44.0 24,-4.0" fill="#f8f8f8"></polyline><polyline points="24,4.0 0,52.0 0,68.0 24,20.0" fill="#f8f8f8"></polyline></svg> \ No newline at end of file
diff --git a/docs/pageStyles.sc b/docs/pageStyles.sc
new file mode 100644
index 0000000..06651b1
--- /dev/null
+++ b/docs/pageStyles.sc
@@ -0,0 +1,130 @@
+import $ivy.`com.lihaoyi::scalatags:0.6.5`
+
+import scalatags.stylesheet._
+import scalatags.Text.all._
+
+
+val marginWidth = "25%"
+object WideStyles extends StyleSheet{
+ initStyleSheet()
+ override def customSheetName = Some("WideStyles")
+ def header = cls(
+ position.fixed,
+ top := 0,
+ bottom := 0,
+ width := marginWidth,
+ justifyContent.center,
+ display.flex,
+ flexDirection.column
+ )
+ def tableOfContentsItem = cls(
+ // We have to use inline-block and verticalAlign.middle and width: 100%
+ // here, instead of simply using display.block, because display.block items
+ // with overflow.hidden seem to misbehave and render badly in different ways
+ // between firefox (renders correctly), chrome (body of list item is offset
+ // one row from the bullet) and safari (bullet is entirely missing)
+ display.`inline-block`,
+ width := "100%",
+ verticalAlign.middle,
+ overflow.hidden,
+ textOverflow.ellipsis
+
+ )
+ def tableOfContents = cls(
+ display.flex,
+ flexDirection.column,
+ flexGrow := 1,
+ flexShrink := 1,
+ minHeight := 0,
+ width := "100%"
+
+ )
+ def content = cls(
+ padding := "2em 3em 0",
+ padding := 48,
+ marginLeft := marginWidth,
+ boxSizing.`border-box`
+ )
+ def footer = cls(
+ position.fixed,
+ bottom := 0,
+ height := 50,
+ width := marginWidth
+ )
+ def marginLeftZero = cls(
+ marginLeft := 0
+ )
+}
+object NarrowStyles extends StyleSheet{
+ initStyleSheet()
+ override def customSheetName = Some("NarrowStyles")
+ def header = cls(
+ marginBottom := 10
+ )
+ def content = cls(
+ padding := 16
+ )
+ def headerContent = cls(
+ flexDirection.row,
+ width := "100%",
+ display.flex,
+ alignItems.center
+ )
+
+ def flexFont = cls(
+ fontSize := "4vw"
+ )
+ def disappear = cls(
+ display.none
+ )
+ def floatLeft = cls(
+ float.left,
+ marginLeft := 30
+ )
+}
+object Styles extends CascadingStyleSheet{
+ initStyleSheet()
+ override def customSheetName = Some("Styles")
+ def hoverBox = cls(
+ display.flex,
+ flexDirection.row,
+ alignItems.center,
+ justifyContent.spaceBetween,
+ &hover(
+ hoverLink(
+ opacity := 0.5
+ )
+ )
+ )
+ def hoverLink = cls(
+ opacity := 0.1,
+ &hover(
+ opacity := 1.0
+ )
+ )
+ def headerStyle = cls(
+ backgroundColor := "rgb(61, 79, 93)",
+ display.flex,
+ boxSizing.`border-box`
+ )
+ def headerLinkBox = cls(
+ flex := 1,
+ display.flex,
+ flexDirection.column,
+ )
+ def headerLink = cls(
+ flex := 1,
+ display.flex,
+ justifyContent.center,
+ alignItems.center,
+ padding := "10px 10px"
+ )
+ def footerStyle = cls(
+ display.flex,
+ justifyContent.center,
+ color := "rgb(158, 167, 174)"
+ )
+ def subtleLink = cls(
+ textDecoration.none
+ )
+} \ No newline at end of file
diff --git a/docs/pages.sc b/docs/pages.sc
new file mode 100644
index 0000000..a6c05c0
--- /dev/null
+++ b/docs/pages.sc
@@ -0,0 +1,192 @@
+import $ivy.`com.lihaoyi::scalatags:0.6.5`
+import scalatags.Text.all._, scalatags.Text.tags2
+import java.time.LocalDate
+import $file.pageStyles, pageStyles._
+
+case class PostInfo(name: String,
+ headers: Seq[(String, Int)],
+ rawHtmlContent: String)
+
+
+def sanitize(s: String): String = {
+ s.split(" |-", -1).map(_.filter(_.isLetterOrDigit)).mkString("-").toLowerCase
+}
+def pageChrome(titleText: Option[String],
+ homePage: Boolean,
+ contents: Frag,
+ contentHeaders: Seq[(String, Int)],
+ pageHeaders: Seq[String]): String = {
+ val pageTitle = titleText.getOrElse("Mill")
+ val sheets = Seq(
+ "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
+ "https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css",
+ "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/github-gist.min.css"
+ )
+
+
+ html(
+ head(
+ meta(charset := "utf-8"),
+ for(sheet <- sheets)
+ yield link(href := sheet, rel := "stylesheet", `type` := "text/css" ),
+ tags2.title(pageTitle),
+ tags2.style(s"@media (min-width: 60em) {${WideStyles.styleSheetText}}"),
+ tags2.style(s"@media (max-width: 60em) {${NarrowStyles.styleSheetText}}"),
+ tags2.style(Styles.styleSheetText),
+ script(src:="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js"),
+ script(src:="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/languages/scala.min.js"),
+ script(raw("hljs.initHighlightingOnLoad();")),
+ // This makes media queries work on iphone (???)
+ // http://stackoverflow.com/questions/13002731/responsive-design-media-query-not-working-on-iphone
+ meta(name:="viewport", content:="initial-scale = 1.0,maximum-scale = 1.0")
+ ),
+ body(margin := 0, backgroundColor := "#f8f8f8")(
+ navBar(homePage, contentHeaders, pageHeaders),
+ div(
+ WideStyles.content,
+ NarrowStyles.content,
+ maxWidth := 900,
+ titleText.map(h1(_)),
+ contents
+ ),
+ if (contentHeaders.nonEmpty) frag()
+ else div(
+ WideStyles.footer,
+ Styles.footerStyle,
+ "Last published ", currentTimeText
+ )
+
+ )
+ ).render
+}
+
+def navBar(homePage: Boolean, contentHeaders: Seq[(String, Int)], pageHeaders: Seq[String]) = {
+ def navList(navLabel: String, frags: Frag, narrowHide: Boolean) = {
+ div(
+ WideStyles.tableOfContents,
+ if(narrowHide) NarrowStyles.disappear else frag(),
+ color := "#f8f8f8"
+ )(
+ div(paddingLeft := 40, NarrowStyles.disappear)(
+ b(navLabel)
+ ),
+ div(overflowY.auto, flexShrink := 1, minHeight := 0)(
+ ul(
+ overflow.hidden,
+ textAlign.start,
+ marginTop := 10,
+ whiteSpace.nowrap,
+ textOverflow.ellipsis,
+ marginRight := 10
+ )(
+ frags
+ )
+ )
+ )
+ }
+ val pageList = navList(
+ "Pages",
+ for((header, i) <- pageHeaders.zipWithIndex) yield li(
+ WideStyles.marginLeftZero,
+ NarrowStyles.floatLeft
+ )(
+ a(
+ color := "#f8f8f8",
+ WideStyles.tableOfContentsItem,
+ href := {
+ (homePage, i == 0) match {
+ case (true, true) => s"index.html"
+ case (true, false) => s"page/${sanitize(header)}.html"
+ case (false, true) => s"../index.html"
+ case (false, false) => s"${sanitize(header)}.html"
+ }
+ }
+ )(
+ header
+ )
+ ),
+ narrowHide = false
+ )
+
+ val headerBox = div(
+ NarrowStyles.headerContent,
+ h1(
+ textAlign.center,
+ a(
+ img(
+ src := {homePage match{
+ case false => s"../logo-white.svg"
+ case true => "logo-white.svg"
+ }},
+ height := 30,
+ marginTop := -5
+ ),
+ color := "#f8f8f8",
+ " Mill",
+ href := (if (homePage) "" else ".."),
+ Styles.subtleLink,
+ NarrowStyles.flexFont,
+ fontWeight.bold
+ ),
+ padding := "30px 30px",
+ margin := 0
+ ),
+ div(
+ Styles.headerLinkBox,
+ pageList
+ )
+ )
+
+
+ val tableOfContents = navList(
+ "Table of Contents",
+ for {
+ (header, indent) <- contentHeaders
+ offset <- indent match{
+ case 2 => Some(0)
+ case 3 => Some(20)
+ case _ => None
+ }
+ } yield li(marginLeft := offset)(
+ a(
+ color := "#f8f8f8",
+ WideStyles.tableOfContentsItem,
+ href := s"#${sanitize(header)}"
+ )(
+ header
+ )
+ ),
+ narrowHide = true
+ )
+
+ div(
+ WideStyles.header,
+ NarrowStyles.header,
+ Styles.headerStyle,
+ headerBox,
+ hr(NarrowStyles.disappear, backgroundColor := "#f8f8f8", width := "80%"),
+ tableOfContents
+ )
+}
+
+
+val currentTimeText = LocalDate.now.toString
+
+
+def renderAdjacentLink(next: Boolean, name: String, url: String) = {
+ a(href := url)(
+ if(next) frag(name, " ", i(cls:="fa fa-arrow-right" , aria.hidden:=true))
+ else frag(i(cls:="fa fa-arrow-left" , aria.hidden:=true), " ", name)
+ )
+}
+def postContent(homePage: Boolean, post: PostInfo, adjacentLinks: Frag, posts: Seq[String]) = pageChrome(
+ Some(post.name),
+ homePage,
+ Seq[Frag](
+ div(adjacentLinks, marginBottom := 10),
+ raw(post.rawHtmlContent),
+ adjacentLinks
+ ),
+ post.headers,
+ pageHeaders = posts
+) \ No newline at end of file
diff --git a/docs/readme.md b/docs/pages/1 - Cask: a Scala HTTP micro-framework .md
index 4add926..22d879e 100644
--- a/docs/readme.md
+++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework .md
@@ -1,6 +1,3 @@
-Cask: a Scala HTTP micro-framework
-==================================
-
```scala
object MinimalApplication extends cask.MainRoutes{
@cask.get("/")
@@ -17,10 +14,10 @@ object MinimalApplication extends cask.MainRoutes{
}
```
-Cask is a simple Scala web framework inspired by Python's
-[Flask](http://flask.pocoo.org/docs/1.0/) project. It aims to bring simplicity,
-flexibility and ease-of-use to Scala webservers, avoiding cryptic DSLs or
-complicated asynchrony.
+[Cask](https://github.com/lihaoyi/cask) is a simple Scala web framework inspired
+by Python's [Flask](http://flask.pocoo.org/docs/1.0/) project. It aims to bring
+simplicity, flexibility and ease-of-use to Scala webservers, avoiding cryptic
+DSLs or complicated asynchrony.
Getting Started
---------------
@@ -29,7 +26,8 @@ The easiest way to begin using Cask is by downloading the
[Mill](http://www.lihaoyi.com/mill/) example project:
- Install [Mill](http://www.lihaoyi.com/mill/)
-- Unzip [XXX](XXX) into a folder. This should give you the following files:
+- Unzip the $$$minimalApplication into a folder. This should
+ give you the following files:
```text
build.sc
app/src/MinimalExample.scala
@@ -80,8 +78,11 @@ ivy"com.lihaoyi::cask:0.1.0"
"com.lihaoyi" %% "cask" % "0.1.0"
```
-Minimal Example
----------------
+Example Projects
+----------------
+
+### Minimal Example
+
```scala
object MinimalApplication extends cask.MainRoutes{
@cask.get("/")
@@ -98,6 +99,8 @@ object MinimalApplication extends cask.MainRoutes{
}
```
+- $$$minimalApplication
+
The rough outline of how the minimal example works should be easy to understand:
- You define an object that inherits from `cask.MainRoutes`
@@ -142,11 +145,13 @@ object MinimalRoutes extends cask.Routes{
object MinimalMain extends cask.Main(MinimalRoutes)
```
+- $$$minimalApplication2
+-
You can split up your routes into separate `cask.Routes` objects as makes sense
and pass them all into `cask.Main`.
-Variable Routes
----------------
+### Variable Routes
+
```scala
object VariableRoutes extends cask.MainRoutes{
@@ -169,6 +174,8 @@ object VariableRoutes extends cask.MainRoutes{
}
```
+- $$$variableRoutes
+
You can bind variables to endpoints by declaring them as parameters: these are
either taken from a path-segment matcher of the same name (e.g. `postId` above),
or from query-parameters of the same name (e.g. `param` above). You can make
@@ -181,8 +188,8 @@ If you need to capture the entire sub-path of the request, you can set the flag
matter). This will make the route match any sub-path of the prefix given to the
`@cask` decorator, and give you the remainder to use in your endpoint logic.
-Receiving Form-encoded or JSON data
------------------------------------
+### Receiving Form-encoded or JSON data
+
```scala
object FormJsonPost extends cask.MainRoutes{
@@ -205,6 +212,8 @@ object FormJsonPost extends cask.MainRoutes{
}
```
+- $$$formJsonPost
+
If you need to handle a JSON-encoded POST request, you can use the
`@cast.postJson` decorator. This assumes the posted request body is a JSON dict,
and uses its keys to populate the endpoint's parameters, either as raw
@@ -226,8 +235,8 @@ deserialization into Scala data-types fails, a 400 response is returned
automatically with a helpful error message.
-Processing Cookies
-------------------
+### Processing Cookies
+
```scala
object Cookies extends cask.MainRoutes{
@@ -256,14 +265,16 @@ object Cookies extends cask.MainRoutes{
}
```
+- $$$cookies
+
Cookies are most easily read by declaring a `: cask.Cookie` parameter; the
parameter name is used to fetch the cookie you are interested in. Cookies can be
stored by setting the `cookie` attribute in the response, and deleted simply by
setting `expires = java.time.Instant.EPOCH` (i.e. to have expired a long time
ago)
-Serving Static Files
---------------------
+### Serving Static Files
+
```scala
object StaticFiles extends cask.MainRoutes{
@cask.get("/")
@@ -278,13 +289,15 @@ object StaticFiles extends cask.MainRoutes{
}
```
+- $$$staticFiles
+
You can ask Cask to serve static files by defining a `@cask.static` endpoint.
This will match any subpath of the value returned by the endpoint (e.g. above
`/static/file.txt`, `/static/folder/file.txt`, etc.) and return the file
contents from the corresponding file on disk (and 404 otherwise).
-Redirects or Aborts
--------------------
+### Redirects or Aborts
+
```scala
object RedirectAbort extends cask.MainRoutes{
@cask.get("/")
@@ -301,12 +314,14 @@ object RedirectAbort extends cask.MainRoutes{
}
```
+- $$$redirectAbort
+
Cask provides some convenient helpers `cask.Redirect` and `cask.Abort` which you
can return; these are simple wrappers around `cask.Request`, and simply set up
the relevant headers or status code for you.
-Extending Endpoints with Decorators
------------------------------------
+### Extending Endpoints with Decorators
+
```scala
object Decorated extends cask.MainRoutes{
@@ -354,6 +369,8 @@ object Decorated extends cask.MainRoutes{
}
```
+- $$$decorateds
+
You can write extra decorator annotations that stack on top of the existing
`@cask.get`/`@cask.post` to provide additional arguments or validation. This is
done by implementing the `cask.Decorator` interface and it's `getRawParams`
@@ -430,12 +447,14 @@ object Decorated2 extends cask.MainRoutes{
}
```
+- $$$decorated2
+
This is convenient for cases where you want a set of decorators to apply broadly
across your web application, and do not want to repeat them over and over at
every single endpoint.
-Gzip & Deflated Responses
--------------------------
+### Gzip & Deflated Responses
+
```scala
object Compress extends cask.MainRoutes{
@@ -451,6 +470,8 @@ object Compress extends cask.MainRoutes{
```
+- $$$compress
+
Cask provides a useful `@cask.decorators.compress` decorator that gzips or
deflates a response body if possible. This is useful if you don't have a proxy
like Nginx or similar in front of your server to perform the compression for
@@ -474,6 +495,8 @@ object Compress2 extends cask.Routes{
object Compress2Main extends cask.Main(Compress2)
```
+- $$$compress2
+
Or globally, in your `cask.Main`:
```scala
@@ -492,8 +515,10 @@ object Compress3Main extends cask.Main(Compress3){
}
```
-TodoMVC Api Server
-------------------
+- $$$compress3
+
+### TodoMVC Api Server
+
```scala
object TodoMvcApi extends cask.MainRoutes{
@@ -535,6 +560,8 @@ object TodoMvcApi extends cask.MainRoutes{
}
```
+- $$$todoApi
+
This is a simple self-contained example of using Cask to write an in-memory API
server for the common [TodoMVC example app](http://todomvc.com/).
@@ -543,8 +570,8 @@ etc.. Those can be managed via the normal mechanism for
[Serving Static Files](#serving-static-files).
-TodoMVC Database Integration
-----------------------------
+### TodoMVC Database Integration
+
```scala
import cask.internal.Router
import com.typesafe.config.ConfigFactory
@@ -631,6 +658,8 @@ object TodoMvcDb extends cask.MainRoutes{
```
+- $$$todoDb
+
This example demonstrates how to use Cask to write a TodoMVC API server that
persists it's state in a database rather than in memory. We use the
[Quill](http://getquill.io/) database access library to write a `@transactional`
@@ -648,8 +677,8 @@ be passed into each endpoint function as an additional parameter list as
described in
[Extending Endpoints with Decorators](#extending-endpoints-with-decorators).
-TodoMVC Full Stack Web
-----------------------
+### TodoMVC Full Stack Web
+
The following code snippet is the complete code for a full-stack TodoMVC
implementation: including HTML generation for the web UI via
@@ -845,6 +874,8 @@ object Server extends cask.MainRoutes{
}
```
+- $$$todo
+
Main Customization
------------------