Skip to content

Instantly share code, notes, and snippets.

@keynmol
Last active July 5, 2024 15:45
Show Gist options
  • Save keynmol/5c277dd7cca8b6559b7a111fae772265 to your computer and use it in GitHub Desktop.
Save keynmol/5c277dd7cca8b6559b7a111fae772265 to your computer and use it in GitHub Desktop.
Render a yak shaving page to keep track of your side projects

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

CleanShot 2024-07-05 at 16 37 38

Copy and adapt for your needs.

My personal page is huge: image

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment