Skip to content

Commit f7c71d3

Browse files
committed
tut powered blog
1 parent 7d9a902 commit f7c71d3

File tree

10 files changed

+249
-1
lines changed

10 files changed

+249
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Gemfile.lock
66
/vendor/bundle
77
css/main.css
88
.sass-cache
9+
target

_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ kramdown:
1414
hard_wrap: false
1515
highlighter: rouge
1616
exclude:
17-
["node_modules", "gulpfile.js", "package.json", "README.md", "CNAME", "vendor"]
17+
["node_modules", "gulpfile.js", "package.json", "README.md", "CNAME", "vendor", "src", "project", "build.sbt", "_tut"]
1818
permalink: /blog/:year/:month/:day/:title.html
1919
paginate: 10
2020
paginate_path: "/blog/:num/"

build.sbt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
scalaVersion := "2.11.8"
2+
3+
name := "typelevel-blog-tut"
4+
5+
scalacOptions ++= Seq(
6+
"-deprecation",
7+
"-feature"
8+
)
9+
10+
libraryDependencies ++= Seq(
11+
"io.get-coursier" %% "coursier" % "1.0.0-M14",
12+
"io.get-coursier" %% "coursier-cache" % "1.0.0-M14",
13+
"com.chuusai" %% "shapeless" % "2.3.0",
14+
"org.yaml" % "snakeyaml" % "1.17"
15+
)
16+
17+
lazy val tutInput = SettingKey[File]("tutInput")
18+
lazy val tutOutput = SettingKey[File]("tutOutput")
19+
lazy val tutVersion = SettingKey[String]("tutVersion")
20+
21+
tutInput := (baseDirectory in ThisBuild).value / "_tut"
22+
tutOutput := (baseDirectory in ThisBuild).value / "_posts"
23+
tutVersion := "0.4.4"
24+
25+
watchSources ++= (tutInput.value ** "*.md").get
26+
27+
enablePlugins(BuildInfoPlugin)
28+
29+
buildInfoKeys := Seq[BuildInfoKey](tutInput, tutOutput, tutVersion)
30+
31+
buildInfoPackage := "org.typelevel.blog"

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=0.13.12

project/plugins.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1")

src/main/scala/FrontMatter.scala

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.typelevel.blog
2+
3+
import coursier._
4+
import coursier.util.Parse
5+
import java.io.File
6+
import java.net.URLClassLoader
7+
8+
case class Tut(scala: String, binaryScala: String, dependencies: List[String]) {
9+
10+
val tutResolution: Resolution = Resolution(Set(
11+
Dependency(Module("org.tpolecat", s"tut-core_$binaryScala"), BuildInfo.tutVersion)
12+
))
13+
14+
val libResolution: Resolution = Resolution(dependencies.map { dep =>
15+
val (mod, v) = Parse.moduleVersion(dep, binaryScala).right.get
16+
Dependency(mod, v)
17+
}.toSet)
18+
19+
def invoke(file: File): Unit = {
20+
val tutClasspath = resolve(tutResolution).get
21+
val libClasspath = resolve(libResolution).get
22+
23+
val classLoader = new URLClassLoader(tutClasspath.map(_.toURI.toURL).toArray, null)
24+
val tutClass = classLoader.loadClass("tut.TutMain")
25+
val tutMain = tutClass.getDeclaredMethod("main", classOf[Array[String]])
26+
27+
val commandLine = Array(
28+
file.toString,
29+
BuildInfo.tutOutput.toString,
30+
".*",
31+
"-classpath",
32+
libClasspath.mkString(File.pathSeparator)
33+
)
34+
35+
tutMain.invoke(null, commandLine)
36+
}
37+
}
38+
39+
case class FrontMatter(tut: Tut)

src/main/scala/Main.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.typelevel.blog
2+
3+
import org.yaml.snakeyaml.Yaml
4+
5+
object Main extends App {
6+
7+
val posts = BuildInfo.tutInput.listFiles().toList.filter { file =>
8+
file.isFile() && file.getName.endsWith(".md")
9+
}.map(Post(_))
10+
11+
posts.foreach(_.process())
12+
13+
}

src/main/scala/Post.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.typelevel.blog
2+
3+
import coursier._
4+
import java.io.{File, FileInputStream}
5+
import java.nio.file.Files
6+
import org.yaml.snakeyaml.Yaml
7+
import scala.util.Try
8+
9+
case class Post(file: File) {
10+
11+
lazy val frontMatter: Option[FrontMatter] = Try {
12+
val yaml = new Yaml()
13+
val stream = new FileInputStream(file)
14+
val any = yaml.loadAll(stream).iterator.next()
15+
stream.close()
16+
any
17+
}.flatMap(YAML.decodeTo[FrontMatter]).toOption
18+
19+
lazy val out: File =
20+
new File(BuildInfo.tutOutput.toString + File.separator + file.getName)
21+
22+
def outdated(): Boolean =
23+
!(out.exists() && out.isFile() && file.lastModified() <= out.lastModified())
24+
25+
def process(): Unit =
26+
if (outdated()) {
27+
println(s"Processing ${file.getName} ...")
28+
29+
frontMatter match {
30+
case Some(FrontMatter(tut)) =>
31+
tut.invoke(file)
32+
case None =>
33+
println("No tut header, copying.")
34+
Files.copy(file.toPath, out.toPath)
35+
}
36+
}
37+
else {
38+
println(s"Skipping ${file.getName} (up to date).")
39+
}
40+
41+
}

src/main/scala/YAML.scala

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.typelevel.blog
2+
3+
import org.yaml.snakeyaml.Yaml
4+
import scala.collection.JavaConverters._
5+
import scala.util.Try
6+
import shapeless._
7+
import shapeless.labelled.FieldType
8+
9+
trait YAML[T] {
10+
def rawDecode(any: Any): T
11+
12+
final def decode(any: Any): Try[T] =
13+
Try(rawDecode(any))
14+
}
15+
16+
trait LowPriorityYAML {
17+
18+
implicit def deriveInstance[F, G](implicit gen: LabelledGeneric.Aux[F, G], yg: Lazy[YAML[G]]): YAML[F] =
19+
new YAML[F] {
20+
def rawDecode(any: Any) = gen.from(yg.value.rawDecode(any))
21+
}
22+
23+
}
24+
25+
object YAML extends LowPriorityYAML {
26+
27+
def apply[T](implicit T: YAML[T]): YAML[T] = T
28+
29+
def decodeTo[T : YAML](any: Any): Try[T] =
30+
YAML[T].decode(any)
31+
32+
implicit def listYAML[T : YAML]: YAML[List[T]] =
33+
new YAML[List[T]] {
34+
def rawDecode(any: Any) =
35+
any.asInstanceOf[java.util.List[_]].asScala.toList.map(YAML[T].rawDecode)
36+
}
37+
38+
implicit def stringYAML: YAML[String] =
39+
new YAML[String] {
40+
def rawDecode(any: Any) =
41+
any.asInstanceOf[String]
42+
}
43+
44+
implicit def deriveHNil: YAML[HNil] =
45+
new YAML[HNil] {
46+
def rawDecode(any: Any) = HNil
47+
}
48+
49+
implicit def deriveHCons[K <: Symbol, V, T <: HList]
50+
(implicit
51+
key: Witness.Aux[K],
52+
yv: Lazy[YAML[V]],
53+
yt: Lazy[YAML[T]]
54+
): YAML[FieldType[K, V] :: T] = new YAML[FieldType[K, V] :: T] {
55+
def rawDecode(any: Any) = {
56+
val k = key.value.name
57+
val map = any.asInstanceOf[java.util.Map[String, _]]
58+
if (!map.containsKey(k))
59+
throw new IllegalArgumentException(s"key $k not defined")
60+
val head: FieldType[K, V] = labelled.field(yv.value.rawDecode(map.get(k)))
61+
val tail = yt.value.rawDecode(map)
62+
head :: tail
63+
}
64+
}
65+
66+
}

src/main/scala/package.scala

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.typelevel.blog
2+
3+
import coursier._
4+
import java.io.File
5+
import scala.language.implicitConversions
6+
import scala.util.{Failure, Success, Try}
7+
import scalaz.\/
8+
import scalaz.concurrent.Task
9+
import scalaz.std.list._
10+
import scalaz.syntax.traverse._
11+
12+
object `package` {
13+
14+
val repositories = Seq(
15+
Cache.ivy2Local,
16+
MavenRepository("https://repo1.maven.org/maven2"),
17+
MavenRepository("https://dl.bintray.com/tpolecat/maven/")
18+
)
19+
20+
implicit def eitherToTry[T](e: Throwable \/ T): Try[T] =
21+
e.fold(Failure(_), Success(_))
22+
23+
def resolve(start: Resolution): Try[List[File]] = {
24+
val logger = new Cache.Logger {
25+
override def downloadingArtifact(url: String, file: File) =
26+
println(s"Downloading artifact from $url ...")
27+
override def downloadedArtifact(url: String, success: Boolean) = {
28+
val file = url.split('/').last
29+
if (success)
30+
println(s"Successfully downloaded $file")
31+
else
32+
println(s"Failed to download $file")
33+
}
34+
}
35+
36+
val fetch = Fetch.from(repositories, Cache.fetch(logger = Some(logger)))
37+
38+
start.process.run(fetch).unsafePerformSyncAttempt.flatMap { resolution =>
39+
if (!resolution.isDone)
40+
\/.left(new RuntimeException("resolution did not converge"))
41+
else if (!resolution.conflicts.isEmpty)
42+
\/.left(new RuntimeException(s"resolution has conflicts: ${resolution.conflicts.mkString(", ")}"))
43+
else if (!resolution.errors.isEmpty)
44+
\/.left(new RuntimeException(s"resolution has errors: ${resolution.errors.mkString(", ")}"))
45+
else {
46+
Task.gatherUnordered(
47+
resolution.artifacts.map(artifact =>
48+
Cache.file(artifact, logger = Some(logger)).run
49+
)
50+
).unsafePerformSyncAttempt.map(_.sequenceU).flatMap(_.leftMap(err => new RuntimeException(err.describe)))
51+
}
52+
}
53+
}
54+
55+
}

0 commit comments

Comments
 (0)