diff --git a/.gitignore b/.gitignore index 76675066..2b8b8e75 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ Gemfile.lock /vendor/bundle css/main.css .sass-cache +target +_posts +.deploy diff --git a/.travis.yml b/.travis.yml index e7f61edd..eb67e251 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,34 @@ sudo: false language: ruby rvm: 2.2 +env: + global: + - secure: "WowDbou7Zejy22YFIxnO0enMSGtxQi++cfp0IKDv/8aK2JVR2FG4TLvLUpW2py0QWfRCyUfNfPxM/Srr4czx580mG5PUrPdYwOR4sk3/VI2SA6FNxteObBp1I0eRopHrXzXFess50ruiLBDVuc+3eM63n2O1gl2VSnW7jKdpL80=" + install: - bundle install + - curl -s https://raw.githubusercontent.com/paulp/sbt-extras/master/sbt -o sbt + - chmod +x sbt script: + - ./sbt run - bundle exec jekyll build + +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt/boot +before_cache: + - find $HOME/.ivy2 -name "ivydata-*.properties" -delete + - find $HOME/.sbt -name "*.lock" -delete + +branches: + except: + - master + +deploy: + - provider: script + script: ./deploy.sh + skip_cleanup: true + on: + branch: development diff --git a/_config.yml b/_config.yml index 624b9b3f..c393597f 100644 --- a/_config.yml +++ b/_config.yml @@ -14,7 +14,7 @@ kramdown: hard_wrap: false highlighter: rouge exclude: - ["node_modules", "gulpfile.js", "package.json", "README.md", "CNAME", "vendor"] + ["node_modules", "gulpfile.js", "package.json", "README.md", "CNAME", "vendor", "src", "project", "build.sbt", "posts"] permalink: /blog/:year/:month/:day/:title.html paginate: 10 paginate_path: "/blog/:num/" diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..5ef112e3 --- /dev/null +++ b/build.sbt @@ -0,0 +1,31 @@ +scalaVersion := "2.11.8" + +name := "typelevel-blog-tut" + +scalacOptions ++= Seq( + "-deprecation", + "-feature" +) + +libraryDependencies ++= Seq( + "io.get-coursier" %% "coursier" % "1.0.0-M14", + "io.get-coursier" %% "coursier-cache" % "1.0.0-M14", + "com.chuusai" %% "shapeless" % "2.3.0", + "org.yaml" % "snakeyaml" % "1.17" +) + +lazy val tutInput = SettingKey[File]("tutInput") +lazy val tutOutput = SettingKey[File]("tutOutput") +lazy val tutVersion = SettingKey[String]("tutVersion") + +tutInput := (baseDirectory in ThisBuild).value / "posts" +tutOutput := (baseDirectory in ThisBuild).value / "_posts" +tutVersion := "0.4.4" + +watchSources ++= (tutInput.value ** "*.md").get + +enablePlugins(BuildInfoPlugin) + +buildInfoKeys := Seq[BuildInfoKey](tutInput, tutOutput, tutVersion) + +buildInfoPackage := "org.typelevel.blog" diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 00000000..b1605a99 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -o pipefail +set -e + +GIT_EMAIL="bot@typelevel.org" +GIT_NAME="Typelevel Bot" +REMOTE_REPO="https://$GH_TOKEN@github.com/typelevel/typelevel.github.com.git" +BRANCH="master" + +if [ -z "$GH_TOKEN" ]; then + echo "No GitHub access token set, not deploying" + exit 1 +fi + +git config --global user.name "$GIT_NAME" +git config --global user.email "$GIT_EMAIL" + +git clone --depth 1 -b "$BRANCH" --no-checkout "$REMOTE_REPO" .deploy +git archive --format=tar "$TRAVIS_COMMIT" | ( + cd .deploy + tar xf - +) + +cp -r _posts .deploy + +cd .deploy +git add -A -f +git commit -m "Update site at $(date --rfc-2822 -u)" --allow-empty +git push diff --git a/_posts/2013-04-04-inauguration.md b/posts/2013-04-04-inauguration.md similarity index 100% rename from _posts/2013-04-04-inauguration.md rename to posts/2013-04-04-inauguration.md diff --git a/_posts/2013-06-24-deriving-instances-1.md b/posts/2013-06-24-deriving-instances-1.md similarity index 100% rename from _posts/2013-06-24-deriving-instances-1.md rename to posts/2013-06-24-deriving-instances-1.md diff --git a/_posts/2013-07-07-generic-numeric-programming.md b/posts/2013-07-07-generic-numeric-programming.md similarity index 100% rename from _posts/2013-07-07-generic-numeric-programming.md rename to posts/2013-07-07-generic-numeric-programming.md diff --git a/_posts/2013-09-11-using-scalaz-Unapply.md b/posts/2013-09-11-using-scalaz-Unapply.md similarity index 100% rename from _posts/2013-09-11-using-scalaz-Unapply.md rename to posts/2013-09-11-using-scalaz-Unapply.md diff --git a/_posts/2013-10-13-spires-ops-macros.md b/posts/2013-10-13-spires-ops-macros.md similarity index 100% rename from _posts/2013-10-13-spires-ops-macros.md rename to posts/2013-10-13-spires-ops-macros.md diff --git a/_posts/2013-10-13-towards-scalaz-1.md b/posts/2013-10-13-towards-scalaz-1.md similarity index 100% rename from _posts/2013-10-13-towards-scalaz-1.md rename to posts/2013-10-13-towards-scalaz-1.md diff --git a/_posts/2013-10-18-treelog.md b/posts/2013-10-18-treelog.md similarity index 100% rename from _posts/2013-10-18-treelog.md rename to posts/2013-10-18-treelog.md diff --git a/_posts/2013-11-17-discipline.md b/posts/2013-11-17-discipline.md similarity index 100% rename from _posts/2013-11-17-discipline.md rename to posts/2013-11-17-discipline.md diff --git a/_posts/2013-12-15-towards-scalaz-2.md b/posts/2013-12-15-towards-scalaz-2.md similarity index 100% rename from _posts/2013-12-15-towards-scalaz-2.md rename to posts/2013-12-15-towards-scalaz-2.md diff --git a/_posts/2014-01-18-implicitly_existential.md b/posts/2014-01-18-implicitly_existential.md similarity index 100% rename from _posts/2014-01-18-implicitly_existential.md rename to posts/2014-01-18-implicitly_existential.md diff --git a/_posts/2014-02-21-error-handling.md b/posts/2014-02-21-error-handling.md similarity index 100% rename from _posts/2014-02-21-error-handling.md rename to posts/2014-02-21-error-handling.md diff --git a/_posts/2014-03-09-liskov_lifting.md b/posts/2014-03-09-liskov_lifting.md similarity index 100% rename from _posts/2014-03-09-liskov_lifting.md rename to posts/2014-03-09-liskov_lifting.md diff --git a/_posts/2014-04-14-fix.md b/posts/2014-04-14-fix.md similarity index 100% rename from _posts/2014-04-14-fix.md rename to posts/2014-04-14-fix.md diff --git a/_posts/2014-06-22-mapping-sets.md b/posts/2014-06-22-mapping-sets.md similarity index 100% rename from _posts/2014-06-22-mapping-sets.md rename to posts/2014-06-22-mapping-sets.md diff --git a/_posts/2014-07-02-type_equality_to_leibniz.md b/posts/2014-07-02-type_equality_to_leibniz.md similarity index 100% rename from _posts/2014-07-02-type_equality_to_leibniz.md rename to posts/2014-07-02-type_equality_to_leibniz.md diff --git a/_posts/2014-07-06-singleton_instance_trick_unsafe.md b/posts/2014-07-06-singleton_instance_trick_unsafe.md similarity index 100% rename from _posts/2014-07-06-singleton_instance_trick_unsafe.md rename to posts/2014-07-06-singleton_instance_trick_unsafe.md diff --git a/_posts/2014-09-02-typelevel-scala.md b/posts/2014-09-02-typelevel-scala.md similarity index 100% rename from _posts/2014-09-02-typelevel-scala.md rename to posts/2014-09-02-typelevel-scala.md diff --git a/_posts/2014-09-20-higher_leibniz.md b/posts/2014-09-20-higher_leibniz.md similarity index 100% rename from _posts/2014-09-20-higher_leibniz.md rename to posts/2014-09-20-higher_leibniz.md diff --git a/_posts/2014-11-10-why_is_adt_pattern_matching_allowed.md b/posts/2014-11-10-why_is_adt_pattern_matching_allowed.md similarity index 100% rename from _posts/2014-11-10-why_is_adt_pattern_matching_allowed.md rename to posts/2014-11-10-why_is_adt_pattern_matching_allowed.md diff --git a/_posts/2015-02-26-rawtypes.md b/posts/2015-02-26-rawtypes.md similarity index 100% rename from _posts/2015-02-26-rawtypes.md rename to posts/2015-02-26-rawtypes.md diff --git a/_posts/2015-07-13-type-members-parameters.md b/posts/2015-07-13-type-members-parameters.md similarity index 100% rename from _posts/2015-07-13-type-members-parameters.md rename to posts/2015-07-13-type-members-parameters.md diff --git a/_posts/2015-07-16-method-equiv.md b/posts/2015-07-16-method-equiv.md similarity index 100% rename from _posts/2015-07-16-method-equiv.md rename to posts/2015-07-16-method-equiv.md diff --git a/_posts/2015-07-19-forget-refinement-aux.md b/posts/2015-07-19-forget-refinement-aux.md similarity index 100% rename from _posts/2015-07-19-forget-refinement-aux.md rename to posts/2015-07-19-forget-refinement-aux.md diff --git a/_posts/2015-07-23-type-projection.md b/posts/2015-07-23-type-projection.md similarity index 100% rename from _posts/2015-07-23-type-projection.md rename to posts/2015-07-23-type-projection.md diff --git a/_posts/2015-07-27-nested-existentials.md b/posts/2015-07-27-nested-existentials.md similarity index 100% rename from _posts/2015-07-27-nested-existentials.md rename to posts/2015-07-27-nested-existentials.md diff --git a/_posts/2015-07-30-values-never-change-types.md b/posts/2015-07-30-values-never-change-types.md similarity index 100% rename from _posts/2015-07-30-values-never-change-types.md rename to posts/2015-07-30-values-never-change-types.md diff --git a/_posts/2015-08-06-machinist.md b/posts/2015-08-06-machinist.md similarity index 100% rename from _posts/2015-08-06-machinist.md rename to posts/2015-08-06-machinist.md diff --git a/_posts/2015-08-07-symbolic-operators.md b/posts/2015-08-07-symbolic-operators.md similarity index 100% rename from _posts/2015-08-07-symbolic-operators.md rename to posts/2015-08-07-symbolic-operators.md diff --git a/_posts/2015-09-21-change-values.md b/posts/2015-09-21-change-values.md similarity index 100% rename from _posts/2015-09-21-change-values.md rename to posts/2015-09-21-change-values.md diff --git a/_posts/2015-12-11-announcement_summit.md b/posts/2015-12-11-announcement_summit.md similarity index 100% rename from _posts/2015-12-11-announcement_summit.md rename to posts/2015-12-11-announcement_summit.md diff --git a/_posts/2016-01-14-summit_assistance.md b/posts/2016-01-14-summit_assistance.md similarity index 100% rename from _posts/2016-01-14-summit_assistance.md rename to posts/2016-01-14-summit_assistance.md diff --git a/_posts/2016-01-20-summit_keynote.md b/posts/2016-01-20-summit_keynote.md similarity index 100% rename from _posts/2016-01-20-summit_keynote.md rename to posts/2016-01-20-summit_keynote.md diff --git a/_posts/2016-01-28-existential-inside.md b/posts/2016-01-28-existential-inside.md similarity index 100% rename from _posts/2016-01-28-existential-inside.md rename to posts/2016-01-28-existential-inside.md diff --git a/_posts/2016-01-28-summit_programme.md b/posts/2016-01-28-summit_programme.md similarity index 100% rename from _posts/2016-01-28-summit_programme.md rename to posts/2016-01-28-summit_programme.md diff --git a/_posts/2016-02-04-variance-and-functors.md b/posts/2016-02-04-variance-and-functors.md similarity index 100% rename from _posts/2016-02-04-variance-and-functors.md rename to posts/2016-02-04-variance-and-functors.md diff --git a/_posts/2016-03-13-information-hiding.md b/posts/2016-03-13-information-hiding.md similarity index 100% rename from _posts/2016-03-13-information-hiding.md rename to posts/2016-03-13-information-hiding.md diff --git a/_posts/2016-03-24-typelevel-boulder.md b/posts/2016-03-24-typelevel-boulder.md similarity index 100% rename from _posts/2016-03-24-typelevel-boulder.md rename to posts/2016-03-24-typelevel-boulder.md diff --git a/_posts/2016-05-10-internal-state.md b/posts/2016-05-10-internal-state.md similarity index 100% rename from _posts/2016-05-10-internal-state.md rename to posts/2016-05-10-internal-state.md diff --git a/_posts/2016-08-21-hkts-moving-forward.md b/posts/2016-08-21-hkts-moving-forward.md similarity index 100% rename from _posts/2016-08-21-hkts-moving-forward.md rename to posts/2016-08-21-hkts-moving-forward.md diff --git a/_posts/2016-09-19-variance-phantom.md b/posts/2016-09-19-variance-phantom.md similarity index 100% rename from _posts/2016-09-19-variance-phantom.md rename to posts/2016-09-19-variance-phantom.md diff --git a/_posts/2016-09-21-edsls-part-1.md b/posts/2016-09-21-edsls-part-1.md similarity index 100% rename from _posts/2016-09-21-edsls-part-1.md rename to posts/2016-09-21-edsls-part-1.md diff --git a/_posts/2016-09-30-subtype-typeclasses.md b/posts/2016-09-30-subtype-typeclasses.md similarity index 87% rename from _posts/2016-09-30-subtype-typeclasses.md rename to posts/2016-09-30-subtype-typeclasses.md index bf225d8b..6addebd8 100644 --- a/_posts/2016-09-30-subtype-typeclasses.md +++ b/posts/2016-09-30-subtype-typeclasses.md @@ -6,6 +6,12 @@ meta: nav: blog author: adelbertc pygments: true + +tut: + scala: 2.11.8 + binaryScala: "2.11" + dependencies: + - org.typelevel::cats-core:0.7.2 --- The common encoding of type classes in Scala relies on subtyping. This singular @@ -16,7 +22,7 @@ fact gives us a certain cleanliness in the code, but at what cost? Consider the following hierarchy of type classes. A similar hierarchy can be found in both [Cats][cats] and [Scalaz 7][scalaz7]. -```scala +```tut:book:silent trait Functor[F[_]] trait Applicative[F[_]] extends Functor[F] @@ -34,7 +40,7 @@ us to call methods like `map`, `flatMap`, and `traverse` directly on some `F[A]`, provided `F` has the appropriate type class instances (`Functor`, `Monad`, and `Traverse`, respectively). -```scala +```tut:reset:book:silent import cats._ import cats.implicits._ ``` @@ -42,7 +48,7 @@ import cats.implicits._ One important consequence is we can use for comprehensions in methods parameterized over some `Monad`. -```scala +```tut:book:silent def foo[F[_]: Monad]: F[Int] = for { a <- Monad[F].pure(10) b <- Monad[F].pure(20) @@ -57,17 +63,9 @@ Or is it? Consider a case where we want to abstract over a data type that has both `Monad` and `Traverse`. -```scala +```tut:book:fail // Ignore the fact we're not even using `Traverse` - we can't even call `map`! def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity) -// :19: error: value map is not a member of type parameter F[Int] -// def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity) -// ^ -// :19: error: missing argument list for method identity in object Predef -// Unapplied methods are only converted to functions when a function type is expected. -// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`. -// def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].pure(10).map(identity) -// ^ ``` We're already in trouble. In order to call `map` we need `F` to have a @@ -83,26 +81,20 @@ knowing that. This problem generalizes to anytime the compiler decides an implicit is ambiguous, such as method calls. -```scala +```tut:book:silent // The fact we don't actually use `Functor` here is irrelevant. def bar[F[_]: Applicative: Functor]: F[Int] = Applicative[F].pure(10) ``` -```scala +```tut:book:fail def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F] -// :19: error: ambiguous implicit values: -// both value evidence$2 of type cats.Traverse[F] -// and value evidence$1 of type cats.Monad[F] -// match expected type cats.Functor[F] -// def callBar[F[_]: Monad: Traverse]: F[Int] = bar[F] -// ^ ``` What do we do? For `map` it is easy enough to arbitrarily pick one of the instances and call `map` on that. For function calls you can thread the implicit through explicitly. -```scala +```tut:book:silent def foo[F[_]: Monad: Traverse]: F[Int] = Monad[F].map(Monad[F].pure(10))(identity) def callBar[F[_]: Monad: Traverse]: F[Int] = bar(Monad[F], Monad[F]) @@ -122,14 +114,11 @@ if we had three, four, five? And the trouble doesn't end there. We asked for a `Monad` so let's try using a for comprehension. -```scala +```tut:book:fail def foo[F[_]: Monad: Traverse]: F[Int] = for { a <- Monad[F].pure(10) b <- Monad[F].pure(20) } yield a + b -// :21: error: value map is not a member of type parameter F[Int] -// b <- Monad[F].pure(20) -// ^ ``` This is also broken! Because of how [for comprehensions][forcomp] desugar, a @@ -139,7 +128,7 @@ This drastically reduces the ergonomics of doing anything monadic. As with `map` we could call `flatMap` on `Monad` directly, but this quickly becomes cumbersome. -```scala +```tut:book:silent def foo[F[_]: Monad: Traverse]: F[Int] = { val M = Monad[F] M.flatMap(M.pure(10)) { a => @@ -171,7 +160,7 @@ an interesting alternative prototyped in [scato][scato], now making its way to encoding completely throws out the notion of subtyping, encoding the hierarchy via members instead. -```scala +```tut:reset:book:silent trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } @@ -195,7 +184,7 @@ danger of implicit ambiguity. However, for that very reason, having a anyway. What we can do is use implicit conversions to re-encode the hierarchy. -```scala +```tut:book:silent implicit def applicativeIsFunctor[F[_]: Applicative]: Functor[F] = implicitly[Applicative[F]].functor @@ -205,31 +194,23 @@ implicit def traverseIsFunctor[F[_]: Traverse]: Functor[F] = But now we're back to square one. -```scala +```tut:book:silent // Syntax for Functor implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) { def map[B](f: A => B): F[B] = F.map(fa)(f) } ``` -```scala +```tut:book:fail def foo[F[_]: Applicative: Traverse]: F[Int] = implicitly[Applicative[F]].pure(10).map(identity) -// :19: error: value map is not a member of type parameter F[Int] -// implicitly[Applicative[F]].pure(10).map(identity) -// ^ -// :19: error: missing argument list for method identity in object Predef -// Unapplied methods are only converted to functions when a function type is expected. -// You can make this conversion explicit by writing `identity _` or `identity(_)` instead of `identity`. -// implicitly[Applicative[F]].pure(10).map(identity) -// ^ ``` Since both implicits have equal priority, the compiler doesn't know which one to pick. **However**, Scala has mechanisms for [prioritizing implicits][implicits] which solves the problem. -```scala +```tut:reset:book:silent object Prioritized { // needed for tut, irrelevant to demonstration trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] @@ -289,7 +270,7 @@ sources. This can be solved by removing the hierarchy from the superclasses (removing `Functor`'s `extends FunctorConversions0`), but comes at the cost of needing an import at use sites to bring the implicits into scope. -```scala +```tut:reset:book:silent trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } @@ -322,7 +303,7 @@ trait FunctorConversions0 extends FunctorConversions1 { object Prelude extends FunctorConversions0 ``` -```scala +```tut:book:silent // Need this import to get implicit conversions in scope import Prelude._ @@ -350,7 +331,7 @@ Another thing we can try is to make some compromise of the two. We can continue to use subtyping for a blessed subset of the hierarchy, and use members for any branching type class. -```scala +```tut:reset:book:silent trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..35c88bab --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.12 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 00000000..04935e45 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1") diff --git a/src/main/scala/FrontMatter.scala b/src/main/scala/FrontMatter.scala new file mode 100644 index 00000000..af5ae9b0 --- /dev/null +++ b/src/main/scala/FrontMatter.scala @@ -0,0 +1,39 @@ +package org.typelevel.blog + +import coursier._ +import coursier.util.Parse +import java.io.File +import java.net.URLClassLoader + +case class Tut(scala: String, binaryScala: String, dependencies: List[String]) { + + val tutResolution: Resolution = Resolution(Set( + Dependency(Module("org.tpolecat", s"tut-core_$binaryScala"), BuildInfo.tutVersion) + )) + + val libResolution: Resolution = Resolution(dependencies.map { dep => + val (mod, v) = Parse.moduleVersion(dep, binaryScala).right.get + Dependency(mod, v) + }.toSet) + + def invoke(file: File): Unit = { + val tutClasspath = resolve(tutResolution).get + val libClasspath = resolve(libResolution).get + + val classLoader = new URLClassLoader(tutClasspath.map(_.toURI.toURL).toArray, null) + val tutClass = classLoader.loadClass("tut.TutMain") + val tutMain = tutClass.getDeclaredMethod("main", classOf[Array[String]]) + + val commandLine = Array( + file.toString, + BuildInfo.tutOutput.toString, + ".*", + "-classpath", + libClasspath.mkString(File.pathSeparator) + ) + + tutMain.invoke(null, commandLine) + } +} + +case class FrontMatter(tut: Tut) diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala new file mode 100644 index 00000000..510a4a3d --- /dev/null +++ b/src/main/scala/Main.scala @@ -0,0 +1,13 @@ +package org.typelevel.blog + +import org.yaml.snakeyaml.Yaml + +object Main extends App { + + val posts = BuildInfo.tutInput.listFiles().toList.filter { file => + file.isFile() && file.getName.endsWith(".md") + }.map(Post(_)) + + posts.foreach(_.process()) + +} diff --git a/src/main/scala/Post.scala b/src/main/scala/Post.scala new file mode 100644 index 00000000..7bf291d3 --- /dev/null +++ b/src/main/scala/Post.scala @@ -0,0 +1,42 @@ +package org.typelevel.blog + +import coursier._ +import java.io.{File, FileInputStream} +import java.nio.file.Files +import org.yaml.snakeyaml.Yaml +import scala.util.Try + +case class Post(file: File) { + + lazy val frontMatter: Option[FrontMatter] = Try { + val yaml = new Yaml() + val stream = new FileInputStream(file) + val any = yaml.loadAll(stream).iterator.next() + stream.close() + any + }.flatMap(YAML.decodeTo[FrontMatter]).toOption + + lazy val out: File = + new File(BuildInfo.tutOutput.toString + File.separator + file.getName) + + def outdated(): Boolean = + !(out.exists() && out.isFile() && file.lastModified() <= out.lastModified()) + + def process(): Unit = + if (outdated()) { + println(s"[blog] Processing ${file.getName} ...") + BuildInfo.tutOutput.mkdirs() + + frontMatter match { + case Some(FrontMatter(tut)) => + tut.invoke(file) + case None => + println("[blog] No tut header, copying.") + Files.copy(file.toPath, out.toPath) + } + } + else { + println(s"[blog] Skipping ${file.getName} (up to date).") + } + +} diff --git a/src/main/scala/YAML.scala b/src/main/scala/YAML.scala new file mode 100644 index 00000000..00ce9160 --- /dev/null +++ b/src/main/scala/YAML.scala @@ -0,0 +1,66 @@ +package org.typelevel.blog + +import org.yaml.snakeyaml.Yaml +import scala.collection.JavaConverters._ +import scala.util.Try +import shapeless._ +import shapeless.labelled.FieldType + +trait YAML[T] { + def rawDecode(any: Any): T + + final def decode(any: Any): Try[T] = + Try(rawDecode(any)) +} + +trait LowPriorityYAML { + + implicit def deriveInstance[F, G](implicit gen: LabelledGeneric.Aux[F, G], yg: Lazy[YAML[G]]): YAML[F] = + new YAML[F] { + def rawDecode(any: Any) = gen.from(yg.value.rawDecode(any)) + } + +} + +object YAML extends LowPriorityYAML { + + def apply[T](implicit T: YAML[T]): YAML[T] = T + + def decodeTo[T : YAML](any: Any): Try[T] = + YAML[T].decode(any) + + implicit def listYAML[T : YAML]: YAML[List[T]] = + new YAML[List[T]] { + def rawDecode(any: Any) = + any.asInstanceOf[java.util.List[_]].asScala.toList.map(YAML[T].rawDecode) + } + + implicit def stringYAML: YAML[String] = + new YAML[String] { + def rawDecode(any: Any) = + any.asInstanceOf[String] + } + + implicit def deriveHNil: YAML[HNil] = + new YAML[HNil] { + def rawDecode(any: Any) = HNil + } + + implicit def deriveHCons[K <: Symbol, V, T <: HList] + (implicit + key: Witness.Aux[K], + yv: Lazy[YAML[V]], + yt: Lazy[YAML[T]] + ): YAML[FieldType[K, V] :: T] = new YAML[FieldType[K, V] :: T] { + def rawDecode(any: Any) = { + val k = key.value.name + val map = any.asInstanceOf[java.util.Map[String, _]] + if (!map.containsKey(k)) + throw new IllegalArgumentException(s"key $k not defined") + val head: FieldType[K, V] = labelled.field(yv.value.rawDecode(map.get(k))) + val tail = yt.value.rawDecode(map) + head :: tail + } + } + +} diff --git a/src/main/scala/package.scala b/src/main/scala/package.scala new file mode 100644 index 00000000..a9282b25 --- /dev/null +++ b/src/main/scala/package.scala @@ -0,0 +1,55 @@ +package org.typelevel.blog + +import coursier._ +import java.io.File +import scala.language.implicitConversions +import scala.util.{Failure, Success, Try} +import scalaz.\/ +import scalaz.concurrent.Task +import scalaz.std.list._ +import scalaz.syntax.traverse._ + +object `package` { + + val repositories = Seq( + Cache.ivy2Local, + MavenRepository("https://repo1.maven.org/maven2"), + MavenRepository("https://dl.bintray.com/tpolecat/maven/") + ) + + implicit def eitherToTry[T](e: Throwable \/ T): Try[T] = + e.fold(Failure(_), Success(_)) + + def resolve(start: Resolution): Try[List[File]] = { + val logger = new Cache.Logger { + override def downloadingArtifact(url: String, file: File) = + println(s"[blog] Downloading artifact from $url ...") + override def downloadedArtifact(url: String, success: Boolean) = { + val file = url.split('/').last + if (success) + println(s"[blog] Successfully downloaded $file") + else + println(s"[blog] Failed to download $file") + } + } + + val fetch = Fetch.from(repositories, Cache.fetch(logger = Some(logger))) + + start.process.run(fetch).unsafePerformSyncAttempt.flatMap { resolution => + if (!resolution.isDone) + \/.left(new RuntimeException("resolution did not converge")) + else if (!resolution.conflicts.isEmpty) + \/.left(new RuntimeException(s"resolution has conflicts: ${resolution.conflicts.mkString(", ")}")) + else if (!resolution.errors.isEmpty) + \/.left(new RuntimeException(s"resolution has errors: ${resolution.errors.mkString(", ")}")) + else { + Task.gatherUnordered( + resolution.artifacts.map(artifact => + Cache.file(artifact, logger = Some(logger)).run + ) + ).unsafePerformSyncAttempt.map(_.sequenceU).flatMap(_.leftMap(err => new RuntimeException(err.describe))) + } + } + } + +}