Skip to content

Commit cd4c942

Browse files
authored
Merge pull request #36 from RinkiyaKeDad/24_graph_for_arg_dep
supporting graph generation based on input dependency
2 parents 863c8de + 94720db commit cd4c942

File tree

5 files changed

+266
-89
lines changed

5 files changed

+266
-89
lines changed

cmd/cycles.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ var cyclesCmd = &cobra.Command{
3333
RunE: func(cmd *cobra.Command, args []string) error {
3434
depGraph, _, mainModule := getDepInfo()
3535
var cycleChains []Chain
36-
var chains []Chain
3736
var temp Chain
38-
getChains(mainModule, depGraph, temp, &chains, &cycleChains)
37+
getCycleChains(mainModule, depGraph, temp, &cycleChains)
3938
cycles := getCycles(cycleChains)
4039

4140
if !jsonOutputCycles {
@@ -60,6 +59,23 @@ var cyclesCmd = &cobra.Command{
6059
},
6160
}
6261

62+
// get all chains which have a cycle
63+
func getCycleChains(currentDep string, graph map[string][]string, currentChain Chain, cycleChains *[]Chain) {
64+
currentChain = append(currentChain, currentDep)
65+
_, ok := graph[currentDep]
66+
if ok {
67+
for _, dep := range graph[currentDep] {
68+
if !contains(currentChain, dep) {
69+
cpy := make(Chain, len(currentChain))
70+
copy(cpy, currentChain)
71+
getCycleChains(dep, graph, cpy, cycleChains)
72+
} else {
73+
*cycleChains = append(*cycleChains, append(currentChain, dep))
74+
}
75+
}
76+
}
77+
}
78+
6379
// gets the cycles from the cycleChains
6480
func getCycles(cycleChains []Chain) []Chain {
6581
var cycles []Chain

cmd/graph.go

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,33 @@ package cmd
1919
import (
2020
"fmt"
2121
"io/ioutil"
22+
"strings"
2223

2324
"github.com/spf13/cobra"
2425
)
2526

27+
var dep string
28+
2629
var graphCmd = &cobra.Command{
2730
Use: "graph",
2831
Short: "Generate a .dot file to be used with Graphviz's dot command.",
2932
Long: `A graph.dot file will be generated which can be used with Graphviz's dot command.
3033
For example to generate a svg image use:
3134
twopi -Tsvg -o dag.svg graph.dot`,
3235
RunE: func(cmd *cobra.Command, args []string) error {
33-
depGraph, deps, _ := getDepInfo()
34-
fileContents := "digraph {\ngraph [rankdir=TB, overlap=false];\n"
35-
for _, dep := range deps {
36-
_, ok := depGraph[dep]
37-
if !ok {
38-
continue
39-
}
40-
for _, neighbour := range depGraph[dep] {
41-
fileContents += fmt.Sprintf("\"%s\" -> \"%s\"\n", dep, neighbour)
42-
}
36+
depGraph, deps, mainModule := getDepInfo()
37+
// strict ensures that there is only one edge between two vertices
38+
// overlap = false ensures the vertices don't overlap
39+
fileContents := "strict digraph {\ngraph [overlap=false];\n"
40+
41+
// graph to be generated is based around input dep
42+
if dep != "" {
43+
var chains []Chain
44+
var temp Chain
45+
getAllChains(mainModule, depGraph, temp, &chains)
46+
fileContents += getFileContentsForSingleDep(chains, dep)
47+
} else {
48+
fileContents += getFileContentsForAllDeps(deps, depGraph, mainModule)
4349
}
4450
fileContents += "}"
4551
fileContentsByte := []byte(fileContents)
@@ -52,6 +58,86 @@ var graphCmd = &cobra.Command{
5258
},
5359
}
5460

61+
// find all possible chains starting from currentDep
62+
func getAllChains(currentDep string, graph map[string][]string, currentChain Chain, chains *[]Chain) {
63+
currentChain = append(currentChain, currentDep)
64+
_, ok := graph[currentDep]
65+
if ok {
66+
for _, dep := range graph[currentDep] {
67+
if !contains(currentChain, dep) {
68+
cpy := make(Chain, len(currentChain))
69+
copy(cpy, currentChain)
70+
getAllChains(dep, graph, cpy, chains)
71+
} else {
72+
*chains = append(*chains, currentChain)
73+
}
74+
}
75+
} else {
76+
*chains = append(*chains, currentChain)
77+
}
78+
}
79+
80+
// get the contents of the .dot file for the graph
81+
// when the -d flag is set
82+
func getFileContentsForSingleDep(chains []Chain, dep string) string {
83+
// to color the entered node as yellow
84+
data := colorMainNode(dep)
85+
86+
// add all chains which have the input dep to the .dot file
87+
for _, chain := range chains {
88+
if chainContains(chain, dep) {
89+
for i := range chain {
90+
if chain[i] == dep {
91+
chain[i] = "MainNode"
92+
} else {
93+
chain[i] = "\"" + chain[i] + "\""
94+
}
95+
}
96+
data += strings.Join(chain, " -> ")
97+
data += "\n"
98+
}
99+
}
100+
return data
101+
}
102+
103+
// get the contents of the .dot file for the graph
104+
// of all dependencies (when -d is not set)
105+
func getFileContentsForAllDeps(deps []string, depGraph map[string][]string, mainModule string) string {
106+
107+
// color the main module as yellow
108+
data := colorMainNode(mainModule)
109+
for _, dep := range deps {
110+
_, ok := depGraph[dep]
111+
if !ok {
112+
continue
113+
}
114+
// main module can never be a neighbour
115+
for _, neighbour := range depGraph[dep] {
116+
if dep == mainModule {
117+
// for the main module use a colored node
118+
data += fmt.Sprintf("\"MainNode\" -> \"%s\"\n", neighbour)
119+
} else {
120+
data += fmt.Sprintf("\"%s\" -> \"%s\"\n", dep, neighbour)
121+
}
122+
}
123+
}
124+
return data
125+
}
126+
127+
func chainContains(chain Chain, dep string) bool {
128+
for _, d := range chain {
129+
if d == dep {
130+
return true
131+
}
132+
}
133+
return false
134+
}
135+
136+
func colorMainNode(mainNode string) string {
137+
return fmt.Sprintf("MainNode [label=\"%s\", style=\"filled\" color=\"yellow\"]\n", mainNode)
138+
}
139+
55140
func init() {
56141
rootCmd.AddCommand(graphCmd)
142+
graphCmd.Flags().StringVarP(&dep, "dep", "d", "", "Specify dependency to create a graph around")
57143
}

cmd/stats.go

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,14 @@ var statsCmd = &cobra.Command{
3939
RunE: func(cmd *cobra.Command, args []string) error {
4040
depGraph, deps, mainModule := getDepInfo()
4141

42-
// Get all chains starting from main module
43-
// also get all cycles
44-
// cycleChains stores the chain containing the cycles and
45-
// not the actual cycle itself
46-
var cycleChains []Chain
47-
var chains []Chain
42+
// get the longest chain
43+
var longestChain Chain
4844
var temp Chain
49-
getChains(mainModule, depGraph, temp, &chains, &cycleChains)
45+
getLongestChain(mainModule, depGraph, temp, &longestChain)
5046

5147
// get values
5248
totalDeps := len(deps)
53-
maxDepth := getMaxDepth(chains)
49+
maxDepth := len(longestChain)
5450
directDeps := len(depGraph[mainModule])
5551
transitiveDeps := totalDeps - directDeps
5652

@@ -68,9 +64,7 @@ var statsCmd = &cobra.Command{
6864
// print the longest chain
6965
if verbose {
7066
fmt.Println("Longest chain/s: ")
71-
for _, chain := range getLongestChains(maxDepth, chains) {
72-
printChain(chain)
73-
}
67+
printChain(longestChain)
7468
}
7569

7670
if jsonOutput {
@@ -94,24 +88,27 @@ var statsCmd = &cobra.Command{
9488
},
9589
}
9690

97-
// get the length of the longest dependency chain
98-
func getMaxDepth(chains []Chain) int {
99-
maxDeps := 0
100-
for _, chain := range chains {
101-
maxDeps = max(maxDeps, len(chain))
102-
}
103-
// for A -> B -> C the depth is 3
104-
return maxDeps
105-
}
106-
107-
func getLongestChains(maxDepth int, chains []Chain) []Chain {
108-
var longestChains []Chain
109-
for _, chain := range chains {
110-
if len(chain) == maxDepth {
111-
longestChains = append(longestChains, chain)
91+
// get the longest chain starting from currentDep
92+
func getLongestChain(currentDep string, graph map[string][]string, currentChain Chain, longestChain *Chain) {
93+
currentChain = append(currentChain, currentDep)
94+
_, ok := graph[currentDep]
95+
if ok {
96+
for _, dep := range graph[currentDep] {
97+
if !contains(currentChain, dep) {
98+
cpy := make(Chain, len(currentChain))
99+
copy(cpy, currentChain)
100+
getLongestChain(dep, graph, cpy, longestChain)
101+
} else {
102+
if len(currentChain) > len(*longestChain) {
103+
*longestChain = currentChain
104+
}
105+
}
106+
}
107+
} else {
108+
if len(currentChain) > len(*longestChain) {
109+
*longestChain = currentChain
112110
}
113111
}
114-
return longestChains
115112
}
116113

117114
func init() {

cmd/utils.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,6 @@ import (
2525
"strings"
2626
)
2727

28-
func max(x, y int) int {
29-
if x <= y {
30-
return y
31-
}
32-
return x
33-
}
34-
35-
// find all possible chains starting from currentDep
36-
func getChains(currentDep string, graph map[string][]string, longestPath Chain, chains *[]Chain, cycleChains *[]Chain) {
37-
longestPath = append(longestPath, currentDep)
38-
_, ok := graph[currentDep]
39-
if ok {
40-
for _, dep := range graph[currentDep] {
41-
if !contains(longestPath, dep) {
42-
cpy := make(Chain, len(longestPath))
43-
copy(cpy, longestPath)
44-
getChains(dep, graph, cpy, chains, cycleChains)
45-
} else {
46-
//chains[len(longestPath)] = append(chains[len(longestPath)], longestPath)
47-
*chains = append(*chains, longestPath)
48-
*cycleChains = append(*cycleChains, append(longestPath, dep))
49-
}
50-
}
51-
} else {
52-
*chains = append(*chains, longestPath)
53-
//chains[len(longestPath)] = append(chains[len(longestPath)], longestPath)
54-
}
55-
}
56-
5728
func printChain(slice []string) {
5829
fmt.Println()
5930
fmt.Println(strings.Join(slice, " -> "))

0 commit comments

Comments
 (0)