src/Tools/Find_Facts/elm.scala
changeset 81764 fcba3250fb2a
child 81765 eb40020efda7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Tools/Find_Facts/elm.scala	Sat Jan 11 21:31:13 2025 +0100
@@ -0,0 +1,94 @@
+/*  Author:     Fabian Huch, TU Muenchen
+
+Elm module for Isabelle.
+*/
+
+package isabelle
+
+
+import java.io.{File => JFile}
+
+import scala.jdk.CollectionConverters._
+
+import org.jsoup.nodes.Element
+
+
+object Elm {
+  private lazy val elm_home =
+    proper_string(Isabelle_System.getenv("ISABELLE_ELM")).getOrElse(error("No elm component found"))
+
+  private lazy val exec = Path.explode(elm_home) + Path.basic("elm")
+
+  object Project {
+    def apply(
+      name: String,
+      dir: Path,
+      main: Path = Path.explode("src/Main.elm"),
+      output: Path = Path.explode("index.html"),
+      head: XML.Body = Nil
+    ): Project = {
+      if (!dir.is_dir) error("Project directory does not exist: " + dir)
+      val main_file = dir + main
+      if (!main_file.is_file) error("Main elm file does not exist: " + main_file)
+      new Project(name, dir, main, dir + output, head)
+    }
+  }
+
+  class Project private(name: String, dir: Path, main: Path, output: Path, head: XML.Body) {
+    val definition = JSON.parse(File.read(dir + Path.basic("elm.json")))
+    val src_dirs =
+      JSON.strings(definition, "source-directories").getOrElse(
+        error("Missing source directories in elm.json"))
+
+    def sources: List[JFile] =
+      for {
+        src_dir <- src_dirs
+        path = dir + Path.explode(src_dir)
+        file <- File.find_files(path.file, _.getName.endsWith(".elm"))
+      } yield file
+
+    def sources_shasum: SHA1.Shasum = {
+      val meta_info = SHA1.shasum_meta_info(SHA1.digest(JSON.Format(definition)))
+      val head_digest = SHA1.shasum(SHA1.digest(XML.string_of_body(head)), "head")
+      val source_digest =
+        SHA1.shasum_sorted(for (file <- sources) yield SHA1.digest(file) -> file.getCanonicalPath)
+      meta_info ::: head_digest ::: source_digest
+    }
+
+    def get_digest: SHA1.Digest =
+      Exn.capture {
+        val html = HTML.parse_document(File.read(output))
+        val elem = html.head.getElementsByTag("meta").attr("name", "shasum")
+        Library.the_single(elem.eachAttr("content").asScala.toList)
+      } match {
+        case Exn.Res(s) => SHA1.fake_digest(s)
+        case _ => SHA1.digest_empty
+      }
+
+    def build_html(progress: Progress): String = {
+      val digest = sources_shasum.digest
+      if (digest == get_digest) File.read(output)
+      else {
+        progress.echo("### Building " + name + " (" + output.canonical.implode + ") ...")
+
+        val cmd =
+          File.bash_path(exec) + " make " + File.bash_path(main) + " --optimize --output=" + output
+        val res = Isabelle_System.bash(cmd, cwd = dir)
+
+        if (!res.ok) {
+          progress.echo(res.err)
+          error("Failed to compile Elm sources")
+        }
+
+        val file = HTML.parse_document(File.read(output))
+        file.head.appendChild(
+          Element("meta").attr("name", "shasum").attr("content", digest.toString))
+        file.head.append(XML.string_of_body(head))
+        val html = file.html
+        File.write(output, html)
+
+        html
+      }
+    }
+  }
+}
\ No newline at end of file