Build modules: base, content, app; example content, minimal layout
authorTomas Zeman <tzeman@volny.cz>
Thu, 29 Nov 2018 12:20:20 +0100
changeset 4 1a1347e8c5be
parent 3 48479e4b89d4
child 5 de7c56ce0654
Build modules: base, content, app; example content, minimal layout
base/resources/config.conf
base/resources/reference.conf
base/src/sqwl/cms/Layout.scala
base/src/sqwl/cms/MD5.scala
base/src/sqwl/cms/Server.scala
base/src/sqwl/cms/Service.scala
base/src/sqwl/cms/UrlScheme.scala
base/src/sqwl/cms/config.scala
base/src/sqwl/cms/datamodel.scala
base/src/sqwl/cms/viewstates.scala
build.sc
example/content/a1.html
example/content/a1/test.txt
example/src/sqwl/cms/Content.scala
jvm/resources/config.conf
jvm/resources/reference.conf
jvm/src/sqwl/cms/Server.scala
jvm/src/sqwl/cms/Service.scala
jvm/src/sqwl/cms/UrlScheme.scala
jvm/src/sqwl/cms/config.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/resources/config.conf	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,7 @@
+config {
+  http {
+    interface = localhost
+    port = 8080
+    prefix = cms
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/resources/reference.conf	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,1 @@
+include "config.conf"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/Layout.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,37 @@
+package sqwl.cms
+
+import scalatags.Text.all._
+import scalatags.Text.tags2
+import scalatags.Text.TypedTag
+
+object Layout {
+  def apply(content: iContent, st: ViewState): TypedTag[String] = {
+    html(
+      head(
+        tags2.title(st match {
+          case ViewArticle(v) => v.title
+          case ViewCategory(v) => v.name
+          case ViewTag(v) => v.name
+          case Dashboard => "SQWL"
+          case News => "Novinky"
+        })
+
+      ),
+      body(
+        div("foo"),
+        st match {
+          case ViewArticle(article) => div(
+            a(href:=article.pathSegment, article.title),
+            raw(article.htmlContent)
+          )
+          case _ => ""
+        },
+        div(
+          content.categories map(c => div(a(href:=c.pathSegment, c.name)))
+        )
+
+      )
+    )
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/MD5.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,10 @@
+package sqwl.cms
+
+import java.security.MessageDigest
+
+object MD5 {
+  private val md5 = MessageDigest.getInstance("MD5")
+
+  def apply(value: String): String =
+    md5.digest(value.getBytes("UTF-8")).map("%02x".format(_)).mkString
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/Server.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,58 @@
+package sqwl.cms
+
+import akka.actor.ActorSystem
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.model.headers.{ETag, EntityTag}
+import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpHeader, HttpResponse}
+import akka.http.scaladsl.server.Directives._
+import akka.stream.{ActorMaterializer, Materializer}
+
+import scala.collection.immutable
+import scala.concurrent.ExecutionContextExecutor
+import scala.io.StdIn
+
+trait Server extends App with Service with config with UrlScheme {
+  override implicit val system: ActorSystem = ActorSystem()
+  override implicit val executor: ExecutionContextExecutor = system.dispatcher
+  override implicit val materializer: Materializer = ActorMaterializer()
+
+  protected def content: iContent
+
+  val asArticle = Segment.flatMap(content.articleByPath(_))
+  val asCategory = Segment.flatMap(content.categoryByPath(_))
+  val asTag = Segment.flatMap(content.tagByPath(_))
+
+  private def asHtml(st: ViewState): HttpResponse = {
+    val s = Layout(content, st).render
+    HttpResponse(
+      headers = immutable.Seq[HttpHeader](ETag(MD5(s))),
+      entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
+      "<!DOCTYPE html>" + s))
+  }
+
+  private val routes = get {
+    pathPrefix(http.prefix) {
+      pathEnd {
+        complete(asHtml(Dashboard))
+      } ~ pathPrefix(asArticle) { article =>
+        pathEnd {
+          complete(asHtml(ViewArticle(article)))
+        } ~ getFromDirectory(article.assets.toString)
+      } ~ path(asCategory) { category =>
+        complete(asHtml(ViewCategory(category)))
+      } ~ path(TAG / asTag) { tag =>
+        complete(asHtml(ViewTag(tag)))
+      }
+    } ~ pathPrefix(http.prefix / ASSETS) {
+      getFromResourceDirectory("META-INF/resources/webjars")
+    } ~ pathPrefix(http.prefix / PUBLIC) {
+      getFromResourceDirectory("public")
+    }
+  }
+
+  Http().bindAndHandle(routes, http.interface, http.port)
+  system.log.info("Click `Enter` to close application...")
+  StdIn.readLine()
+  system.terminate()
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/Service.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,12 @@
+package sqwl.cms
+
+import akka.actor.ActorSystem
+import akka.stream.Materializer
+
+import scala.concurrent.ExecutionContextExecutor
+
+trait Service {
+  implicit val system: ActorSystem
+  implicit def executor: ExecutionContextExecutor
+  implicit val materializer: Materializer
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/UrlScheme.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,8 @@
+package sqwl.cms
+
+trait UrlScheme {
+  val ASSETS = "assets"
+  val PUBLIC = "public"
+  val TAG = "tag"
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/config.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,13 @@
+package sqwl.cms
+
+import com.wacai.config.annotation.conf
+
+@conf trait config {
+
+  final val http = new {
+    val interface = "localhost"
+    val port = 8080
+    val prefix = "cms"
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/datamodel.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,33 @@
+package sqwl.cms
+
+import java.nio.file.Path
+
+trait Named {
+  def name: String
+}
+
+trait Navigable {
+  def pathSegment: String
+}
+
+trait iCategory extends Named with Navigable
+
+trait iTag extends Named with Navigable
+
+trait iArticle extends Navigable {
+  def title: String
+  def htmlContent: String
+  def category: Option[iCategory]
+  def tags: Seq[iTag]
+  def assets: Path
+}
+
+trait iContent {
+  def articlesByTag(t: iTag): Seq[iArticle]
+  def articlesByCategory(c: iCategory): Seq[iArticle]
+  def tags: Seq[iTag]
+  def categories: Seq[iCategory]
+  def articleByPath(path: String): Option[iArticle]
+  def categoryByPath(path: String): Option[iCategory]
+  def tagByPath(path: String): Option[iTag]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/viewstates.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,9 @@
+package sqwl.cms
+
+sealed trait ViewState
+
+case object Dashboard extends ViewState
+case object News extends ViewState
+case class ViewArticle(article: iArticle) extends ViewState
+case class ViewCategory(category: iCategory) extends ViewState
+case class ViewTag(tag: iTag) extends ViewState
--- a/build.sc	Thu Nov 22 13:15:29 2018 +0100
+++ b/build.sc	Thu Nov 29 12:20:20 2018 +0100
@@ -55,7 +55,7 @@
   )}
 }
 
-object jvm extends Common {
+object base extends Common {
   override def scalacPluginIvyDeps: Target[Loose.Agg[Dep]] = super.scalacPluginIvyDeps() ++ Agg(
     ivy"org.scalamacros:::paradise:2.1.0"
   )
@@ -72,11 +72,34 @@
   )
 
   override def scalacOptions = T{super.scalacOptions.map(_ :+
-    "-Xmacro-settings:conf.output.dir=jvm/resources"
+    "-Xmacro-settings:conf.output.dir=base/resources"
   )}
 
-  override def mainClass = Some("sqwl.cms.Server")
+}
+
+object content extends Common {
+  override def moduleDeps = Seq(base)
+  override def sources : Sources = T.sources{ millSourcePath / up / 'example / 'src }
+  override def ivyDeps = Agg(
+    ivy"com.beachape::enumeratum:1.5.13"
+  )
+}
 
+object app extends Common {
+  override def moduleDeps = Seq(content)
+  override def mainClass = Some("sqwl.cms.Main")
+
+  override def generatedSources: Target[Seq[PathRef]] = T{
+    val dir = T.ctx().dest
+    write(dir / "main.scala",
+      """
+        | package sqwl.cms
+        | object Main extends Server {
+        |   override def content: iContent = Content
+        | }
+      """.stripMargin)
+    Seq(PathRef(dir))
+  }
 }
 
 object js extends Common with ScalaJSModule {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example/content/a1.html	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,3 @@
+<div>Article 1</div>
+<p>Lorem ipsum...</div>
+<a href="a1/test.txt">test.txt</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example/content/a1/test.txt	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,1 @@
+Test asset.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example/src/sqwl/cms/Content.scala	Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,56 @@
+
+package sqwl.cms
+
+import java.nio.file.{FileSystems, Path, Paths}
+
+import akka.stream.scaladsl.FileIO
+import enumeratum.EnumEntry.Hyphencase
+import enumeratum._
+
+import scala.io.Source
+
+sealed abstract class Category(val name: String) extends EnumEntry with iCategory with Hyphencase {
+  override def pathSegment: String = entryName
+}
+
+object Category extends Enum[Category] {
+  val values = findValues
+  case object Cat1 extends Category("Category 1")
+  case object Cat2 extends Category("Category 2")
+}
+
+sealed abstract class Article(val title: String,
+  val category: Option[iCategory],
+  val tags: Seq[iTag],
+
+) extends EnumEntry with iArticle with Hyphencase {
+  override def htmlContent: String = {
+    val src = Source.fromFile(s"example/content/${entryName}.html", "UTF-8")
+    try {
+      src.getLines.mkString
+    } catch {
+      case _: Throwable => ""
+    } finally {
+      src.close()
+    }
+  }
+
+  override def assets: Path = Paths.get(s"example/content/${entryName}")
+  override def pathSegment: String = entryName
+}
+
+object Article extends Enum[Article] {
+  import Category._
+  val values = findValues
+  case object A1 extends Article("Article 1", Some(Cat1), Seq())
+}
+
+object Content extends iContent {
+  def articlesByTag(t: iTag): Seq[iArticle] = Seq()
+  def articlesByCategory(c: iCategory): Seq[iArticle] = Seq()
+  def tags: Seq[iTag] = Seq()
+  def categories: Seq[iCategory] = Category.values
+  def articleByPath(path: String): Option[iArticle] = Article.withNameOption(path)
+  def categoryByPath(path: String): Option[iCategory] = Category.withNameOption(path)
+  def tagByPath(path: String): Option[iTag] = None
+}
\ No newline at end of file
--- a/jvm/resources/config.conf	Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-config {
-  http {
-    interface = localhost
-    port = 8080
-    prefix = cms
-  }
-}
--- a/jvm/resources/reference.conf	Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-include "config.conf"
\ No newline at end of file
--- a/jvm/src/sqwl/cms/Server.scala	Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-package sqwl.cms
-
-import akka.actor.ActorSystem
-import akka.http.scaladsl.Http
-import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
-import akka.http.scaladsl.server.Directives._
-import akka.stream.{ActorMaterializer, Materializer}
-
-import scala.concurrent.ExecutionContextExecutor
-
-object Server extends App with Service with config with UrlScheme {
-  override implicit val system: ActorSystem = ActorSystem()
-  override implicit val executor: ExecutionContextExecutor = system.dispatcher
-  override implicit val materializer: Materializer = ActorMaterializer()
-
-  private val routes = get {
-    path(http.prefix) {
-      complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "main"))
-    } ~ pathPrefix(http.prefix / ASSETS) {
-      getFromResourceDirectory("META-INF/resources/webjars")
-    } ~ pathPrefix(http.prefix / PUBLIC) {
-      getFromResourceDirectory("public")
-    }
-  }
-
-  Http().bindAndHandle(routes, http.interface, http.port)
-
-}
--- a/jvm/src/sqwl/cms/Service.scala	Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-package sqwl.cms
-
-import akka.actor.ActorSystem
-import akka.stream.Materializer
-
-import scala.concurrent.ExecutionContextExecutor
-
-trait Service {
-  implicit val system: ActorSystem
-  implicit def executor: ExecutionContextExecutor
-  implicit val materializer: Materializer
-}
--- a/jvm/src/sqwl/cms/UrlScheme.scala	Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-package sqwl.cms
-
-trait UrlScheme {
-  val ASSETS = "assets"
-  val PUBLIC = "public"
-
-}
--- a/jvm/src/sqwl/cms/config.scala	Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-package sqwl.cms
-
-import com.wacai.config.annotation.conf
-
-@conf trait config {
-
-  final val http = new {
-    val interface = "localhost"
-    val port = 8080
-    val prefix = "cms"
-  }
-
-}