Adds support for the d2 syntax (#17)
* Fixes issue where nodes were not added to existing cluster * Adds missing dependency * Adds support for the d2 syntax * Adds d2 to README * Update README.md * Update README.md
|
@ -790,6 +790,20 @@
|
|||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D2GraphMapper"
|
||||
BuildableName = "D2GraphMapper"
|
||||
BlueprintName = "D2GraphMapper"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
|
@ -998,6 +1012,26 @@
|
|||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DirectedGraphTests"
|
||||
BuildableName = "DirectedGraphTests"
|
||||
BlueprintName = "DirectedGraphTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D2GraphMapperTests"
|
||||
BuildableName = "D2GraphMapperTests"
|
||||
BlueprintName = "D2GraphMapperTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
|
@ -1021,6 +1055,16 @@
|
|||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "/Users/simonbs/Developer/RunestoneEditor"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--syntax d2"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
|
@ -16,6 +16,7 @@ let package = Package(
|
|||
targets: [
|
||||
.executableTarget(name: "Main", dependencies: [
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
"D2GraphMapper",
|
||||
"DirectedGraphMapper",
|
||||
"DirectedGraphWriter",
|
||||
"DOTGraphMapper",
|
||||
|
@ -55,6 +56,11 @@ let package = Package(
|
|||
], path: "Sources/Library/Commands/GraphCommand"),
|
||||
|
||||
// Sources/Library/Graphing
|
||||
.target(name: "D2GraphMapper", dependencies: [
|
||||
"DirectedGraph",
|
||||
"DirectedGraphMapper",
|
||||
"StringIndentHelpers"
|
||||
], path: "Sources/Library/Graphing/D2GraphMapper"),
|
||||
.target(name: "DirectedGraph", path: "Sources/Library/Graphing/DirectedGraph"),
|
||||
.target(name: "DirectedGraphXcodeHelpers", dependencies: [
|
||||
"DirectedGraph"
|
||||
|
@ -163,6 +169,10 @@ let package = Package(
|
|||
.target(name: "StringIndentHelpers", path: "Sources/Library/Utilities/StringIndentHelpers"),
|
||||
|
||||
// Tests
|
||||
.testTarget(name: "D2GraphMapperTests", dependencies: [
|
||||
"DirectedGraph",
|
||||
"D2GraphMapper"
|
||||
]),
|
||||
.testTarget(name: "DirectedGraphTests", dependencies: [
|
||||
"DirectedGraph"
|
||||
]),
|
||||
|
|
85
README.md
|
@ -4,19 +4,19 @@
|
|||
|
||||
dependency-graph is a command-line tool that can visualize the dependencies of packages. The tool takes the path to an Xcode project or a Package.swift file as input and outputs a graph that shows the dependencies of the packages in the project or package.
|
||||
|
||||
## 👀 Sample
|
||||
## 👀 Examples
|
||||
|
||||
The graph below shows the relationship of the products and targets in this package as of December 11, 2022. Click on the image to see a larger version.
|
||||
The following graphs are examples of the graphs that dependency-graph can output. The first graph built by providing dependency-graph the path to a Package.swift file and the second graph was made by providing dependency-graph the path to an .xcodeproj file as input.
|
||||
|
||||
<img width="400" src="./sample-swift-package.png" alt="Example graph showing the dependencies of this package." />
|
||||
|Swift Package|Xcode Project|
|
||||
|-|-|
|
||||
|<img width="400" src="./example-swift-package.png" alt="Example graph showing the dependencies of this package." />|<img width="400" src="./example-xcodeproj.png" alt="Example graph showing the dependencies of an Xcode project." />|
|
||||
|
||||
Nodes shaped as an ellipse represent products, e.g. the libraries in a Swift package, and the square nodes represent targets.
|
||||
Nodes shaped as ellipsis represent products, e.g. the libraries in a Swift package, and the square nodes represent targets.
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
Start off by installing the tool.
|
||||
|
||||
#### Using [Homebrew](https://brew.sh)
|
||||
Start off by installing the tool with [Homebrew](https://brew.sh).
|
||||
|
||||
```bash
|
||||
brew tap simonbs/dependency-graph https://github.com/simonbs/dependency-graph.git
|
||||
|
@ -39,14 +39,6 @@ brew install dependency-graph
|
|||
> arch -arm64 brew install dependency-graph
|
||||
> ```
|
||||
|
||||
#### Using [Mint](https://github.com/yonaskolb/Mint)
|
||||
|
||||
```bash
|
||||
mint install simonbs/dependency-graph
|
||||
```
|
||||
|
||||
#### Confirm Installation
|
||||
|
||||
You may now run the following command to verify that the tool was installed correctly. The following command should print information on how the tool can be used.
|
||||
|
||||
```
|
||||
|
@ -97,7 +89,13 @@ digraph g {
|
|||
}
|
||||
```
|
||||
|
||||
The output can be rendered to an image by piping it to the [dot CLI](https://graphviz.org/doc/info/command.html), which is part of [Graphviz](https://graphviz.org).
|
||||
The output can be rendered to an image by piping it to a renderer. See the following sections for details on the supported renderers.
|
||||
|
||||
#### DOT
|
||||
|
||||
<img width="400" src="./example-dot.png" alt="Example graph rendered with dot." />
|
||||
|
||||
By default dependency-graph will use the DOT syntax which can be rendered by the [dot CLI](https://graphviz.org/doc/info/command.html), which is part of [Graphviz](https://graphviz.org).
|
||||
|
||||
Install Graphviz and run `dependency-graph` and pass the output to the newly installed `dot` CLI.
|
||||
|
||||
|
@ -106,23 +104,37 @@ brew install graphviz
|
|||
dependency-graph ~/Developer/Example | dot -Tsvg -o graph.svg
|
||||
```
|
||||
|
||||
The previous example output would look different but similar when using [the Mermaid diagram syntax](https://mermaid-js.github.io/mermaid/#/flowchart) instead. The syntax is used by passing the `--syntax mermaid` option.
|
||||
When rendering the graph to a PNG, you will likely want to specify the size of the output to ensure it is readable. To generate an image with dot that is exactly 6000 pixels wide or 8000 pixels tall but not necessarily both, do the following:
|
||||
|
||||
Output in the Mermaid diagram syntax can be rendered to an image using the [the mermaid cli](https://github.com/mermaid-js/mermaid-cli).
|
||||
```bash
|
||||
dependency-graph ~/Developer/Example | dot -Tpng -Gsize=60,80\! -Gdpi=100 -o graph.png
|
||||
```
|
||||
|
||||
You may want to play around with the values for `--node-spacing` and `--rank-spacing` to increase the readability of the graph.
|
||||
|
||||
```bash
|
||||
dependency-graph --node-spacing 50 --rank-spacing 150 ~/Developer/Example | dot -Tsvg -o graph.svg
|
||||
```
|
||||
|
||||
For large projects the graph may become unreadable. Passing the output through Grahpviz' [unflatten](https://graphviz.org/docs/cli/unflatten/) command may improve the results.
|
||||
|
||||
```bash
|
||||
dependency-graph ~/Developer/Example | unflatten -l 100 -c 100 -f | dot -Tpng -o graph.png
|
||||
```
|
||||
|
||||
#### Mermaid
|
||||
|
||||
<img width="400" src="./example-mermaid.png" alt="Example graph rendered with mermaid." />
|
||||
|
||||
Specify the `--syntax mermaid` option to have dependency-graph output a graph using [the Mermaid diagram syntax](https://mermaid-js.github.io/mermaid/#/flowchart).
|
||||
|
||||
The output be rendered to an image using the [the mermaid cli](https://github.com/mermaid-js/mermaid-cli).
|
||||
|
||||
```bash
|
||||
npm install -g @mermaid-js/mermaid-cli
|
||||
dependency-graph --syntax mermaid ~/Developer/Example | mmdc -o graph.svg
|
||||
```
|
||||
|
||||
When rendering the graph to a PNG, you will likely want to specify the size of the output to ensure it is readable. You can do this with both the dot and mermaid CLIs.
|
||||
|
||||
To generate an image with dot that is exactly 6000 pixels wide or 8000 pixels tall but not necessarily both, do the following:
|
||||
|
||||
```bash
|
||||
dependency-graph ~/Developer/norlys-ios/Features/Notes | dot -Tpng -Gsize=60,80\! -Gdpi=100 -o ~/Desktop/dot.png
|
||||
```
|
||||
|
||||
To generate an image on a page that is 6000 pixels wide with mermaid, do the following:
|
||||
|
||||
```bash
|
||||
|
@ -135,16 +147,25 @@ You may also want to play around with the values for `--node-spacing` and `--ran
|
|||
dependency-graph --syntax mermaid --node-spacing 50 --rank-spacing 150 ~/Developer/Example | mmdc -o graph.png
|
||||
```
|
||||
|
||||
Pass the `--packages-only` flag to include only the Xcode project and Swift packages in the graph. This omits the libraries and targets within the Xcode project and Swift packages.
|
||||
#### D2
|
||||
|
||||
<img width="400" src="./sample-packages-only.png" alt="Example graph showing only an Xcode project and Swift packages." />
|
||||
<img width="400" src="./example-d2.png" alt="Example graph rendered with d2." />
|
||||
|
||||
For large projects the graph may become unreadable. Passing the output through Grahpviz' [unflatten](https://graphviz.org/docs/cli/unflatten/) command may improve the resutls.
|
||||
Specify the `--syntax d2` option to have dependency-graph output a graph using [the d2 scripting language](https://d2lang.com/tour/intro).
|
||||
|
||||
The output be rendered to an image using the [the d2 cli](https://github.com/terrastruct/d2#install).
|
||||
|
||||
```bash
|
||||
dependency-graph ~/Developer/Example | unflatten -l 100 -c 100 -f | dot -Tpng -o graph.png
|
||||
curl -fsSL https://d2lang.com/install.sh | sh -s --
|
||||
dependency-graph --syntax d2 ~/Developer/Example | d2 - graph.png
|
||||
```
|
||||
|
||||
## Graphing Packages Only
|
||||
|
||||
Pass the `--packages-only` flag to include only the Xcode project and Swift packages in the graph. This omits the libraries and targets within the Xcode project and Swift packages.
|
||||
|
||||
<img width="400" src="./example-packages-only.png" alt="Example graph showing only an Xcode project and Swift packages." />
|
||||
|
||||
## 🤷♂️ OK, why?
|
||||
|
||||
As I'm splitting my iOS and macOS applications into small Swift packages with several small targets, I started wishing for a way to visualise the relationship between the products and targets in my Swift packages. That's why I built this tool.
|
||||
|
@ -154,9 +175,7 @@ Several other tools can visualise a Swift package, however, I wanted a tool that
|
|||
The example in the top of this README shows a visualization of a Swift package and the graph below shows a visualisation of an Xcode project.
|
||||
Notice that the left-most subgraph represents an Xcode project named ScriptUIEditor.xcodeproj and it has three targets: ScriptUIEditor, ScriptBrowserFeature, and ScriptBrowserFeatureUITests. Two of these depends on the Swift packages represented by the remaining subgraphs.
|
||||
|
||||
These graphs provide a good way to get an overview of a package or the relationship between several packages. Sometimes it can be helpful to generate multiple graphs to get a good overview, for example, a graph of the entire project and graphs of selected packages. Fortunately, the `dependency-graph` cLI makes this easy as it can take either an Xcode project and a Package.swift file as input.
|
||||
|
||||
<img width="400" src="./sample-xcodeproj.png" alt="Example graph showing the dependencies of an Xcode project." />
|
||||
These graphs provide a good way to get an overview of a package or the relationship between several packages. Sometimes it can be helpful to generate multiple graphs to get a good overview, for example, a graph of the entire project and graphs of selected packages. Fortunately, the `dependency-graph` CLI makes this easy as it can take either an Xcode project and a Package.swift file as input.
|
||||
|
||||
## 🧐 ...but how?
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
import DirectedGraphWriter
|
||||
|
||||
public struct DirectedGraphWriterFactory {
|
||||
private let d2GraphWriter: any DirectedGraphWriter
|
||||
private let dotGraphWriter: any DirectedGraphWriter
|
||||
private let mermaidGraphWriter: any DirectedGraphWriter
|
||||
|
||||
public init(dotGraphWriter: any DirectedGraphWriter, mermaidGraphWriter: any DirectedGraphWriter) {
|
||||
public init(d2GraphWriter: any DirectedGraphWriter, dotGraphWriter: any DirectedGraphWriter, mermaidGraphWriter: any DirectedGraphWriter) {
|
||||
self.d2GraphWriter = d2GraphWriter
|
||||
self.dotGraphWriter = dotGraphWriter
|
||||
self.mermaidGraphWriter = mermaidGraphWriter
|
||||
}
|
||||
|
||||
func writer(for syntax: Syntax) -> any DirectedGraphWriter {
|
||||
switch syntax {
|
||||
case .d2:
|
||||
return d2GraphWriter
|
||||
case .dot:
|
||||
return dotGraphWriter
|
||||
case .mermaid:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
public enum Syntax: String {
|
||||
case d2
|
||||
case dot
|
||||
case mermaid
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import DirectedGraph
|
||||
import DirectedGraphMapper
|
||||
import Foundation
|
||||
import StringIndentHelpers
|
||||
|
||||
enum D2GraphMapperError: LocalizedError {
|
||||
case failedFindingClusterContainingNode(DirectedGraph.Node)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .failedFindingClusterContainingNode(let node):
|
||||
return "Could not find cluster containing node named '\(node.name)'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct D2GraphMapper: DirectedGraphMapper {
|
||||
private let settings = D2GraphSettings()
|
||||
|
||||
public init() {}
|
||||
|
||||
public func map(_ graph: DirectedGraph) throws -> String {
|
||||
return try graph.stringRepresentation(withSettings: settings)
|
||||
}
|
||||
}
|
||||
|
||||
extension DirectedGraph {
|
||||
func stringRepresentation(withSettings settings: D2GraphSettings) throws -> String {
|
||||
var lines: [String] = []
|
||||
lines.append(settings.stringRepresentation)
|
||||
if !clusters.isEmpty {
|
||||
lines.append(clusters.stringRepresentation)
|
||||
}
|
||||
if !nodes.isEmpty {
|
||||
lines.append(nodes.stringRepresentation)
|
||||
}
|
||||
if !edges.isEmpty {
|
||||
lines.append(try edges.stringRepresentation(in: self))
|
||||
}
|
||||
return lines.joined(separator: "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
extension DirectedGraph.Cluster {
|
||||
var stringRepresentation: String {
|
||||
var lines = ["\(name): \(label) {"]
|
||||
lines += nodes.map(\.stringRepresentation).indented
|
||||
lines += ["}"]
|
||||
return lines.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
extension DirectedGraph.Node {
|
||||
var stringRepresentation: String {
|
||||
var lines = [name + ": " + label]
|
||||
switch shape {
|
||||
case .box:
|
||||
lines += [name + ".shape: rectangle"]
|
||||
case .ellipse:
|
||||
lines += [name + ".shape: oval"]
|
||||
}
|
||||
return lines.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
extension DirectedGraph.Edge {
|
||||
func stringRepresentation(in graph: DirectedGraph) throws -> String {
|
||||
func path(for node: DirectedGraph.Node) throws -> String {
|
||||
guard !graph.isRootNode(node) else {
|
||||
return node.name
|
||||
}
|
||||
guard let cluster = graph.cluster(containing: node) else {
|
||||
throw D2GraphMapperError.failedFindingClusterContainingNode(node)
|
||||
}
|
||||
// print(cluster.name + "." + node.name)
|
||||
return cluster.name + "." + node.name
|
||||
}
|
||||
return "\(try path(for: sourceNode)) -> \(try path(for: destinationNode))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == DirectedGraph.Cluster {
|
||||
var stringRepresentation: String {
|
||||
return map { $0.stringRepresentation }.joined(separator: "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == DirectedGraph.Node {
|
||||
var stringRepresentation: String {
|
||||
return map(\.stringRepresentation).joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == DirectedGraph.Edge {
|
||||
func stringRepresentation(in graph: DirectedGraph) throws -> String {
|
||||
return try map { try $0.stringRepresentation(in: graph) }.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
private extension DirectedGraph {
|
||||
func isRootNode(_ node: DirectedGraph.Node) -> Bool {
|
||||
return nodes.contains(node)
|
||||
}
|
||||
|
||||
func cluster(containing node: DirectedGraph.Node) -> DirectedGraph.Cluster? {
|
||||
return clusters.first { cluster in
|
||||
return cluster.nodes.contains { $0.name == node.name }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
|
||||
public struct D2GraphSettings {
|
||||
public let direction: String
|
||||
|
||||
public init() {
|
||||
direction = "right"
|
||||
}
|
||||
}
|
||||
|
||||
extension D2GraphSettings {
|
||||
var stringRepresentation: String {
|
||||
let lines = ["direction: \(direction)"]
|
||||
return lines.joined(separator: "\n")
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import D2GraphMapper
|
||||
import DirectedGraphMapper
|
||||
import DirectedGraphWriter
|
||||
import DOTGraphMapper
|
||||
|
@ -40,7 +41,12 @@ private extension CompositionRoot {
|
|||
private static func directedGraphWriterFactory(nodeSpacing: Float?, rankSpacing: Float?) -> DirectedGraphWriterFactory {
|
||||
let dotGraphWriter = dotGraphWriter(nodesep: nodeSpacing, ranksep: rankSpacing)
|
||||
let mermaidGraphWriter = mermaidGraphWriter(nodeSpacing: nodeSpacing.map(Int.init), rankSpacing: rankSpacing.map(Int.init))
|
||||
return DirectedGraphWriterFactory(dotGraphWriter: dotGraphWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
return DirectedGraphWriterFactory(d2GraphWriter: d2GraphWriter, dotGraphWriter: dotGraphWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
}
|
||||
|
||||
private static var d2GraphWriter: some DirectedGraphWriter {
|
||||
let mapper = D2GraphMapper()
|
||||
return MappingDirectedGraphWriter(mapper: mapper, writer: stdoutWriter)
|
||||
}
|
||||
|
||||
private static func dotGraphWriter(nodesep: Float?, ranksep: Float?) -> some DirectedGraphWriter {
|
||||
|
@ -49,32 +55,42 @@ private extension CompositionRoot {
|
|||
return MappingDirectedGraphWriter(mapper: mapper, writer: stdoutWriter)
|
||||
}
|
||||
|
||||
private static var dumpPackageService: DumpPackageService {
|
||||
return DumpPackageServiceLive(shellCommandRunner: shellCommandRunner)
|
||||
}
|
||||
|
||||
private static var fileSystem: FileSystem {
|
||||
return FileSystemLive()
|
||||
}
|
||||
|
||||
private static func mermaidGraphWriter(nodeSpacing: Int?, rankSpacing: Int?) -> some DirectedGraphWriter {
|
||||
let settings = MermaidGraphSettings(nodeSpacing: nodeSpacing, rankSpacing: rankSpacing)
|
||||
let mapper = MermaidGraphMapper(settings: settings)
|
||||
return MappingDirectedGraphWriter(mapper: mapper, writer: stdoutWriter)
|
||||
}
|
||||
|
||||
private static func packageGraphBuilder(packagesOnly: Bool) -> PackageGraphBuilder {
|
||||
return PackageGraphBuilderLive(packagesOnly: packagesOnly)
|
||||
private static var projectRootClassifier: ProjectRootClassifier {
|
||||
return ProjectRootClassifierLive(fileSystem: fileSystem)
|
||||
}
|
||||
|
||||
private static var dumpPackageService: DumpPackageService {
|
||||
return DumpPackageServiceLive(shellCommandRunner: shellCommandRunner)
|
||||
}
|
||||
|
||||
private static var packageSwiftFileParser: PackageSwiftFileParser {
|
||||
return PackageSwiftFileParserLive(cache: packageSwiftFileParserCache, dumpPackageService: dumpPackageService)
|
||||
}
|
||||
|
||||
private static func packageGraphBuilder(packagesOnly: Bool) -> PackageGraphBuilder {
|
||||
return PackageGraphBuilderLive(packagesOnly: packagesOnly)
|
||||
}
|
||||
|
||||
private static let packageSwiftFileParserCache: PackageSwiftFileParserCache = PackageSwiftFileParserCacheLive()
|
||||
|
||||
private static var projectRootClassifier: ProjectRootClassifier {
|
||||
return ProjectRootClassifierLive(fileSystem: fileSystem)
|
||||
private static var xcodeProjectParser: XcodeProjectParser {
|
||||
return XcodeProjectParserLive(fileSystem: fileSystem)
|
||||
}
|
||||
|
||||
private static func xcodeProjectGraphBuilder(packagesOnly: Bool) -> XcodeProjectGraphBuilder {
|
||||
return XcodeProjectGraphBuilderLive(packageSwiftFileParser: packageSwiftFileParser,
|
||||
packageGraphBuilder: packageGraphBuilder(packagesOnly: packagesOnly),
|
||||
packagesOnly: packagesOnly)
|
||||
}
|
||||
|
||||
private static var fileSystem: FileSystem {
|
||||
return FileSystemLive()
|
||||
}
|
||||
|
||||
private static var shellCommandRunner: ShellCommandRunner {
|
||||
|
@ -84,14 +100,4 @@ private extension CompositionRoot {
|
|||
private static var stdoutWriter: StdoutWriter {
|
||||
return StdoutWriter()
|
||||
}
|
||||
|
||||
private static func xcodeProjectGraphBuilder(packagesOnly: Bool) -> XcodeProjectGraphBuilder {
|
||||
return XcodeProjectGraphBuilderLive(packageSwiftFileParser: packageSwiftFileParser,
|
||||
packageGraphBuilder: packageGraphBuilder(packagesOnly: packagesOnly),
|
||||
packagesOnly: packagesOnly)
|
||||
}
|
||||
|
||||
private static var xcodeProjectParser: XcodeProjectParser {
|
||||
return XcodeProjectParserLive(fileSystem: fileSystem)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ The syntax of the output.
|
|||
|
||||
Valid values are:
|
||||
|
||||
- d2: D2 document to be passed to d2. More info: https://github.com/terrastruct/d2
|
||||
|
||||
- dot: DOT document to be passed to GraphViz. More info: https://graphviz.org/doc/info/command.html
|
||||
|
||||
- mermaid: Mermaid.js' diagram syntax. To be be passed to the Mermaid CLI. More info: https://github.com/mermaid-js/mermaid-cli.
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
@testable import D2GraphMapper
|
||||
import XCTest
|
||||
|
||||
final class D2GraphMapperTests: XCTestCase {
|
||||
func testMapsGraphToD2Syntax() throws {
|
||||
let mapper = D2GraphMapper()
|
||||
let string = try mapper.map(.mock)
|
||||
let expectedString = """
|
||||
direction: right
|
||||
|
||||
Foo: Foo {
|
||||
Foo: Foo
|
||||
Foo.shape: rectangle
|
||||
}
|
||||
|
||||
Bar: Bar {
|
||||
Bar: Bar
|
||||
Bar.shape: rectangle
|
||||
}
|
||||
|
||||
Baz: Baz {
|
||||
Baz: Baz
|
||||
Baz.shape: oval
|
||||
}
|
||||
|
||||
Foo.Foo -> Bar.Bar
|
||||
Foo.Foo -> Baz.Baz
|
||||
"""
|
||||
XCTAssertEqual(string, expectedString)
|
||||
}
|
||||
|
||||
func testMapsGraphWithRootNodesToD2Syntax() throws {
|
||||
let mapper = D2GraphMapper()
|
||||
let string = try mapper.map(.mockWithRootNodes)
|
||||
let expectedString = """
|
||||
direction: right
|
||||
|
||||
Foo: Foo
|
||||
Foo.shape: rectangle
|
||||
Bar: Bar
|
||||
Bar.shape: rectangle
|
||||
Baz: Baz
|
||||
Baz.shape: oval
|
||||
|
||||
Foo -> Bar
|
||||
Foo -> Baz
|
||||
"""
|
||||
XCTAssertEqual(string, expectedString)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import DirectedGraph
|
||||
|
||||
extension DirectedGraph {
|
||||
static var mock: DirectedGraph {
|
||||
let fooNode = DirectedGraph.Node(name: "Foo", label: "Foo")
|
||||
let fooCluster = DirectedGraph.Cluster(name: "Foo", label: "Foo", nodes: [fooNode])
|
||||
let barNode = DirectedGraph.Node(name: "Bar", label: "Bar")
|
||||
let barCluster = DirectedGraph.Cluster(name: "Bar", label: "Bar", nodes: [barNode])
|
||||
let bazNode = DirectedGraph.Node(name: "Baz", label: "Baz", shape: .ellipse)
|
||||
let bazCluster = DirectedGraph.Cluster(name: "Baz", label: "Baz", nodes: [bazNode])
|
||||
return DirectedGraph(clusters: [
|
||||
fooCluster,
|
||||
barCluster,
|
||||
bazCluster
|
||||
], edges: [
|
||||
DirectedGraph.Edge(from: fooNode, to: barNode),
|
||||
DirectedGraph.Edge(from: fooNode, to: bazNode)
|
||||
])
|
||||
}
|
||||
|
||||
static var mockWithRootNodes: DirectedGraph {
|
||||
let fooNode = DirectedGraph.Node(name: "Foo", label: "Foo")
|
||||
let barNode = DirectedGraph.Node(name: "Bar", label: "Bar")
|
||||
let bazNode = DirectedGraph.Node(name: "Baz", label: "Baz", shape: .ellipse)
|
||||
return DirectedGraph(nodes: [
|
||||
fooNode,
|
||||
barNode,
|
||||
bazNode
|
||||
], edges: [
|
||||
DirectedGraph.Edge(from: fooNode, to: barNode),
|
||||
DirectedGraph.Edge(from: fooNode, to: bazNode)
|
||||
])
|
||||
}
|
||||
}
|
|
@ -4,23 +4,38 @@ import DirectedGraphWriter
|
|||
import XCTest
|
||||
|
||||
final class DirectedGraphWriterFactoryTests: XCTestCase {
|
||||
func testReturnsDotGraphMapper() throws {
|
||||
func testReturnsD2GraphMapper() throws {
|
||||
let d2GrapWriter = D2GraphWriterMock()
|
||||
let dotGrapWriter = DOTGraphWriterMock()
|
||||
let mermaidGraphWriter = MermaidGraphWriterMock()
|
||||
let factory = DirectedGraphWriterFactory(dotGraphWriter: dotGrapWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let factory = DirectedGraphWriterFactory(d2GraphWriter: d2GrapWriter, dotGraphWriter: dotGrapWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let writer = factory.writer(for: .d2)
|
||||
XCTAssertTrue(writer is D2GraphWriterMock)
|
||||
}
|
||||
|
||||
func testReturnsDotGraphMapper() throws {
|
||||
let d2GrapWriter = DOTGraphWriterMock()
|
||||
let dotGrapWriter = DOTGraphWriterMock()
|
||||
let mermaidGraphWriter = MermaidGraphWriterMock()
|
||||
let factory = DirectedGraphWriterFactory(d2GraphWriter: d2GrapWriter, dotGraphWriter: dotGrapWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let writer = factory.writer(for: .dot)
|
||||
XCTAssertTrue(writer is DOTGraphWriterMock)
|
||||
}
|
||||
|
||||
func testReturnsMermaidGraphMapper() throws {
|
||||
let d2GrapWriter = DOTGraphWriterMock()
|
||||
let dotGrapWriter = DOTGraphWriterMock()
|
||||
let mermaidGraphWriter = MermaidGraphWriterMock()
|
||||
let factory = DirectedGraphWriterFactory(dotGraphWriter: dotGrapWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let factory = DirectedGraphWriterFactory(d2GraphWriter: d2GrapWriter, dotGraphWriter: dotGrapWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let writer = factory.writer(for: .mermaid)
|
||||
XCTAssertTrue(writer is MermaidGraphWriterMock)
|
||||
}
|
||||
}
|
||||
|
||||
private struct D2GraphWriterMock: DirectedGraphWriter {
|
||||
func write(_ directedGraph: DirectedGraph) throws {}
|
||||
}
|
||||
|
||||
private struct DOTGraphWriterMock: DirectedGraphWriter {
|
||||
func write(_ directedGraph: DirectedGraph) throws {}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,37 @@
|
|||
import XCTest
|
||||
|
||||
final class GraphCommandTests: XCTestCase {
|
||||
func testInvokesOnlyDotGraphWriter() throws {
|
||||
func testInvokesOnlyD2GraphWriter() throws {
|
||||
let d2GraphWriter = DirectedGraphWriterMock()
|
||||
let dotGraphWriter = DirectedGraphWriterMock()
|
||||
let mermaidGraphWriter = DirectedGraphWriterMock()
|
||||
let directedGraphWriterFactory = DirectedGraphWriterFactory(dotGraphWriter: dotGraphWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let directedGraphWriterFactory = DirectedGraphWriterFactory(
|
||||
d2GraphWriter: d2GraphWriter,
|
||||
dotGraphWriter: dotGraphWriter,
|
||||
mermaidGraphWriter: mermaidGraphWriter
|
||||
)
|
||||
let command = GraphCommand(projectRootClassifier: ProjectRootClassifierMock(),
|
||||
packageSwiftFileParser: PackageSwiftFileParserMock(),
|
||||
xcodeProjectParser: XcodeProjectParserMock(),
|
||||
packageGraphBuilder: PackageGraphBuilderMock(),
|
||||
xcodeProjectGraphBuilder: XcodeProjectGraphBuilderMock(),
|
||||
directedGraphWriterFactory: directedGraphWriterFactory)
|
||||
let fileURL = NSURL.fileURL(withPath: "/Users/simon/Developer/Example")
|
||||
try command.run(withInput: fileURL.path, syntax: .d2)
|
||||
XCTAssertTrue(d2GraphWriter.didWrite)
|
||||
XCTAssertFalse(dotGraphWriter.didWrite)
|
||||
XCTAssertFalse(mermaidGraphWriter.didWrite)
|
||||
}
|
||||
|
||||
func testInvokesOnlyDotGraphWriter() throws {
|
||||
let d2GraphWriter = DirectedGraphWriterMock()
|
||||
let dotGraphWriter = DirectedGraphWriterMock()
|
||||
let mermaidGraphWriter = DirectedGraphWriterMock()
|
||||
let directedGraphWriterFactory = DirectedGraphWriterFactory(
|
||||
d2GraphWriter: d2GraphWriter,
|
||||
dotGraphWriter: dotGraphWriter,
|
||||
mermaidGraphWriter: mermaidGraphWriter
|
||||
)
|
||||
let command = GraphCommand(projectRootClassifier: ProjectRootClassifierMock(),
|
||||
packageSwiftFileParser: PackageSwiftFileParserMock(),
|
||||
xcodeProjectParser: XcodeProjectParserMock(),
|
||||
|
@ -14,14 +41,20 @@ final class GraphCommandTests: XCTestCase {
|
|||
directedGraphWriterFactory: directedGraphWriterFactory)
|
||||
let fileURL = NSURL.fileURL(withPath: "/Users/simon/Developer/Example")
|
||||
try command.run(withInput: fileURL.path, syntax: .dot)
|
||||
XCTAssertFalse(d2GraphWriter.didWrite)
|
||||
XCTAssertTrue(dotGraphWriter.didWrite)
|
||||
XCTAssertFalse(mermaidGraphWriter.didWrite)
|
||||
}
|
||||
|
||||
func testInvokesOnlyMermaidGraphWriter() throws {
|
||||
let d2GraphWriter = DirectedGraphWriterMock()
|
||||
let dotGraphWriter = DirectedGraphWriterMock()
|
||||
let mermaidGraphWriter = DirectedGraphWriterMock()
|
||||
let directedGraphWriterFactory = DirectedGraphWriterFactory(dotGraphWriter: dotGraphWriter, mermaidGraphWriter: mermaidGraphWriter)
|
||||
let directedGraphWriterFactory = DirectedGraphWriterFactory(
|
||||
d2GraphWriter: d2GraphWriter,
|
||||
dotGraphWriter: dotGraphWriter,
|
||||
mermaidGraphWriter: mermaidGraphWriter
|
||||
)
|
||||
let command = GraphCommand(projectRootClassifier: ProjectRootClassifierMock(),
|
||||
packageSwiftFileParser: PackageSwiftFileParserMock(),
|
||||
xcodeProjectParser: XcodeProjectParserMock(),
|
||||
|
@ -30,7 +63,8 @@ final class GraphCommandTests: XCTestCase {
|
|||
directedGraphWriterFactory: directedGraphWriterFactory)
|
||||
let fileURL = NSURL.fileURL(withPath: "/Users/simon/Developer/Example")
|
||||
try command.run(withInput: fileURL.path, syntax: .mermaid)
|
||||
XCTAssertTrue(mermaidGraphWriter.didWrite)
|
||||
XCTAssertFalse(d2GraphWriter.didWrite)
|
||||
XCTAssertFalse(dotGraphWriter.didWrite)
|
||||
XCTAssertTrue(mermaidGraphWriter.didWrite)
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 544 KiB |
After Width: | Height: | Size: 170 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 691 KiB |
After Width: | Height: | Size: 916 KiB |
Before Width: | Height: | Size: 456 KiB |
Before Width: | Height: | Size: 914 KiB |
Before Width: | Height: | Size: 542 KiB |