Skip to content

Commit

Permalink
feat: add graph command
Browse files Browse the repository at this point in the history
  • Loading branch information
Antoine Gelloz committed Apr 30, 2024
1 parent a1cc197 commit a29596b
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 65 deletions.
60 changes: 60 additions & 0 deletions cmd/graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"fmt"
"path"

"github.com/radiofrance/dib/internal/logger"
"github.com/radiofrance/dib/pkg/dib"
"github.com/spf13/cobra"
)

type GraphOpts struct {
BuildPath string `mapstructure:"build_path"`
RegistryURL string `mapstructure:"registry_url"`
PlaceholderTag string `mapstructure:"placeholder_tag"`
HashListFilePath string `mapstructure:"hash_list_file_path"`
}

// buildCmd represents the build command.
var graphCmd = &cobra.Command{
Use: "graph",
Short: "Compute the graph of images, and print it.",
Long: "Compute the graph of images, and print it.",
Run: func(cmd *cobra.Command, _ []string) {
bindPFlagsSnakeCase(cmd.Flags())

opts := GraphOpts{}
hydrateOptsFromViper(&opts)

if err := doGraph(opts); err != nil {
logger.Fatalf("Graph failed: %v", err)
}
},
}

func init() {
rootCmd.AddCommand(graphCmd)
}

func doGraph(opts GraphOpts) error {
workingDir, err := getWorkingDir()
if err != nil {
logger.Fatalf("failed to get current working directory: %v", err)
}

buildPath := path.Join(workingDir, opts.BuildPath)
logger.Infof("Building images in directory \"%s\"", buildPath)

logger.Debugf("Generate DAG")
graph, err := dib.GenerateDAG(buildPath, opts.RegistryURL, opts.HashListFilePath, map[string]string{})
if err != nil {
return fmt.Errorf("cannot generate DAG: %w", err)
}
logger.Debugf("Generate DAG -- Done")

logger.Debugf("Print DAG")
graph.Print(opts.BuildPath)
logger.Debugf("Print DAG -- Done")
return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/config v1.27.11
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/docker/cli v26.0.2+incompatible
github.com/google/uuid v1.6.0
github.com/mholt/archiver/v3 v3.5.1
Expand Down Expand Up @@ -56,7 +57,6 @@ require (
github.com/containerd/containerd v1.7.12 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
Expand Down
22 changes: 22 additions & 0 deletions pkg/dag/dag.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package dag

import (
"cmp"
"slices"
"strings"
"sync"

"github.com/radiofrance/dib/internal/logger"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -203,3 +207,21 @@ func createUniqueVisitorErr(visitor NodeVisitorFuncErr) NodeVisitorFuncErr {

return uniqueVisitor
}

func sort(a, b *Node) int {
return cmp.Compare(strings.ToLower(a.Image.ShortName), strings.ToLower(b.Image.ShortName))
}

func (d *DAG) Print(name string) {
d.WalkInDepth(func(node *Node) {
slices.SortFunc(node.Children(), sort)
})
slices.SortFunc(d.nodes, sort)
rootNode := &Node{
Image: &Image{Name: name},
children: d.nodes,
}
if err := DefaultTree.WithRoot(rootNode).Render(); err != nil {
logger.Fatalf(err.Error())
}
}
8 changes: 4 additions & 4 deletions pkg/dag/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (n *Node) Parents() []*Node {
// walk applies the visitor func to the current node, then to every children nodes, recursively.
func (n *Node) walk(visitor NodeVisitorFunc) {
visitor(n)
for _, childNode := range n.children {
for _, childNode := range n.Children() {
childNode.walk(visitor)
}
}
Expand All @@ -63,7 +63,7 @@ func (n *Node) walkErr(visitor NodeVisitorFuncErr) error {
if err != nil {
return err
}
for _, childNode := range n.children {
for _, childNode := range n.Children() {
err = childNode.walkErr(visitor)
if err != nil {
return err
Expand All @@ -79,7 +79,7 @@ func (n *Node) walkAsyncErr(visitor NodeVisitorFuncErr) error {
errG.Go(func() error {
return visitor(n)
})
for _, childNode := range n.children {
for _, childNode := range n.Children() {
errG.Go(func() error {
return childNode.walkAsyncErr(visitor)
})
Expand All @@ -90,7 +90,7 @@ func (n *Node) walkAsyncErr(visitor NodeVisitorFuncErr) error {
// walkInDepth makes a depth-first recursive walk through the graph.
// It applies the visitor func to every children node, then to the current node itself.
func (n *Node) walkInDepth(visitor NodeVisitorFunc) {
for _, childNode := range n.children {
for _, childNode := range n.Children() {
childNode.walkInDepth(visitor)
}
visitor(n)
Expand Down
142 changes: 142 additions & 0 deletions pkg/dag/printer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package dag

import (
"io"
"strings"

"github.com/pterm/pterm"
)

// DefaultTree contains standards, which can be used to render a TreePrinter.
var DefaultTree = TreePrinter{
TreeStyle: &pterm.ThemeDefault.TreeStyle,
TextStyle: &pterm.ThemeDefault.TreeTextStyle,
TopRightCornerString: "└",
HorizontalString: "─",
TopRightDownString: "├",
VerticalString: "│",
RightDownLeftString: "┬",
Indent: 2,
}

// TreePrinter is able to render a list.
type TreePrinter struct {
Root *Node
TreeStyle *pterm.Style
TextStyle *pterm.Style
TopRightCornerString string
TopRightDownString string
HorizontalString string
VerticalString string
RightDownLeftString string
Indent int
Writer io.Writer
}

// WithTreeStyle returns a new list with a specific tree style.
func (p TreePrinter) WithTreeStyle(style *pterm.Style) *TreePrinter {
p.TreeStyle = style
return &p
}

// WithTopRightCornerString returns a new list with a specific TopRightCornerString.
func (p TreePrinter) WithTopRightCornerString(s string) *TreePrinter {
p.TopRightCornerString = s
return &p
}

// WithTopRightDownStringOngoing returns a new list with a specific TopRightDownString.
func (p TreePrinter) WithTopRightDownStringOngoing(s string) *TreePrinter {
p.TopRightDownString = s
return &p
}

// WithHorizontalString returns a new list with a specific HorizontalString.
func (p TreePrinter) WithHorizontalString(s string) *TreePrinter {
p.HorizontalString = s
return &p
}

// WithVerticalString returns a new list with a specific VerticalString.
func (p TreePrinter) WithVerticalString(s string) *TreePrinter {
p.VerticalString = s
return &p
}

// WithRoot returns a new list with a specific Root.
func (p TreePrinter) WithRoot(root *Node) *TreePrinter {
p.Root = root
return &p
}

// WithIndent returns a new list with a specific amount of spacing between the levels.
// Indent must be at least 1.
func (p TreePrinter) WithIndent(indent int) *TreePrinter {
if indent < 1 {
indent = 1
}
p.Indent = indent
return &p
}

// Render prints the list to the terminal.
func (p TreePrinter) Render() error {
s, _ := p.Srender()
pterm.Fprintln(p.Writer, s)

return nil
}

// Srender renders the list as a string.
func (p TreePrinter) Srender() (string, error) {
if p.TreeStyle == nil {
p.TreeStyle = pterm.NewStyle()
}
if p.TextStyle == nil {
p.TextStyle = pterm.NewStyle()
}

var result string
if p.Root.Image.Name != "" {
result += p.TextStyle.Sprint(p.Root.Image.Name) + "\n"
}
result += walkOverTree(p.Root.Children(), p, "")
return result, nil
}

// walkOverTree is a recursive function,
// which analyzes a TreePrinter and connects the items with specific characters.
// Returns TreePrinter as string.
func walkOverTree(nodes []*Node, printer TreePrinter, prefix string) string {
var ret string
for nodeIndex, node := range nodes {
if len(nodes) > nodeIndex+1 { // if not last in nodes
if len(node.Children()) == 0 { // if there are no children
ret += prefix + printer.TreeStyle.Sprint(printer.TopRightDownString) +
strings.Repeat(printer.TreeStyle.Sprint(printer.HorizontalString), printer.Indent) +
printer.TextStyle.Sprint(node.Image.ShortName) + "\n"
} else { // if there are children
ret += prefix + printer.TreeStyle.Sprint(printer.TopRightDownString) +
strings.Repeat(printer.TreeStyle.Sprint(printer.HorizontalString), printer.Indent-1) +
printer.TreeStyle.Sprint(printer.RightDownLeftString) +
printer.TextStyle.Sprint(node.Image.ShortName) + "\n"
ret += walkOverTree(node.Children(), printer,
prefix+printer.TreeStyle.Sprint(printer.VerticalString)+strings.Repeat(" ", printer.Indent-1))
}
} else if len(nodes) == nodeIndex+1 { // if last in nodes
if len(node.Children()) == 0 { // if there are no children
ret += prefix + printer.TreeStyle.Sprint(printer.TopRightCornerString) +
strings.Repeat(printer.TreeStyle.Sprint(printer.HorizontalString), printer.Indent) +
printer.TextStyle.Sprint(node.Image.ShortName) + "\n"
} else { // if there are children
ret += prefix + printer.TreeStyle.Sprint(printer.TopRightCornerString) +
strings.Repeat(printer.TreeStyle.Sprint(printer.HorizontalString), printer.Indent-1) +
printer.TreeStyle.Sprint(printer.RightDownLeftString) +
printer.TextStyle.Sprint(node.Image.ShortName) + "\n"
ret += walkOverTree(node.Children(), printer,
prefix+strings.Repeat(" ", printer.Indent))
}
}
}
return ret
}
Loading

0 comments on commit a29596b

Please sign in to comment.