Skip to content

Commit

Permalink
help util: replace jtextutils with asciitable (#145)
Browse files Browse the repository at this point in the history
* help util: replace jtextutils with asciitable

* main motivation: jtextutils became vulnerable because the domain
`com.massisframework` is available for sale...
* also: asciitable is better
* includes latest tweakments from overflowdb as of v1.186

* fix tests

* fmt
  • Loading branch information
mpollmeier authored Feb 12, 2024
1 parent da3af3d commit 12b9197
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 32 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ lazy val core = project
"org.slf4j" % "slf4j-api" % slf4jVersion,

// for doc/help
"com.massisframework" % "j-text-utils" % "0.3.4",
"de.vandermeer" % "asciitable" % "0.3.2",
"net.oneandone.reflections8" % "reflections8" % "0.11.7",
)
)
Expand Down
54 changes: 38 additions & 16 deletions core/src/main/scala/flatgraph/help/Table.scala
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
package flatgraph.help

import dnl.utils.text.table.TextTable
import java.io.{ByteArrayOutputStream, PrintStream}
import java.nio.charset.StandardCharsets
import scala.util.Using

case class Table(columnNames: Iterable[String], rows: Iterable[Iterable[String]]) {

lazy val render: String = {
Using.Manager { use =>
val charset = StandardCharsets.UTF_8
val baos = use(new ByteArrayOutputStream)
val ps = use(new PrintStream(baos, true, charset.name))
val rowsAsArray = rows.map(_.map(_ + " ").toArray.asInstanceOf[Array[Object]]).toArray
new TextTable(columnNames.toArray, rowsAsArray).printTable(ps, 0)
new String(baos.toByteArray, charset)
}.get
import de.vandermeer.asciitable.AsciiTable
import de.vandermeer.asciithemes.TA_GridThemes
import de.vandermeer.skb.interfaces.transformers.textformat.TextAlignment
import scala.jdk.CollectionConverters.SeqHasAsJava

import Table.*

case class Table(columnNames: Seq[String], rows: Seq[Seq[String]]) {

def render(implicit availableWidthProvider: AvailableWidthProvider): String = {
if (columnNames.isEmpty && rows.isEmpty) {
""
} else {
val table = new AsciiTable()
table.addRule()
table.addRow(columnNames.asJava)
table.addRule()
if (rows.nonEmpty) {
rows.map(_.asJava).foreach(table.addRow)
}
table.addRule()
table.getContext.setGridTheme(TA_GridThemes.FULL)
table.setTextAlignment(TextAlignment.LEFT)

// some terminal emulators (e.g. on github actions CI) report to have a width of 0...
// that doesn't work for rendering a table, so we compensate by using a minimum width
val renderingWidth = math.max(availableWidthProvider.apply(), 60)
table.render(renderingWidth)
}
}

}

object Table {
trait AvailableWidthProvider extends (() => Int)

class ConstantWidth(width: Int) extends AvailableWidthProvider {
override def apply() = width
}

}
7 changes: 4 additions & 3 deletions core/src/main/scala/flatgraph/help/TraversalHelp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package flatgraph.help
import flatgraph.GNode
import flatgraph.help
import flatgraph.help.DocFinder.StepDoc
import flatgraph.help.Table.AvailableWidthProvider

import java.lang.annotation.Annotation as JAnnotation
import org.reflections8.Reflections
Expand All @@ -21,7 +22,7 @@ import scala.jdk.CollectionConverters.*
class TraversalHelp(packageNamesToSearch: DocSearchPackages) {
import TraversalHelp._

def forElementSpecificSteps(elementClass: Class[_], verbose: Boolean): String = {
def forElementSpecificSteps(elementClass: Class[_], verbose: Boolean)(implicit availableWidthProvider: AvailableWidthProvider): String = {
val isNode = classOf[GNode].isAssignableFrom(elementClass)

val stepDocs = {
Expand Down Expand Up @@ -51,7 +52,7 @@ class TraversalHelp(packageNamesToSearch: DocSearchPackages) {
|""".stripMargin
}

def forTraversalSources(verbose: Boolean): String = {
def forTraversalSources(verbose: Boolean)(implicit availableWidthProvider: AvailableWidthProvider): String = {
val stepDocs = for {
packageName <- packageNamesToSearch()
traversal <- findClassesAnnotatedWith(packageName, classOf[help.TraversalSource])
Expand Down Expand Up @@ -106,5 +107,5 @@ class TraversalHelp(packageNamesToSearch: DocSearchPackages) {
}

object TraversalHelp {
private val ColumnNames = Array("step", "description")
private val ColumnNames = Seq("step", "description")
}
13 changes: 11 additions & 2 deletions core/src/main/scala/flatgraph/traversal/Language.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package flatgraph.traversal

import flatgraph.help.{Doc, DocSearchPackages, Traversal, TraversalHelp}
import flatgraph.help.Table.AvailableWidthProvider
import flatgraph.{Accessors, Edge, GNode, MultiPropertyKey, OptionalPropertyKey, PropertyKey, Schema, SinglePropertyKey}

import scala.annotation.implicitNotFound
Expand Down Expand Up @@ -37,7 +38,11 @@ class GenericSteps[A](iterator: Iterator[A]) extends AnyVal {
|If you have generated domain classes, use `given DocSearchPackages = MyDomain.defaultDocSearchPackage`.
|If you have additional custom extension steps that specify help texts via @Doc annotations, use `given DocSearchPackages = MyDomain.defaultDocSearchPackage.withAdditionalPackage("my.custom.package)"`
|""".stripMargin)
def help[B >: A](implicit elementType: ClassTag[B], searchPackages: DocSearchPackages): String =
def help[B >: A](implicit
elementType: ClassTag[B],
searchPackages: DocSearchPackages,
availableWidthProvider: AvailableWidthProvider
): String =
new TraversalHelp(searchPackages).forElementSpecificSteps(elementType.runtimeClass, verbose = false)

@Doc(info = "print verbose help/documentation based on the current elementType `A`.")
Expand All @@ -46,7 +51,11 @@ class GenericSteps[A](iterator: Iterator[A]) extends AnyVal {
|If you have generated domain classes, use `given DocSearchPackages = MyDomain.defaultDocSearchPackage`.
|If you have additional custom extension steps that specify help texts via @Doc annotations, use `given DocSearchPackages = MyDomain.defaultDocSearchPackage.withAdditionalPackage("my.custom.package)"`
|""".stripMargin)
def helpVerbose[B >: A](implicit elementType: ClassTag[B], searchPackages: DocSearchPackages): String =
def helpVerbose[B >: A](implicit
elementType: ClassTag[B],
searchPackages: DocSearchPackages,
availableWidthProvider: AvailableWidthProvider
): String =
new TraversalHelp(searchPackages).forElementSpecificSteps(elementType.runtimeClass, verbose = true)

/** Execute the traversal and convert the result to a list - shorthand for `toList` */
Expand Down
60 changes: 60 additions & 0 deletions core/src/test/scala/flatgraph/help/TableTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package flatgraph.help

import org.scalatest.matchers.should.Matchers._
import org.scalatest.wordspec.AnyWordSpec
import flatgraph.help.Table.AvailableWidthProvider

class TableTests extends AnyWordSpec {

"render a nice generic table" in {
val table = Table(Seq("column a", "column b"), Seq(Seq("abc 1", "bde 1"), Seq("abc 2", "bde 2")))

implicit val availableWidthProvider: AvailableWidthProvider = new Table.ConstantWidth(100)
table.render.trim shouldBe
"""┌─────────────────────────────────────────────────┬────────────────────────────────────────────────┐
|│column a │column b │
|├─────────────────────────────────────────────────┼────────────────────────────────────────────────┤
|│abc 1 │bde 1 │
|│abc 2 │bde 2 │
|└─────────────────────────────────────────────────┴────────────────────────────────────────────────┘
|""".stripMargin.trim
}

"adapt to dynamically changing terminal width" in {
val table = Table(
Seq("lorem ipsum"),
Seq(
Seq(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et" +
" dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip " +
"ex ea commodo consequat."
)
)
)

var currentTerminalWidth = 80 // think "looking up current value from an actual terminal"
implicit val availableWidthProvider: AvailableWidthProvider = () => currentTerminalWidth

table.render.trim shouldBe
"""┌──────────────────────────────────────────────────────────────────────────────┐
|│lorem ipsum │
|├──────────────────────────────────────────────────────────────────────────────┤
|│Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor│
|│incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis │
|│nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. │
|└──────────────────────────────────────────────────────────────────────────────┘
|""".stripMargin.trim

currentTerminalWidth = 100 // emulating: terminal size has changed
table.render.trim shouldBe
"""┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|│lorem ipsum │
|├──────────────────────────────────────────────────────────────────────────────────────────────────┤
|│Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut │
|│labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris │
|│nisi ut aliquip ex ea commodo consequat. │
|└──────────────────────────────────────────────────────────────────────────────────────────────────┘
|""".stripMargin.trim
}

}
19 changes: 11 additions & 8 deletions core/src/test/scala/flatgraph/traversal/TraversalTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package flatgraph.traversal

import flatgraph.Implicits.start
import flatgraph.GNode
import flatgraph.help.DocSearchPackages
import flatgraph.help.{DocSearchPackages, Table}
import flatgraph.help.Table.AvailableWidthProvider
import flatgraph.traversal.Language.*
import flatgraph.traversal.testdomains.simple.SimpleDomain.Thing
import flatgraph.traversal.testdomains.simple.{ExampleGraphSetup, SimpleDomain}
Expand Down Expand Up @@ -88,18 +89,20 @@ class TraversalTests extends AnyWordSpec with ExampleGraphSetup {

".help step" should {
// a specific domain would provide it's own DocSearchPackage implementation, to specify where we're supposed to scan for @Doc annotations
given DocSearchPackages = DocSearchPackages.default
given DocSearchPackages = DocSearchPackages.default
given AvailableWidthProvider = new Table.ConstantWidth(120)

"generic help for `int`" in {
val helpText = Iterator(1, 2, 3, 4).help
helpText should include(".cast")
helpText should include("casts all elements to given type")
helpText should include(".whereNot")
helpText should include(" only preserves elements if the provided traversal does _not_ have any results")
helpText should include("only preserves elements if the provided traversal does")

val helpTextVerbose = Iterator(1, 2, 3, 4).helpVerbose
helpTextVerbose should include(".cast")
helpTextVerbose should include(".whereNot")
helpTextVerbose should include("""flatgraph.traversal.GenericSteps""") // should contain the location of the step definition...
helpTextVerbose should include("""flatgraph.traversal.GenericSt""") // should contain the location of the step definition...
}

"help for nodes" in {
Expand All @@ -114,8 +117,8 @@ class TraversalTests extends AnyWordSpec with ExampleGraphSetup {
helpTextVerbose should include(".label")
helpTextVerbose should include(".property")
helpTextVerbose should include(".cast")
helpTextVerbose should include("""flatgraph.traversal.GenericSteps""") // should contain the location of the step definition...
helpTextVerbose should include("""flatgraph.traversal.NodeSteps""") // should contain the location of the step definition...
helpTextVerbose should include("""flatgraph.traversal.GenericSt""") // should contain the location of the step definition...
helpTextVerbose should include("""flatgraph.traversal.NodeSteps""") // should contain the location of the step definition...
}

"give a domain overview" in {
Expand All @@ -142,11 +145,11 @@ class TraversalTests extends AnyWordSpec with ExampleGraphSetup {

val thingTraversalHelpVerbose = thingTraversal.helpVerbose
thingTraversalHelpVerbose should include("name of the Thing")
thingTraversalHelpVerbose should include("testdomains.simple.SimpleDomainTraversal")
thingTraversalHelpVerbose should include("simple.SimpleDomainTravers")
thingTraversalHelpVerbose should include("node label")
thingTraversalHelpVerbose should include("flatgraph.traversal.NodeSteps")
thingTraversalHelpVerbose should include("result to a list")
thingTraversalHelpVerbose should include("flatgraph.traversal.GenericSteps")
thingTraversalHelpVerbose should include("flatgraph.traversal.GenericSt")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package flatgraph.traversal.testdomains.simple

import flatgraph.help.Table.AvailableWidthProvider
import flatgraph.{DiffGraphApplier, DiffGraphBuilder, GNode, GenericDNode, Graph, TestSchema}
import flatgraph.help.{Doc, DocSearchPackages, Traversal, TraversalHelp, TraversalSource}
import flatgraph.traversal.testdomains.simple.SimpleDomain.Thing
Expand Down Expand Up @@ -58,8 +59,10 @@ object SimpleDomain {
}

val defaultDocSearchPackage: DocSearchPackages = DocSearchPackages(getClass.getPackage.getName)
lazy val help = TraversalHelp(defaultDocSearchPackage).forTraversalSources(verbose = false)
lazy val helpVerbose = TraversalHelp(defaultDocSearchPackage).forTraversalSources(verbose = true)
def help(using AvailableWidthProvider) =
TraversalHelp(defaultDocSearchPackage).forTraversalSources(verbose = false)
def helpVerbose(using AvailableWidthProvider) =
TraversalHelp(defaultDocSearchPackage).forTraversalSources(verbose = true)

def newGraph: Graph = {
val schema = TestSchema.make(1, 1)
Expand Down

0 comments on commit 12b9197

Please sign in to comment.