diff --git a/.gitignore b/.gitignore index 3bf2ca9..2dbff54 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ *.semanticdb /.direnv/ /.scala/ +/.scala-build/ diff --git a/.gitignore b/.gitignore index 3bf2ca9..2dbff54 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ *.semanticdb /.direnv/ /.scala/ +/.scala-build/ diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..22bfe61 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.0.5" +runner.dialect = scala3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3bf2ca9..2dbff54 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ *.semanticdb /.direnv/ /.scala/ +/.scala-build/ diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..22bfe61 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.0.5" +runner.dialect = scala3 \ No newline at end of file diff --git a/Laminarize.scala b/Laminarize.scala new file mode 100644 index 0000000..4037dba --- /dev/null +++ b/Laminarize.scala @@ -0,0 +1,83 @@ +//> 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 () diff --git a/.gitignore b/.gitignore index 3bf2ca9..2dbff54 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ *.semanticdb /.direnv/ /.scala/ +/.scala-build/ diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..22bfe61 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.0.5" +runner.dialect = scala3 \ No newline at end of file diff --git a/Laminarize.scala b/Laminarize.scala new file mode 100644 index 0000000..4037dba --- /dev/null +++ b/Laminarize.scala @@ -0,0 +1,83 @@ +//> 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 () diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..111fe10 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1644399594, + "narHash": "sha256-C4HuSc5NzBePk3OmanJID2S8QR5feQF0ADLp72rM/Gw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "033938349562abb4635df1f2a05959cfa8bc94d2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "master", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +}