-
Notifications
You must be signed in to change notification settings - Fork 59
/
Compose.scala
150 lines (130 loc) · 4.33 KB
/
Compose.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.ybrikman.ping.scalaapi.compose
import com.ybrikman.ping.scalaapi.bigpipe.HtmlStream
import play.api.http.HeaderNames
import play.api.libs.iteratee.Iteratee
import play.api.mvc.{Cookies, Cookie, Codec, Result}
import play.twirl.api.Html
import scala.concurrent.{Future, ExecutionContext}
/**
* Helpers for building Play apps out of standalone, composable pagelets.
* Note: these are not yet tested or documented, so use at your own risk.
*/
object Compose {
val cssHeaderName = "X-CSS"
val jsHeaderName = "X-JS"
/**
* Read the body of a Result as Html. Since the body is an Enumerator and may not be available yet, this method
* returns a Future.
*
* @param result
* @param codec
* @return
*/
def readBody(result: Result)(implicit codec: Codec, ec: ExecutionContext): Future[Html] = {
result.body.run(Iteratee.consume()).map(bytes => Html(new String(bytes, codec.charset)))
}
/**
* Merge all the cookies set in the given results into a single sequence.
*
* @param results
* @return
*/
def mergeCookies(results: Result*): Seq[Cookie] = {
results
.flatMap(result => result.header.headers.get(HeaderNames.SET_COOKIE)
.map(Cookies.decodeSetCookieHeader)
.getOrElse(Seq.empty))
}
/**
* Convert the given sequences of CSS and JS into HTTP headers that can be added to the Result
*
* @param css
* @param js
* @return
*/
def asHeaders(css: Seq[String], js: Seq[String]): Seq[(String, String)] = {
Seq(cssHeaderName -> css.mkString(","), jsHeaderName -> js.mkString(","))
}
/**
* Read the CSS header from each result and merge and de-dupe them into a single sequence
*
* @param results
* @return
*/
def mergeCssHeaders(results: Result*): Seq[String] = {
mergeHeaderValues(cssHeaderName, parseCssHeader, results:_*)
}
/**
* Read the JS header from each the result and merge and de-dupe them into a single sequence
*
* @param results
* @return
*/
def mergeJsHeaders(results: Result*): Seq[String] = {
mergeHeaderValues(jsHeaderName, parseJsHeader, results:_*)
}
private def mergeHeaderValues(headerName: String, parseHeader: Result => Seq[String], results: Result*): Seq[String] = {
results.flatMap(parseHeader).distinct
}
/**
* Read the CSS header from the given Result, which should define the CSS dependencies for the Result
*
* @param result
* @return
*/
def parseCssHeader(result: Result): Seq[String] = parseHeader(cssHeaderName, result)
/**
* Read the JS header from the given Result, which should define the CSS dependencies for the Result
*
* @param result
* @return
*/
def parseJsHeader(result: Result): Seq[String] = parseHeader(jsHeaderName, result)
/**
* Render the given sequence of CSS URLs as link tags
*
* @param css
* @return
*/
def renderCssDependencies(css: Seq[String]): Html = {
com.ybrikman.bigpipe.html.css(css)
}
/**
* Render the given sequence of JS URLs as script tags
*
* @param js
* @return
*/
def renderJsDependencies(js: Seq[String]): Html = {
com.ybrikman.bigpipe.html.js(js)
}
/**
* Merge all the JavaScript dependencies from the results into a list of script tags
*
* @param results
* @return
*/
def mergeJsFromResults(results: Future[Result]*)(implicit ec: ExecutionContext): HtmlStream = {
mergeDependenciesFromResults(parseJsHeader, renderJsDependencies, results)
}
/**
* Merge all the CSS dependencies from the results into a list of link tags
*
* @param results
* @return
*/
def mergeCssFromResults(results: Future[Result]*)(implicit ec: ExecutionContext): HtmlStream = {
mergeDependenciesFromResults(parseCssHeader, renderCssDependencies, results)
}
private def parseHeader(headerName: String, result: Result): Seq[String] = {
result.header.headers.get(headerName).map(_.split(",").toVector).getOrElse(Vector.empty)
}
private def mergeDependenciesFromResults(parseHeader: Result => Seq[String], render: Seq[String] => Html, resultFutures: Seq[Future[Result]])(implicit ec: ExecutionContext): HtmlStream = {
val allResultsFuture = Future.sequence(resultFutures)
val htmlFuture = allResultsFuture.map { results =>
val values = results.map(parseHeader).flatten.distinct
render(values)
}
HtmlStream.fromHtmlFuture(htmlFuture)
}
}