# HG changeset patch # User Tomas Zeman # Date 1543490420 -3600 # Node ID 1a1347e8c5bea7ee2c56a16ee1e7fac014b9be5d # Parent 48479e4b89d4d878254be32ff4a7a2e280dd1443 Build modules: base, content, app; example content, minimal layout diff -r 48479e4b89d4 -r 1a1347e8c5be base/resources/config.conf --- /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 + } +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/resources/reference.conf --- /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 diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/Layout.scala --- /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))) + ) + + ) + ) + } + +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/MD5.scala --- /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 diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/Server.scala --- /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)`, + "" + 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() + +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/Service.scala --- /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 +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/UrlScheme.scala --- /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" + +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/config.scala --- /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" + } + +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/datamodel.scala --- /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] +} diff -r 48479e4b89d4 -r 1a1347e8c5be base/src/sqwl/cms/viewstates.scala --- /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 diff -r 48479e4b89d4 -r 1a1347e8c5be build.sc --- 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 { diff -r 48479e4b89d4 -r 1a1347e8c5be example/content/a1.html --- /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 @@ +
Article 1
+

Lorem ipsum... +test.txt diff -r 48479e4b89d4 -r 1a1347e8c5be example/content/a1/test.txt --- /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. diff -r 48479e4b89d4 -r 1a1347e8c5be example/src/sqwl/cms/Content.scala --- /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 diff -r 48479e4b89d4 -r 1a1347e8c5be jvm/resources/config.conf --- 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 - } -} diff -r 48479e4b89d4 -r 1a1347e8c5be jvm/resources/reference.conf --- 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 diff -r 48479e4b89d4 -r 1a1347e8c5be jvm/src/sqwl/cms/Server.scala --- 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) - -} diff -r 48479e4b89d4 -r 1a1347e8c5be jvm/src/sqwl/cms/Service.scala --- 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 -} diff -r 48479e4b89d4 -r 1a1347e8c5be jvm/src/sqwl/cms/UrlScheme.scala --- 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" - -} diff -r 48479e4b89d4 -r 1a1347e8c5be jvm/src/sqwl/cms/config.scala --- 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" - } - -}