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.

Revisions

  1. keynmol revised this gist Jul 5, 2024. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -4,4 +4,6 @@ This small script exists only to render a HTML page for the various yaks you are

    ![CleanShot 2024-07-05 at 16 37 38](https://gist.github.com/assets/1052965/f9f44e14-dac0-4939-8ca8-5b0d6ed70ca0)

    Copy and adapt for your needs.
    Copy and adapt for your needs.

    My [personal page](https://blog.indoorvivants.com/projects) is huge: ![image](https://gist.github.com/assets/1052965/862c8f80-7723-4fae-a893-d0a80a831644)
  2. keynmol created this gist Jul 5, 2024.
    7 changes: 7 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    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](https://gist.github.com/assets/1052965/f9f44e14-dac0-4939-8ca8-5b0d6ed70ca0)

    Copy and adapt for your needs.
    27 changes: 27 additions & 0 deletions sample-projects.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    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]
    180 changes: 180 additions & 0 deletions yak-render.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,180 @@
    //> 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