allow customizing colors and embedding extra info

This commit is contained in:
jguer 2022-09-07 01:03:29 +02:00
parent 0aa18d61d5
commit d53505be37
No known key found for this signature in database
GPG Key ID: 6D6CC9BEA8556B35
2 changed files with 109 additions and 48 deletions

View File

@ -16,6 +16,39 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
) )
type (
packageType int
sourceType int
)
const (
Explicit packageType = iota
Dep
MakeDep
CheckDep
)
const (
AUR sourceType = iota
Sync
Local
Missing
)
var bgColorMap = map[sourceType]string{
AUR: "lightblue",
Sync: "lemonchiffon",
Local: "darkolivegreen1",
Missing: "tomato",
}
var colorMap = map[packageType]string{
Explicit: "black",
Dep: "deeppink",
MakeDep: "navyblue",
CheckDep: "forestgreen",
}
type Grapher struct { type Grapher struct {
dbExecutor db.Executor dbExecutor db.Executor
aurCache *metadata.AURCache aurCache *metadata.AURCache
@ -34,8 +67,8 @@ func NewGrapher(dbExecutor db.Executor, aurCache *metadata.AURCache, fullGraph,
} }
} }
func (g *Grapher) GraphFromSrcInfo(pkgbuild *gosrc.Srcinfo) (*topo.Graph[string], error) { func (g *Grapher) GraphFromSrcInfo(pkgbuild *gosrc.Srcinfo) (*topo.Graph[string, int], error) {
graph := topo.New[string]() graph := topo.New[string, int]()
aurPkgs, err := makeAURPKGFromSrcinfo(g.dbExecutor, pkgbuild) aurPkgs, err := makeAURPKGFromSrcinfo(g.dbExecutor, pkgbuild)
if err != nil { if err != nil {
@ -44,38 +77,56 @@ func (g *Grapher) GraphFromSrcInfo(pkgbuild *gosrc.Srcinfo) (*topo.Graph[string]
for _, pkg := range aurPkgs { for _, pkg := range aurPkgs {
pkg := pkg pkg := pkg
depSlice := ComputeCombinedDepList(&pkg, false, false) g.addDepNodes(&pkg, graph)
g.addNodes(graph, pkg.Name, depSlice)
} }
return graph, nil return graph, nil
} }
func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string], error) { func (g *Grapher) addDepNodes(pkg *aur.Pkg, graph *topo.Graph[string, int]) {
graph := topo.New[string]() if len(pkg.MakeDepends) > 0 {
g.addNodes(graph, pkg.Name, pkg.MakeDepends, MakeDep)
}
if !false && len(pkg.Depends) > 0 {
g.addNodes(graph, pkg.Name, pkg.Depends, Dep)
}
if !false && len(pkg.CheckDepends) > 0 {
g.addNodes(graph, pkg.Name, pkg.CheckDepends, CheckDep)
}
}
func (g *Grapher) GraphFromAURCache(targets []string) (*topo.Graph[string, int], error) {
graph := topo.New[string, int]()
for _, target := range targets { for _, target := range targets {
aurPkgs, _ := g.aurCache.FindPackage(target) aurPkgs, _ := g.aurCache.FindPackage(target)
pkg := provideMenu(g.w, target, aurPkgs, g.noConfirm) pkg := provideMenu(g.w, target, aurPkgs, g.noConfirm)
depSlice := ComputeCombinedDepList(pkg, false, false) if err := graph.Alias(pkg.PackageBase, pkg.Name); err != nil {
g.addNodes(graph, pkg.Name, depSlice) text.Warnln("aur target alias warn:", pkg.PackageBase, pkg.Name, err)
}
graph.SetNodeInfo(pkg.Name, &topo.NodeInfo[int]{Color: colorMap[Explicit], Background: bgColorMap[AUR]})
g.addDepNodes(pkg, graph)
} }
return graph, nil return graph, nil
} }
func (g *Grapher) addNodes( func (g *Grapher) addNodes(
graph *topo.Graph[string], graph *topo.Graph[string, int],
parentPkgName string, parentPkgName string,
deps []string, deps []string,
depType packageType,
) { ) {
for _, depString := range deps { for _, depString := range deps {
depName, _, _ := splitDep(depString) depName, _, _ := splitDep(depString)
if g.dbExecutor.LocalSatisfierExists(depString) { if g.dbExecutor.LocalSatisfierExists(depString) {
if g.fullGraph { if g.fullGraph {
graph.SetNodeInfo(depName, &topo.NodeInfo{Color: "green"}) graph.SetNodeInfo(depName, &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[Local]})
if err := graph.DependOn(depName, parentPkgName); err != nil { if err := graph.DependOn(depName, parentPkgName); err != nil {
text.Warnln(depName, parentPkgName, err) text.Warnln(depName, parentPkgName, err)
} }
@ -98,7 +149,7 @@ func (g *Grapher) addNodes(
text.Warnln("repo dep warn:", depName, parentPkgName, err) text.Warnln("repo dep warn:", depName, parentPkgName, err)
} }
graph.SetNodeInfo(alpmPkg.Name(), &topo.NodeInfo{Color: "blue"}) graph.SetNodeInfo(alpmPkg.Name(), &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[Sync]})
if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && g.fullGraph { if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && g.fullGraph {
newDepsSlice := make([]string, 0, len(newDeps)) newDepsSlice := make([]string, 0, len(newDeps))
@ -106,7 +157,7 @@ func (g *Grapher) addNodes(
newDepsSlice = append(newDepsSlice, newDep.Name) newDepsSlice = append(newDepsSlice, newDep.Name)
} }
g.addNodes(graph, alpmPkg.Name(), newDepsSlice) g.addNodes(graph, alpmPkg.Name(), newDepsSlice, Dep)
} }
continue continue
@ -127,14 +178,14 @@ func (g *Grapher) addNodes(
text.Warnln("aur dep warn:", pkg.PackageBase, parentPkgName, err) text.Warnln("aur dep warn:", pkg.PackageBase, parentPkgName, err)
} }
graph.SetNodeInfo(pkg.PackageBase, &topo.NodeInfo{Color: "lightgreen"}) graph.SetNodeInfo(pkg.PackageBase, &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[AUR]})
g.addDepNodes(pkg, graph)
if newDeps := ComputeCombinedDepList(pkg, false, false); len(newDeps) != 0 {
g.addNodes(graph, pkg.Name, newDeps)
}
continue continue
} }
// no dep found. add as missing
graph.SetNodeInfo(depString, &topo.NodeInfo[int]{Color: colorMap[depType], Background: bgColorMap[Missing]})
} }
} }

View File

@ -15,16 +15,18 @@ type (
DepMap[T comparable] map[T]NodeSet[T] DepMap[T comparable] map[T]NodeSet[T]
) )
type NodeInfo struct { type NodeInfo[V any] struct {
Color string Color string
Background string
Value V
} }
type Graph[T comparable] struct { type Graph[T comparable, V any] struct {
alias AliasMap[T] alias AliasMap[T]
nodes NodeSet[T] nodes NodeSet[T]
// node info map // node info map
nodeInfo map[T]NodeInfo nodeInfo map[T]*NodeInfo[V]
// `dependencies` tracks child -> parents. // `dependencies` tracks child -> parents.
dependencies DepMap[T] dependencies DepMap[T]
@ -33,29 +35,30 @@ type Graph[T comparable] struct {
// Keep track of the nodes of the graph themselves. // Keep track of the nodes of the graph themselves.
} }
func New[T comparable]() *Graph[T] { func New[T comparable, V any]() *Graph[T, V] {
return &Graph[T]{ return &Graph[T, V]{
nodes: make(NodeSet[T]), nodes: make(NodeSet[T]),
dependencies: make(DepMap[T]), dependencies: make(DepMap[T]),
dependents: make(DepMap[T]), dependents: make(DepMap[T]),
alias: make(AliasMap[T]), alias: make(AliasMap[T]),
nodeInfo: make(map[T]NodeInfo), nodeInfo: make(map[T]*NodeInfo[V]),
} }
} }
func (g *Graph[T]) Len() int { func (g *Graph[T, V]) Len() int {
return len(g.nodes) return len(g.nodes)
} }
func (g *Graph[T]) Exists(node T) bool { func (g *Graph[T, V]) Exists(node T) bool {
// check aliases // check aliases
node = g.getAlias(node) node = g.getAlias(node)
_, ok := g.nodes[node] _, ok := g.nodes[node]
return ok return ok
} }
func (g *Graph[T]) Alias(node, alias T) error { func (g *Graph[T, V]) Alias(node, alias T) error {
if alias == node { if alias == node {
return nil return nil
} }
@ -72,26 +75,32 @@ func (g *Graph[T]) Alias(node, alias T) error {
return nil return nil
} }
func (g *Graph[T]) AddNode(node T) { func (g *Graph[T, V]) AddNode(node T) {
node = g.getAlias(node) node = g.getAlias(node)
g.nodes[node] = true g.nodes[node] = true
} }
func (g *Graph[T]) getAlias(node T) T { func (g *Graph[T, V]) getAlias(node T) T {
if aliasNode, ok := g.alias[node]; ok { if aliasNode, ok := g.alias[node]; ok {
return aliasNode return aliasNode
} }
return node return node
} }
func (g *Graph[T]) SetNodeInfo(node T, nodeInfo *NodeInfo) { func (g *Graph[T, V]) SetNodeInfo(node T, nodeInfo *NodeInfo[V]) {
node = g.getAlias(node) node = g.getAlias(node)
g.nodeInfo[node] = *nodeInfo g.nodeInfo[node] = nodeInfo
} }
func (g *Graph[T]) DependOn(child, parent T) error { func (g *Graph[T, V]) GetNodeInfo(node T) *NodeInfo[V] {
node = g.getAlias(node)
return g.nodeInfo[node]
}
func (g *Graph[T, V]) DependOn(child, parent T) error {
child = g.getAlias(child) child = g.getAlias(child)
parent = g.getAlias(parent) parent = g.getAlias(parent)
@ -117,7 +126,7 @@ func (g *Graph[T]) DependOn(child, parent T) error {
return nil return nil
} }
func (g *Graph[T]) String() string { func (g *Graph[T, V]) String() string {
var sb strings.Builder var sb strings.Builder
sb.WriteString("digraph {\n") sb.WriteString("digraph {\n")
sb.WriteString("compound=true;\n") sb.WriteString("compound=true;\n")
@ -126,9 +135,10 @@ func (g *Graph[T]) String() string {
for node := range g.nodes { for node := range g.nodes {
extra := "" extra := ""
if info, ok := g.nodeInfo[node]; ok { if info, ok := g.nodeInfo[node]; ok {
if info.Color != "" { if info.Background != "" || info.Color != "" {
extra = fmt.Sprintf("[color = %s]", info.Color) extra = fmt.Sprintf("[color = %s ,style = filled, fillcolor = %s]", info.Color, info.Background)
} }
} }
@ -144,21 +154,21 @@ func (g *Graph[T]) String() string {
return sb.String() return sb.String()
} }
func (g *Graph[T]) DependsOn(child, parent T) bool { func (g *Graph[T, V]) DependsOn(child, parent T) bool {
deps := g.Dependencies(child) deps := g.Dependencies(child)
_, ok := deps[parent] _, ok := deps[parent]
return ok return ok
} }
func (g *Graph[T]) HasDependent(parent, child T) bool { func (g *Graph[T, V]) HasDependent(parent, child T) bool {
deps := g.Dependents(parent) deps := g.Dependents(parent)
_, ok := deps[child] _, ok := deps[child]
return ok return ok
} }
func (g *Graph[T]) Leaves() []T { func (g *Graph[T, V]) Leaves() []T {
leaves := make([]T, 0) leaves := make([]T, 0)
for node := range g.nodes { for node := range g.nodes {
@ -177,7 +187,7 @@ func (g *Graph[T]) Leaves() []T {
// any dependencies within each layer. This is useful, e.g. when building an execution plan for // any dependencies within each layer. This is useful, e.g. when building an execution plan for
// some DAG, in which case each element within each layer could be executed in parallel. If you // some DAG, in which case each element within each layer could be executed in parallel. If you
// do not need this layered property, use `Graph.TopoSorted()`, which flattens all elements. // do not need this layered property, use `Graph.TopoSorted()`, which flattens all elements.
func (g *Graph[T]) TopoSortedLayers() [][]T { func (g *Graph[T, V]) TopoSortedLayers() [][]T {
layers := [][]T{} layers := [][]T{}
// Copy the graph // Copy the graph
@ -210,7 +220,7 @@ func (dm DepMap[T]) removeFromDepmap(key, node T) {
} }
} }
func (g *Graph[T]) remove(node T) { func (g *Graph[T, V]) remove(node T) {
// Remove edges from things that depend on `node`. // Remove edges from things that depend on `node`.
for dependent := range g.dependents[node] { for dependent := range g.dependents[node] {
g.dependencies.removeFromDepmap(dependent, node) g.dependencies.removeFromDepmap(dependent, node)
@ -231,7 +241,7 @@ func (g *Graph[T]) remove(node T) {
// TopoSorted returns all the nodes in the graph is topological sort order. // TopoSorted returns all the nodes in the graph is topological sort order.
// See also `Graph.TopoSortedLayers()`. // See also `Graph.TopoSortedLayers()`.
func (g *Graph[T]) TopoSorted() []T { func (g *Graph[T, V]) TopoSorted() []T {
nodeCount := 0 nodeCount := 0
layers := g.TopoSortedLayers() layers := g.TopoSortedLayers()
@ -248,24 +258,24 @@ func (g *Graph[T]) TopoSorted() []T {
return allNodes return allNodes
} }
func (g *Graph[T]) Dependencies(child T) NodeSet[T] { func (g *Graph[T, V]) Dependencies(child T) NodeSet[T] {
return g.buildTransitive(child, g.immediateDependencies) return g.buildTransitive(child, g.immediateDependencies)
} }
func (g *Graph[T]) immediateDependencies(node T) NodeSet[T] { func (g *Graph[T, V]) immediateDependencies(node T) NodeSet[T] {
return g.dependencies[node] return g.dependencies[node]
} }
func (g *Graph[T]) Dependents(parent T) NodeSet[T] { func (g *Graph[T, V]) Dependents(parent T) NodeSet[T] {
return g.buildTransitive(parent, g.immediateDependents) return g.buildTransitive(parent, g.immediateDependents)
} }
func (g *Graph[T]) immediateDependents(node T) NodeSet[T] { func (g *Graph[T, V]) immediateDependents(node T) NodeSet[T] {
return g.dependents[node] return g.dependents[node]
} }
func (g *Graph[T]) clone() *Graph[T] { func (g *Graph[T, V]) clone() *Graph[T, V] {
return &Graph[T]{ return &Graph[T, V]{
dependencies: g.dependencies.copy(), dependencies: g.dependencies.copy(),
dependents: g.dependents.copy(), dependents: g.dependents.copy(),
nodes: g.nodes.copy(), nodes: g.nodes.copy(),
@ -274,7 +284,7 @@ func (g *Graph[T]) clone() *Graph[T] {
// buildTransitive starts at `root` and continues calling `nextFn` to keep discovering more nodes until // buildTransitive starts at `root` and continues calling `nextFn` to keep discovering more nodes until
// the graph cannot produce any more. It returns the set of all discovered nodes. // the graph cannot produce any more. It returns the set of all discovered nodes.
func (g *Graph[T]) buildTransitive(root T, nextFn func(T) NodeSet[T]) NodeSet[T] { func (g *Graph[T, V]) buildTransitive(root T, nextFn func(T) NodeSet[T]) NodeSet[T] {
if _, ok := g.nodes[root]; !ok { if _, ok := g.nodes[root]; !ok {
return nil return nil
} }