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 |