Skip to content

Commit a88d8ff

Browse files
author
colin-passiv
committed
Initial commit
0 parents  commit a88d8ff

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.project
2+
target

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Embed
2+
3+
Scala macro for embedding file content directly into a scala source file at compile time.
4+
5+
## Usage
6+
7+
first import the macro:
8+
```scala
9+
import com.passivsystems.embed.Embed._
10+
```
11+
12+
then can reference any file with a path relative to the current source file:
13+
```scala
14+
val text: String = embed("resource.txt")
15+
```
16+
17+
## String interpolation
18+
19+
By using the ```sEmbed```, we can have similar semantics to ```s""``` String interpolation. The embedded file can refer to any variable in scope at the point of call.
20+
21+
Note, that the curly braces are required for all embedded expressions. e.g. ```${myVal}``` not ```$myVal```
22+
23+
e.g.
24+
```scala
25+
val guard = true
26+
val txt = sEmbed("template.txt")
27+
```
28+
where template.txt:
29+
```
30+
the guard is: ${if (guard) "TRUE" else "FALSE"}
31+
```
32+
33+
## Scala js
34+
35+
One possible usecase is in Scala js projects to move html into their own file.
36+
e.g.
37+
38+
```scala
39+
import org.scalajs.dom
40+
def el[T <: dom.raw.Element](id: String) =
41+
dom.document.getElementById(id).asInstanceOf[T]
42+
43+
val btnId = "uniqueId"
44+
// el[dom.raw.HTMLElement]("div").innerHTML = s"""
45+
// <button id="${btnId}">Click me</button>
46+
// """
47+
el[dom.raw.HTMLElement]("div").innerHTML = sEmbed("status.html")
48+
el[dom.raw.HTMLButtonElement](btnId).onclick = { event: MouseEvent => println("Clicked!") }
49+
```
50+
where status.html:
51+
```html
52+
<button id="${btnId}">Click me</button>
53+
```

build.sbt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
organization := "com.passivsystems"
2+
3+
name := "embed"
4+
5+
version := "0.0.1"
6+
7+
scalaVersion := "2.11.7"
8+
9+
libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _) // for scala macro

src/main/scala/Embed.scala

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.passivsystems.embed
2+
3+
package object Embed {
4+
import scala.language.experimental.macros
5+
6+
/** Embeds file content directly into source at compile time.
7+
*
8+
* The content file is a path relative to the source file.
9+
*/
10+
def embed (path: String): String = macro Embed.embed
11+
12+
/** Embeds file content and string interpolates it at compile time.
13+
*
14+
* The content file is a path relative to the source file.
15+
*
16+
* Note: the curly braces are required, and only simple identifier expressions are supported.
17+
* e.g. {{{ ${myVal} }}}
18+
*
19+
* {{{
20+
* val divId = "uniqueId"
21+
* val html = sEmbed("status.html")
22+
* }}}
23+
* Where status.html (in same directory):
24+
* {{{
25+
* <div id="${divId}">Div content</div>
26+
* }}}
27+
*/
28+
def sEmbed(path: String): String = macro Embed.sEmbed
29+
30+
private[this] object Embed {
31+
import scala.reflect.macros.blackbox.Context
32+
33+
def embed(c: Context)(path: c.Expr[String]): c.Expr[String] = {
34+
import c.universe._
35+
embedImpl(c)(path, false)
36+
}
37+
38+
def sEmbed(c: Context)(path: c.Expr[String]): c.Expr[String] = {
39+
import c.universe._
40+
embedImpl(c)(path, true)
41+
}
42+
43+
def embedImpl(c: Context)(path: c.Expr[String], interpolate: Boolean): c.Expr[String] = {
44+
import c.universe._
45+
46+
val q"${pathConst: String}" = path.tree
47+
48+
val pos = path.tree.pos
49+
val currentDirectory = pos.source.file.file.getAbsoluteFile.getParentFile
50+
val rawContent = contentOf(currentDirectory, pathConst)
51+
52+
if (interpolate) interpolateExpr(c)(rawContent)
53+
else constantExpr(c)(rawContent)
54+
}
55+
56+
def contentOf(currentDirectory: java.io.File, path: String) = {
57+
val source = scala.io.Source.fromFile(new java.io.File(currentDirectory, path))
58+
val content = source.mkString
59+
source.close()
60+
content
61+
}
62+
63+
def interpolateExpr(c: Context)(content: String): c.Expr[String] = {
64+
import c.universe._
65+
val parts = content.split("\\$\\{([^\\}]*)\\}").toSeq
66+
val args = "\\$\\{([^\\}]*)\\}".r.findAllMatchIn(content).map(_.group(1)).map(c.parse(_)).toSeq
67+
c.Expr[String](q"scala.StringContext(..$parts).s(..$args)")
68+
}
69+
70+
def constantExpr(c: Context)(content: String): c.Expr[String] = {
71+
import c.universe._
72+
c.Expr[String](q"$content")
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)