mirror of https://github.com/sbt/sbt.git
commit
67b9ab0a68
|
|
@ -1,57 +1,73 @@
|
|||
package coursier.util
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object Tree {
|
||||
|
||||
def apply[T](roots: IndexedSeq[T])(children: T => Seq[T], print: T => String): String = {
|
||||
/**
|
||||
* Add elements to the stack
|
||||
* @param stack a mutable stack which will have elements
|
||||
* @param elems elements to add
|
||||
* @param isLast a list that contains whether an element is the last in its siblings or not.
|
||||
* The first element is the deepest, due to the fact prepending a List is faster than appending
|
||||
*/
|
||||
def push(stack: mutable.Stack[(T, List[Boolean])], elems: Seq[T], isLast: List[Boolean]) = {
|
||||
// Reverse the list because the stack is LIFO but elems must be shown in the order
|
||||
for ((x, idx) <- elems.zipWithIndex.reverse) {
|
||||
stack.push((x, (idx == elems.length - 1) :: isLast))
|
||||
}
|
||||
def apply[T](roots: IndexedSeq[T])(children: T => Seq[T], show: T => String): String = {
|
||||
|
||||
val buffer = new StringBuilder
|
||||
val printLine: (String) => Unit = { line =>
|
||||
buffer.append(line).append('\n')
|
||||
}
|
||||
|
||||
def prefix(isLast: List[Boolean]): String = {
|
||||
// Reverse the list because its first element is the deepest element
|
||||
isLast.reverse.zipWithIndex.map {
|
||||
case (last, idx) =>
|
||||
if (idx == isLast.length - 1)
|
||||
if (last) "└─ " else "├─ "
|
||||
else
|
||||
if (last) " " else "| "
|
||||
}.mkString("")
|
||||
def last[E, O](seq: Seq[E])(f: E => O) =
|
||||
seq.takeRight(1).map(f)
|
||||
def init[E, O](seq: Seq[E])(f: E => O) =
|
||||
seq.dropRight(1).map(f)
|
||||
|
||||
/**
|
||||
* Add elements to the stack
|
||||
* @param elems elements to add
|
||||
* @param isLast a list that contains whether an element is the last in its siblings or not.
|
||||
*/
|
||||
def childrenWithLast(elems: Seq[T],
|
||||
isLast: Seq[Boolean]): Seq[(T, Seq[Boolean])] = {
|
||||
|
||||
val isNotLast = isLast :+ false
|
||||
|
||||
init(elems)(_ -> isNotLast) ++
|
||||
last(elems)(_ -> (isLast :+ true))
|
||||
}
|
||||
|
||||
/**
|
||||
* Has to end with a "─"
|
||||
*/
|
||||
def showLine(isLast: Seq[Boolean]): String = {
|
||||
val initPrefix = init(isLast) {
|
||||
case true => " "
|
||||
case false => "| "
|
||||
}.mkString
|
||||
|
||||
val lastPrefix = last(isLast) {
|
||||
case true => "└─ "
|
||||
case false => "├─ "
|
||||
}.mkString
|
||||
|
||||
initPrefix + lastPrefix
|
||||
}
|
||||
|
||||
// Depth-first traverse
|
||||
def helper(elems: Seq[T], acc: String => Unit): Unit = {
|
||||
val stack = new mutable.Stack[(T, List[Boolean])]()
|
||||
val seen = new mutable.HashSet[T]()
|
||||
@tailrec
|
||||
def helper(stack: Seq[(T, Seq[Boolean])], seen: Set[T]): Unit = {
|
||||
stack match {
|
||||
case (elem, isLast) +: next =>
|
||||
printLine(showLine(isLast) + show(elem))
|
||||
|
||||
push(stack, elems, List[Boolean]())
|
||||
|
||||
while (stack.nonEmpty) {
|
||||
val (elem, isLast) = stack.pop()
|
||||
acc(prefix(isLast) + print(elem))
|
||||
|
||||
if (! seen.contains(elem)) {
|
||||
push(stack, children(elem), isLast)
|
||||
seen.add(elem)
|
||||
}
|
||||
if (!seen(elem))
|
||||
helper(childrenWithLast(children(elem), isLast) ++ next,
|
||||
seen + elem)
|
||||
else
|
||||
helper(next, seen)
|
||||
case Seq() =>
|
||||
}
|
||||
}
|
||||
|
||||
val b = new ArrayBuffer[String]
|
||||
helper(roots, b += _)
|
||||
b.mkString("\n")
|
||||
helper(childrenWithLast(roots, Vector[Boolean]()), Set.empty)
|
||||
|
||||
buffer
|
||||
.dropRight(1) // drop last appended '\n'
|
||||
.toString
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
package coursier
|
||||
package test
|
||||
|
||||
import coursier.util.{Tree, Xml}
|
||||
import utest._
|
||||
|
||||
object TreeTests extends TestSuite {
|
||||
private def tree(str: String, xs: Array[Xml.Node] = Array()): Xml.Node = {
|
||||
new Xml.Node {
|
||||
override def label: String = str
|
||||
override def children = xs
|
||||
|
||||
override def attributes: Seq[(String, String, String)] = Array[(String, String, String)]()
|
||||
override def isText: Boolean = false
|
||||
override def textContent: String = ""
|
||||
override def isElement: Boolean = true
|
||||
}
|
||||
}
|
||||
|
||||
val roots = Array(
|
||||
tree("p1", Array(tree("c1"), tree("c2"))),
|
||||
tree("p2", Array(tree("c3"), tree("c4"))))
|
||||
|
||||
val tests = TestSuite {
|
||||
'apply {
|
||||
val str = Tree(roots)(_.children, _.label)
|
||||
assert(str == """├─ p1
|
||||
|| ├─ c1
|
||||
|| └─ c2
|
||||
|└─ p2
|
||||
| ├─ c3
|
||||
| └─ c4""".stripMargin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package coursier.util
|
||||
|
||||
import utest._
|
||||
|
||||
object TreeTests extends TestSuite {
|
||||
case class Node(label: String, children: Node*)
|
||||
|
||||
val roots = Array(
|
||||
Node("p1",
|
||||
Node("c1"),
|
||||
Node("c2")),
|
||||
Node("p2",
|
||||
Node("c3"),
|
||||
Node("c4"))
|
||||
)
|
||||
|
||||
val tests = TestSuite {
|
||||
'apply {
|
||||
val str = Tree[Node](roots)(_.children, _.label)
|
||||
assert(str == """├─ p1
|
||||
|| ├─ c1
|
||||
|| └─ c2
|
||||
|└─ p2
|
||||
| ├─ c3
|
||||
| └─ c4""".stripMargin)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue