Newer
Older
iw-utils / Laminarize.scala
//> using lib "org.jsoup:jsoup:1.14.3"
//> using lib "dev.zio::zio:2.0.0-RC2"
//> using lib "dev.zio::zio-streams:2.0.0-RC2"
//
package works.iterative.tailwind.util

import zio.Task
import org.jsoup.nodes.Document
import org.jsoup.Jsoup
import org.jsoup.select.NodeVisitor
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
import scala.jdk.CollectionConverters._
import org.jsoup.nodes.TextNode
import zio.ZIOAppDefault
import zio.ZIO
import zio.stream.ZStream
import java.io.EOFException

object Laminarize extends ZIOAppDefault:

  class LaminarNodeVisitor() extends NodeVisitor:
    private val code: java.lang.StringBuilder = new java.lang.StringBuilder
    private val attrMap: Map[String, String] =
      Map("class" -> "cls", "type" -> "tpe").withDefault(identity)
    override def head(node: Node, depth: Int): Unit =
      node match {
        case el: Element =>
          if (depth > 0) code.append(",\n")
          code.append("\t" * depth)
          writeElement(el, depth)
        case t: TextNode if !t.text.isBlank =>
          if (depth > 0) code.append(",\n")
          code.append("\t" * depth)
          writeTripleQuoted(t.text)
        case _ => ()
      }
    override def tail(node: Node, depth: Int): Unit =
      node match {
        case el: Element => code.append("\t" * depth).append("\n)")
        case _           => ()
      }
    def getCode(): String = code.toString()
    private def writeQuoted(t: String): Unit =
      code.append('"').append(t.replaceAll("\"", "\\\"")).append('"')
    private def writeTripleQuoted(t: String): Unit =
      code.append(""""""""").append(t).append(""""""""")
    private def writeElement(el: Element, depth: Int): Unit =
      code.append(el.normalName).append("(\n")
      val attrs = el.attributes.asList.asScala
      attrs.zipWithIndex.foreach { (attribute, idx) =>
        if (idx != 0) code.append(",\n")
        code.append("\t" * (depth + 1))
        code
          .append(attrMap(attribute.getKey))
          .append(" := ")
        writeQuoted(attribute.getValue)
      }

  def elementToLaminar(el: Element): String =
    val visitor = LaminarNodeVisitor()
    el.children.traverse(visitor)
    visitor.getCode()

  def htmlToLaminar(html: String): Task[String] =
    for {
      doc <- Task.attempt(Jsoup.parseBodyFragment(html))
    } yield elementToLaminar(doc.body)

  override def run =
    for {
      console <- ZIO.environment[zio.Console]
      input <- ZStream
        .repeatZIO(console.get.readLine.asSome.catchSome {
          case _: EOFException =>
            ZIO.succeed(None)
        })
        .takeWhile(_.isDefined)
        .map(_.get)
        .fold("")((a, s) => a + "\n" + s)
      code <- htmlToLaminar(input)
      _ <- console.get.printLine(code)
    } yield ()