Skip to content

Instantly share code, notes, and snippets.

@fokot
Last active December 15, 2023 23:11
Show Gist options
  • Save fokot/9ede8986562e90d566adefc5d44260b7 to your computer and use it in GitHub Desktop.
Save fokot/9ede8986562e90d566adefc5d44260b7 to your computer and use it in GitHub Desktop.
dependency tree to jstree
import java.io.{BufferedWriter, OutputStreamWriter}
import java.nio.file.{Files, Paths, StandardOpenOption}
import StandardOpenOption._
import scala.io.Source
import scala.util.Using
/**
* Generates a dependency tree in html format.
* `dependencyBrowseTree` fails on SO in our big project but `dependencyTree` works.
*
* Use it like:
*
* sbt dependencyTree | scala-cli DependencyJsTree.scala > dep.html
*
* Or you can also uncomment files version
*/
object DependencyJsTree {
def main(args: Array[String]): Unit = {
val root = args match {
case Array(r) => r
case _ => "com.cloudfarms:cloudfarms_2.13:1.3.0"
}
// val source = Source.fromFile("dep.txt")
val source = Source.stdin
// val target = Files.newBufferedWriter(Paths.get("dep.html"), CREATE, TRUNCATE_EXISTING)
val target = new BufferedWriter(new OutputStreamWriter(System.out))
generateHtml(root, source, target)
}
val pageStart =
"""<!doctype html>
|<html>
|<head
| <meta charset="utf-8">
| <title>dependencyBrowseTree</title>
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
| <script src="tree.data.js"></script>
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css"/>
|
| <style>
| #input-field {
| display: inline;
| }
| #tree_div {
| font-family: monospace
| }
| </style>
|</head>
|<body>
|<h1>Dependencies</h1>
|Search: <input type="search" id="input-field" value="">
|<div id="tree_div" class="jstree-no-icons"></div>
|
|<script>
|const tree_data =
""".stripMargin
val pageEnd =
"""$(function() {
| $('#tree_div').jstree({
| "plugins": ["search", "sort"],
| "core": {
| "data": tree_data
| }
| });
| var to = false;
| $('#input-field').keyup(function() {
| if (to) {
| clearTimeout(to);
| }
| to = setTimeout(function() {
| var v = $('#input-field').val();
| $('#tree_div').jstree(true).search(v);
| }, 250);
| });
|});
|</script>
|</body>
|</html>
|""".stripMargin
def generateHtml(root: String, source: Source, writer: BufferedWriter): Unit =
Using.resources(
source,
writer
) { (source, target) =>
target.write(pageStart)
target.write(s"""{ "text": "${root}", "children": [""")
val lastLevel = source.getLines().dropWhile(!_.contains(root)).drop(1).takeWhile(_.contains("[info]")).foldLeft(0)(process(target))
(0 to lastLevel).reverse.foreach { i =>
target.write(" " * i)
if (i == 0)
target.write("]}\n")
else
target.write("]},\n")
}
target.write(pageEnd)
}
def process(target: BufferedWriter)(previousLevel: Int, lineFull: String): Int = {
val line = lineFull.replace("[info]", "").trim
if (line.contains("+-")) {
val (levelString, name) = line.splitAt(line.indexOf("+-") + 2)
val level = levelString.count(_ == '|') + 1
if(level < previousLevel) {
(level to previousLevel).reverse.foreach { i =>
target.write(" " * i)
target.write("]},\n")
}
target.write(" " * level)
target.write(s"""{ "text": "$name", "children": [""")
} else if (level == previousLevel) {
target.write("]},\n")
target.write(" " * level)
target.write(s"""{ "text": "$name", "children": [""")
} else {
target.write("\n")
target.write(" " * level)
target.write(s"""{ "text": "$name", "children": [""")
}
level
} else
previousLevel
}
}
//> using scala "2.13.12"
//> using lib "dev.zio::zio:2.0.20"
//> using lib "dev.zio::zio-streams:2.0.20"
import zio.Console.{printLine, print}
import zio.{Chunk, Scope, ZIO, ZIOAppArgs, ZIOAppDefault}
import zio.stream.{ZPipeline, ZSink, ZStream}
import java.io.BufferedWriter
import java.nio.file.Paths
import scala.io.Source
import scala.util.Using
/**
* Generates a dependency tree in html format.
* `dependencyBrowseTree` fails on SO in our big project but `dependencyTree` works.
*
* Use it like:
*
* sbt dependencyTree | scala-cli DependencyJsTreeStreams.scala > dep.html
*
* Or you can also uncomment files version
*/
object DependencyJsTreeStreams extends ZIOAppDefault {
// val source = ZIO.attempt(Source.fromFile("dep.txt"))
val source = ZIO.succeed(Source.stdin)
// val target = ZPipeline.utf8Encode >>> ZSink.fromPath(Paths.get("dep.html"))
val target = ZSink.foreach((s: String) => printLine(s))
def run: ZIO[ZIOAppArgs with Scope, Any, Any] =
getArgs.map {
case Chunk(r) => r
case _ => "com.cloudfarms:cloudfarms_2.13:1.3.0"
}.flatMap { root =>
val linesAndLevelsDependecies = ZStream.scoped(ZIO.fromAutoCloseable(source))
.flatMap(source => ZStream.fromIterator(source.getLines()))
.dropUntil(_.contains(root))
.filter(_.contains("+-"))
.map(levelAndName)
val linesAndLevels = ZStream.succeed((0, root)) ++ linesAndLevelsDependecies ++ ZStream.succeed((0, ""))
val dependenciesJson = linesAndLevels.sliding(2)
.flatMap(a => lineToStream(a(0)._1, a(1)._1, a(0)._2))
.intersperse("\n")
val result = ZStream.succeed(pageStart) ++ dependenciesJson ++ ZStream.succeed("\n") ++ ZStream.succeed(pageEnd)
result.run(target)
}
def levelAndName(line: String): (Int, String) = {
val (levelString, name) = line.splitAt(line.indexOf("+-") + 2)
val level = levelString.count(_ == '|') + 1
(level, name)
}
def lineToStream(currentLevel: Int, nextLevel: Int, text: String): ZStream[Any, Nothing, String] =
if (currentLevel < nextLevel)
ZStream.succeed(" " * currentLevel ++ s"""{ "text": "$text", "children": [""")
else if (currentLevel == nextLevel)
ZStream.succeed(" " * currentLevel ++ s"""{ "text": "$text", "children": [] },""")
else
ZStream.succeed(" " * currentLevel ++ s"""{ "text": "$text", "children": [] }""") ++
ZStream.fromIterable((nextLevel until currentLevel).reverse.map(i => " " * i + "]}" ++ (if (i == 0) "" else ",")))
val pageStart =
"""<!doctype html>
|<html>
|<head
| <meta charset="utf-8">
| <title>dependencyBrowseTree</title>
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
| <script src="tree.data.js"></script>
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css"/>
|
| <style>
| #input-field {
| display: inline;
| }
| #tree_div {
| font-family: monospace
| }
| </style>
|</head>
|<body>
|<h1>Dependencies</h1>
|Search: <input type="search" id="input-field" value="">
|<div id="tree_div" class="jstree-no-icons"></div>
|
|<script>
|const tree_data =
""".stripMargin
val pageEnd =
"""$(function() {
| $('#tree_div').jstree({
| "plugins": ["search", "sort"],
| "core": {
| "data": tree_data
| }
| });
| var to = false;
| $('#input-field').keyup(function() {
| if (to) {
| clearTimeout(to);
| }
| to = setTimeout(function() {
| var v = $('#input-field').val();
| $('#tree_div').jstree(true).search(v);
| }, 250);
| });
|});
|</script>
|</body>
|</html>
|""".stripMargin
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment