Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea
.zed

*.dot
8 changes: 8 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ linters:
deny:
- pkg: github.com/pkg/errors
desc: Should be replaced by standard lib errors package.
errcheck:
exclude-functions:
- (*os.File).Close
- (*bufio.Writer).Flush
- (*bufio.Reader).Close
- io.Copy
- fmt.Print.*
- log.Print.*
dupl:
threshold: 150
funlen:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (c *Container) MustResolve(target interface{})
- [x] Interface binding support
- [x] Error handling
- [ ] Named dependency resolution
- [ ] Dependency graph visualization
- [x] Dependency graph visualization

## 📊 Benefits

Expand Down
85 changes: 85 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
package compoapp

import (
"bufio"
"fmt"
"os"
"reflect"
"sync"
)
Expand Down Expand Up @@ -443,3 +445,86 @@ func (c *Container) validateDependencies() error {
}
return nil
}

const dotHeader string = `digraph DependencyGraph {
rankdir=LR;
node [shape=box, style=rounded, fontname="Arial"];
edge [fontname="Arial"];

`

// Visualize creates .dot file for graphviz visualization
func (c *Container) Visualize(filepath string) error {
c.mu.RLock()
defer c.mu.RUnlock()
//nolint:gosec
f, err := os.Create(filepath)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer f.Close()

writer := bufio.NewWriter(f)
defer writer.Flush()

// Write DOT header
if _, err := writer.WriteString(dotHeader); err != nil {
return fmt.Errorf("failed to write digraph header: %w", err)
}

nodes := make(map[string]struct{})
edges := make(map[string][]string)

// Process dependencies
for componentName, deps := range c.graph.dependencies {
from := componentName.String()
nodes[from] = struct{}{}
for _, dep := range deps {
to := dep.String()
nodes[to] = struct{}{}
edges[from] = append(edges[from], to)
}
}

// Process dependents (reverse dependencies)
for componentName, dependents := range c.graph.dependents {
to := componentName.String()
nodes[to] = struct{}{}
for _, dep := range dependents {
from := dep.String()
nodes[from] = struct{}{}
edges[from] = append(edges[from], to)
}
}

for nodeName := range nodes {
if _, err := fmt.Fprintf(writer, " %q;\n", nodeName); err != nil {
return fmt.Errorf("failed to write node name: %w", err)
}
}

if _, err := writer.WriteString("\n"); err != nil {
return fmt.Errorf("failed to write newline: %w", err)
}

addedEdges := make(map[string]struct{})
for from, toList := range edges {
for _, to := range toList {
edgeKey := from + "->" + to
if _, exists := addedEdges[edgeKey]; exists {
continue
}
if _, err := fmt.Fprintf(writer, " %q -> %q;\n", from, to); err != nil {
return fmt.Errorf("failed to write edge: %w", err)
}
addedEdges[edgeKey] = struct{}{}
}
}

// Close DOT graph
if _, err := writer.WriteString("}\n"); err != nil {
return fmt.Errorf("failed to write graph closure: %w", err)
}

return nil
}
4 changes: 4 additions & 0 deletions samples/interfaces/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ func main() {

var app *App
container.MustResolve(&app)

if err := container.Visualize("graph.dot"); err != nil {
panic(err)
}
}