Skip to content

Commit cd02590

Browse files
authored
Merge branch 'main' into update/scala-collection-compat-2.4.2
2 parents ccdde4f + 6449dcc commit cd02590

File tree

13 files changed

+291
-32
lines changed

13 files changed

+291
-32
lines changed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,40 @@ It comes as a standalone library, a CLI tool and an sbt plugin.
1111
Scala2PlantUML consumes [SemanticDB] files so you will need to know how to create those or simply follow the sbt setup
1212
instructions below.
1313

14+
## Example
15+
16+
```shell
17+
scala2plantuml \
18+
--url 'https://repo1.maven.org/maven2/nz/co/bottech/scala2plantuml-example_2.13/0.2.0/scala2plantuml-example_2.13-0.2.0.jar'\
19+
--project example \
20+
"nz/co/bottech/scala2plantuml/example/Main."
21+
```
22+
23+
```text
24+
@startuml
25+
class A extends B {
26+
+ {method} <init>
27+
+ {method} b
28+
}
29+
A o-- C
30+
interface B {
31+
+ {abstract} {method} b
32+
}
33+
B o-- C
34+
class C {
35+
+ {method} <init>
36+
+ {field} value
37+
}
38+
C o-- A
39+
class Main {
40+
+ {static} {field} a
41+
}
42+
Main o-- A
43+
@enduml
44+
```
45+
46+
![Example Class Diagram](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/BotTech/scala2plantuml/main/example/example.md)
47+
1448
## sbt
1549

1650
### Enable SemanticDB
@@ -40,6 +74,21 @@ Create `~/.sbt/1.0/plugins/scala2PlantUML.sbt` containing:
4074
addSbtPlugin("nz.co.bottech" % "sbt-scala2plantuml" % "0.2.0")
4175
```
4276

77+
### Generate the Diagram
78+
79+
Run the `scala2PlantUML` task from sbt:
80+
81+
```sbt
82+
scala2PlantUML "com/example/Foo#"
83+
```
84+
85+
This accepts the following arguments:
86+
- `--include`
87+
- `--exclude`
88+
- `--output`
89+
90+
Refer to the [CLI Usage](#usage) for the definition of these arguments.
91+
4392
## CLI
4493

4594
### Install
@@ -179,6 +228,18 @@ Each of these can be provided multiple times. The result will be all combination
179228

180229
> 🚧 TODO: Document Library.
181230
231+
## Limitations
232+
233+
- Only class diagrams are supported.
234+
- Only inheritance or aggregations are supported, compositions are shown as aggregations.
235+
- Aggregations are shown between types not between fields. There is a [bug][namespaced field links] in PlantUML which
236+
prevents us from being able to do this reliably.
237+
- There is no reliable way to determine the path to a SemanticDB file from any symbol.
238+
If Scala2PlantUML is unable to find your symbols then the following may help:
239+
- Only have a single top level type in each file.
240+
- Ensure that the file name matches the type name.
241+
- Nest any subclasses of a sealed class within the companion object of the sealed class.
242+
182243
[coursier]: https://get-coursier.io/docs/cli-install
183244
[plantuml]: https://plantuml.com/
184245
[semanticdb]: https://scalameta.org/docs/semanticdb/guide.html

build.sbt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,15 @@ inThisBuild(
8282
// This needs to be set otherwise the GitHub workflow plugin gets confused about which
8383
// version to use for the publish job.
8484
scalaVersion := scala212,
85-
versionPolicyIntention := Compatibility.BinaryAndSourceCompatible,
85+
// TODO: Revert this after releasing 0.3.0.
86+
//versionPolicyIntention := Compatibility.BinaryAndSourceCompatible,
87+
versionPolicyIntention := Compatibility.None,
8688
versionScheme := Some("early-semver")
8789
)
8890
)
8991

9092
val commonProjectSettings = List(
9193
isScala213 := isScala213Setting.value,
92-
// Who cares about these. Forwards binary compatibility is used as an approximation for source
93-
// backwards compatibility and missing classes isn't a problem.
94-
mimaForwardIssueFilters += "0.2.0" -> List(ProblemFilters.exclude[MissingClassProblem]("nz.co.bottech.scala2plantuml.*")),
9594
name := s"${(LocalRootProject / name).value}-${name.value}",
9695
scalastyleFailOnError := true,
9796
scalastyleFailOnWarning := true,
@@ -110,14 +109,14 @@ val commonProjectSettings = List(
110109
val metaProjectSettings = List(
111110
mimaFailOnNoPrevious := false,
112111
mimaPreviousArtifacts := Set.empty,
113-
publish / skip := true
112+
publish / skip := true,
113+
versionPolicyCheck := Def.unit(())
114114
)
115115

116116
lazy val root = (project in file("."))
117117
.aggregate(cli, core, docs, example, integrationTests, sbtProject)
118118
.settings(metaProjectSettings)
119119
.settings(
120-
crossScalaVersions := supportedScalaVersions,
121120
name := "scala2plantuml",
122121
// Workaround for https://github.com/olafurpg/sbt-ci-release/issues/181
123122
// These have to go on the root project.
@@ -158,6 +157,10 @@ lazy val cli = project
158157
"ch.qos.logback" % "logback-core" % logbackVersion,
159158
"com.github.scopt" %% "scopt" % scoptVersion,
160159
"org.slf4j" % "slf4j-api" % slf4jVersion
160+
),
161+
mimaForwardIssueFilters += "0.2.0" -> List(
162+
// This is private so no harm done.
163+
ProblemFilters.exclude[MissingClassProblem]("nz.co.bottech.scala2plantuml.ConfigParser$Terminated$")
161164
)
162165
)
163166

@@ -211,7 +214,7 @@ lazy val sbtProject = (project in file("sbt"))
211214
lazy val docs = (project in (file("meta") / "docs"))
212215
// Include build info here so that we can override the version.
213216
.enablePlugins(BuildInfoPlugin, MdocPlugin)
214-
.dependsOn(cli)
217+
.dependsOn(cli, example)
215218
.settings(metaProjectSettings)
216219
.settings(
217220
// We use a different version setting so that it may depend on versionPolicyPreviousVersions
@@ -226,7 +229,8 @@ lazy val docs = (project in (file("meta") / "docs"))
226229
},
227230
mdocOut := (ThisBuild / baseDirectory).value,
228231
mdocVariables := Map(
229-
"VERSION" -> (mdoc / version).value
232+
"VERSION" -> (mdoc / version).value,
233+
"SCALA_VERSION" -> scalaMajorMinorVersion.value
230234
),
231235
unusedCompileDependenciesFilter -= moduleFilter("org.scalameta", "mdoc*"),
232236
mdoc / version := versionPolicyPreviousVersions.value.lastOption.getOrElse(version.value)
@@ -237,9 +241,17 @@ lazy val example = project
237241
.settings(
238242
semanticdbEnabled := true,
239243
semanticdbIncludeInJar := true,
240-
semanticdbVersion := sdbVersion
244+
semanticdbVersion := sdbVersion,
245+
versionPolicyCheck := Def.unit(())
241246
)
242247

248+
def scalaMajorMinorVersion: Def.Initialize[String] = Def.setting {
249+
CrossVersion.partialVersion(scala213) match {
250+
case Some((major, minor)) => s"$major.$minor"
251+
case _ => throw new IllegalArgumentException("scalaVersion is malformed.")
252+
}
253+
}
254+
243255
def isScala213Setting: Def.Initialize[Boolean] = Def.setting {
244256
CrossVersion.partialVersion(scalaVersion.value) match {
245257
case Some((2, n)) if n == 13 => true

cli/src/main/scala/nz/co/bottech/scala2plantuml/ConfigParser.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package nz.co.bottech.scala2plantuml
33
import scopt._
44

55
import java.io.File
6+
import java.net.URI
67

78
object ConfigParser {
89

@@ -104,7 +105,7 @@ object ConfigParser {
104105
opt[File]('j', "jar")
105106
.valueName("<jar>")
106107
.unbounded()
107-
.action((jar, config) => config.addDirectory(jar))
108+
.action((jar, config) => config.addFile(jar))
108109
.text(
109110
"""JAR containing META-INF/semanticdb/**/*.semanticdb files.
110111
|
@@ -113,10 +114,10 @@ object ConfigParser {
113114
|""".stripMargin
114115
),
115116
note(""),
116-
opt[File]('u', "url")
117+
opt[URI]('u', "url")
117118
.valueName("<url>")
118119
.unbounded()
119-
.action((url, config) => config.addDirectory(url))
120+
.action((url, config) => config.addURL(url.toURL))
120121
.text(
121122
"""A URL to a JAR containing META-INF/semanticdb/**/*.semanticdb files.
122123
|

core/src/main/scala/nz/co/bottech/scala2plantuml/ClassDiagramGenerator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ object ClassDiagramGenerator {
1313
classloader: ClassLoader,
1414
maxLevel: Option[Int] = None
1515
): Seq[ClassDiagramElement] = {
16-
val loader = new SemanticdbLoader(prefixes, classloader)
16+
val loader = new SemanticDBLoader(prefixes, classloader)
1717
val symbolTable = aggregateSymbolTable(loader)
1818
val symbolIndex = new SymbolIndex(ignore, symbolTable)
1919
val typeIndex = new TypeIndex(symbolIndex)
2020
val definitionIndex = new DefinitionIndex(loader)
2121
SymbolProcessor.processSymbol(symbol, maxLevel, symbolIndex, typeIndex, definitionIndex)
2222
}
2323

24-
private def aggregateSymbolTable(loader: SemanticdbLoader) =
24+
private def aggregateSymbolTable(loader: SemanticDBLoader) =
2525
AggregateSymbolTable(
2626
List(
2727
new LazySymbolTable(loader),

core/src/main/scala/nz/co/bottech/scala2plantuml/ClassDiagramRenderer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ object ClassDiagramRenderer {
9696
def render(elements: Seq[ClassDiagramElement], options: Options, writer: Writer): Unit = {
9797
writer.write("@startuml\n")
9898
renderSnippet(elements, options, writer)
99-
writer.write("@enduml\n")
99+
writer.write("@enduml")
100100
}
101101

102102
def renderSnippetString(elements: Seq[ClassDiagramElement], options: Options): String =

core/src/main/scala/nz/co/bottech/scala2plantuml/DefinitionIndex.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory
55
import scala.collection.concurrent.TrieMap
66
import scala.meta.internal.semanticdb.SymbolOccurrence
77

8-
private[scala2plantuml] class DefinitionIndex(loader: SemanticdbLoader) {
8+
private[scala2plantuml] class DefinitionIndex(loader: SemanticDBLoader) {
99

1010
private val logger = LoggerFactory.getLogger(classOf[DefinitionIndex])
1111
private val cache = TrieMap.empty[String, Option[SymbolOccurrence]]

core/src/main/scala/nz/co/bottech/scala2plantuml/LazySymbolTable.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.collection.concurrent.TrieMap
66
import scala.meta.internal.semanticdb.SymbolInformation
77
import scala.meta.internal.symtab.SymbolTable
88

9-
private[scala2plantuml] class LazySymbolTable(loader: SemanticdbLoader) extends SymbolTable {
9+
private[scala2plantuml] class LazySymbolTable(loader: SemanticDBLoader) extends SymbolTable {
1010

1111
private val logger = LoggerFactory.getLogger(classOf[LazySymbolTable])
1212
private val cache = TrieMap.empty[String, SymbolInformation]

core/src/main/scala/nz/co/bottech/scala2plantuml/SemanticDbLoader.scala

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package nz.co.bottech.scala2plantuml
22

3+
import nz.co.bottech.scala2plantuml.SemanticDBLoader._
4+
35
import java.io.{File, FileInputStream, FilenameFilter, InputStream}
46
import java.net.URL
57
import java.nio.file.Paths
@@ -9,10 +11,7 @@ import scala.meta.internal.semanticdb.Scala._
911
import scala.meta.internal.semanticdb.{TextDocument, TextDocuments}
1012
import scala.util.Using
1113

12-
private[scala2plantuml] class SemanticdbLoader(prefixes: Seq[String], classLoader: ClassLoader) {
13-
14-
type Errors = Vector[String]
15-
type Result = Either[Errors, Seq[TextDocument]]
14+
private[scala2plantuml] class SemanticDBLoader(prefixes: Seq[String], classLoader: ClassLoader) {
1615

1716
private val cache = TrieMap.empty[String, Result]
1817

@@ -78,10 +77,23 @@ private[scala2plantuml] class SemanticdbLoader(prefixes: Seq[String], classLoade
7877
TextDocuments.parseFrom(semanticdb).documents
7978
}.toEither.left.map(error => Vector(error.getLocalizedMessage))
8079

81-
private def semanticdbPath(symbol: String): Either[Errors, String] =
82-
if (symbol.isGlobal)
83-
Right(s"${symbol.dropRight(1).takeWhile(_ != '#')}.scala.semanticdb")
84-
else
80+
}
81+
82+
private[scala2plantuml] object SemanticDBLoader {
83+
84+
private type Errors = Vector[String]
85+
private type Result = Either[Errors, Seq[TextDocument]]
86+
87+
private[scala2plantuml] def semanticdbPath(symbol: String): Either[Errors, String] =
88+
if (symbol.isGlobal) {
89+
val a = symbol.lastIndexOf('/')
90+
val (start, end) = symbol.splitAt(a + 1)
91+
val prefix = if (start == "_empty" || start == "_root_") "" else start
92+
val name = end.takeWhile(c => c != '#' && c != '.')
93+
if (name == "package") {
94+
Right(s"${prefix.init}.scala.semanticdb")
95+
} else Right(s"$prefix$name.scala.semanticdb")
96+
} else
8597
Left(Vector(s"Symbol is not global: $symbol"))
8698

8799
private def packagePath(path: String): Option[String] = {
@@ -91,9 +103,10 @@ private[scala2plantuml] class SemanticdbLoader(prefixes: Seq[String], classLoade
91103
}
92104

93105
private def findSemanticdbs(directory: File): Array[File] =
94-
directory.listFiles(new FilenameFilter {
106+
directory
107+
.listFiles(new FilenameFilter {
95108

96-
override def accept(dir: File, name: String): Boolean =
97-
name.endsWith(".semanticdb")
98-
})
109+
override def accept(dir: File, name: String): Boolean =
110+
name.endsWith(".semanticdb")
111+
})
99112
}

0 commit comments

Comments
 (0)