This small script exists only to render a HTML page for the various yaks you are working on.
scala-cli run yak-render.scala -- sample-projects.yml > index.html
Copy and adapt for your needs.
My personal page is huge:
This small script exists only to render a HTML page for the various yaks you are working on.
scala-cli run yak-render.scala -- sample-projects.yml > index.html
Copy and adapt for your needs.
My personal page is huge:
categories: | |
- name: experiments | |
description: Code thrown together to prove something works (or doesn't) | |
css: "bg-red-100 border-2 border-gray-300 hover:underline" | |
- name: publications | |
description: An application or library polished enough to be published | |
css: "bg-sky-100 border-2 border-gray-300 hover:underline" | |
projects: | |
- name: Scala 3 | |
category: publications | |
priority: high | |
url: "gh:scala/scala3" | |
id: scala3 | |
- name: Scala CLI | |
category: experiments | |
priority: medium | |
url: "gh:VirtusLab/scala-cli" | |
references: [scala3] | |
- name: Scala CLI template | |
category: publications | |
priority: low | |
url: "gh:VirtusLab/scala-cli-template" | |
references: [scala3] |
//> using dep org.virtuslab::scala-yaml:0.1.0 | |
//> using dep com.lihaoyi::scalatags::0.13.1 | |
//> using dep com.lihaoyi::os-lib::0.10.2 | |
//> using option -Wunused:all | |
//> using scala "3.5.0-RC2" | |
import org.virtuslab.yaml.* | |
import scala.util.CommandLineParser.FromString | |
given FromString[os.Path] with | |
override def fromString(s: String): os.Path = os.Path(s, os.pwd) | |
case class Defs( | |
categories: List[Category], | |
projects: List[Project] | |
) derives YamlDecoder | |
case class Category(name: String, description: String, css: String) | |
derives YamlDecoder | |
case class Project( | |
id: Option[String], | |
name: String, | |
category: String, | |
priority: Priority, | |
url: Option[YouErEl], | |
references: Option[List[String]] | |
) derives YamlDecoder | |
enum YouErEl: | |
case Gh(coords: String) | |
case Normal(coords: String) | |
override def toString(): String = | |
this match | |
case Gh(coords) => s"https://github.com/$coords" | |
case Normal(s) => s | |
object YouErEl: | |
def fromString(s: String) = | |
s match | |
case s"gh:$org/$repo" => YouErEl.Gh(s"$org/$repo") | |
case s"https://$coords" => YouErEl.Normal(s) | |
enum Priority: | |
case High, Low, Medium | |
given YamlDecoder[YouErEl] = YamlDecoder.forString.map(YouErEl.fromString(_)) | |
given YamlDecoder[Priority] = summon[YamlDecoder[String]] | |
.map(_.toLowerCase().trim) | |
.map: | |
case "low" => Priority.Low | |
case "high" => Priority.High | |
case "medium" => Priority.Medium | |
given YamlEncoder[Priority] with | |
def asNode(obj: Priority): Node = | |
Node.ScalarNode(obj.toString().toLowerCase()) | |
@main def readProjects(file: os.Path) = | |
val decoded = os.read(file).as[Defs].fold(throw _, identity) | |
given Resolver with | |
val mapping = decoded.categories.map(n => n.name -> n).toMap | |
val projectMapping = decoded.projects.flatMap(p => p.id.map(_ -> p)).toMap | |
override def category(name: String): Category = mapping(name) | |
override def project(id: String): Project = projectMapping(id) | |
println(templates.layout(decoded).render) | |
end readProjects | |
trait Resolver: | |
def category(name: String): Category | |
def project(id: String): Project | |
object templates: | |
def layout(defs: Defs)(using Resolver) = | |
import scalatags.Text.all.* | |
import Priority.* | |
def render(priority: Priority) = | |
div( | |
cls := "flex flex-col gap-4 shrink-0 w-4/12", | |
defs.projects.filter(_.priority == priority).map(projectCard) | |
) | |
def renderCategories() = | |
div( | |
cls := "grid grid-cols-2 gap-2", | |
defs.categories.map: category => | |
div( | |
span( | |
cls := s"text-md p-2 rounded-md inline-block ${category.css}", | |
category.name | |
), | |
s" ${category.description}" | |
) | |
) | |
html( | |
lang := "en", | |
head( | |
tag("title")("Projects"), | |
script(src := "https://cdn.tailwindcss.com"), | |
meta(charset := "UTF-8"), | |
meta( | |
name := "viewport", | |
attr("content") := "width=device-width, initial-scale=1" | |
) | |
), | |
body( | |
div( | |
cls := "content mx-auto w-10/12 m-8", | |
h2("Projects", cls := "text-4xl"), | |
h3("Agenda", cls := "text-2xl my-4"), | |
renderCategories(), | |
h3("Projects", cls := "text-2xl my-4"), | |
div( | |
cls := "flex flex-row gap-8 w-full", | |
render(Priority.High), | |
render(Medium), | |
render(Low) | |
) | |
) | |
) | |
) | |
end layout | |
def projectCard(project: Project)(using resolver: Resolver) = | |
import scalatags.Text.all.* | |
val fontSize = project.priority match | |
case Priority.High => "text-lg" | |
case Priority.Low => "text-sm" | |
case Priority.Medium => "text-md" | |
val category = resolver.category(project.category) | |
def intersperseList[A](xs: List[A], x: A): List[A] = | |
val bld = List.newBuilder[A] | |
val it = xs.iterator | |
if it.hasNext then | |
bld += it.next | |
while it.hasNext do | |
bld += x | |
bld += it.next | |
bld.result | |
end intersperseList | |
val references = | |
project.references.map(value => | |
val us = value | |
.map(resolver.project(_)) | |
.map: proj => | |
proj.url match | |
case None => span(proj.name) | |
case Some(value) => | |
a( | |
href := value.toString(), | |
proj.name, | |
cls := "underline hover:no-underline" | |
) | |
p(cls := "text-sm ml-4", "built with ", intersperseList(us, span(", "))) | |
) | |
div( | |
cls := s"$fontSize", | |
p( | |
cls := s"p-2 rounded-md ${category.css}", | |
project.url match | |
case None => b(project.name) | |
case Some(value) => | |
a(b(project.name), href := value.toString()) | |
), | |
references | |
) | |
end projectCard | |
end templates |