From cd7a26eb23a18379a98066483f74f0966749eb27 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 28 Oct 2024 13:21:59 +0100 Subject: [PATCH] implement treewriter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing mergeable here, but this was me playing around; initially to see if we could print the format such as suggested in https://github.com/docker/cli/issues/5560#issuecomment-2437212303 That output is shown in `TestTree`: IMAGE/TAGS ID DISK USAGE CONTENT SIZE USED alpine:latest beefdbd8a1da 13.6MB 4.09MB ├─ linux/amd64 33735bd63cf8 0B 0B ├─ linux/arm/v6 50f635c8b04d 0B 0B ├─ linux/arm/v7 f2f82d424957 0B 0B ├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB ├─ linux/386 b3e87f642f5c 0B 0B ├─ linux/ppc64le c7a6800e3dc5 0B 0B ├─ linux/riscv64 80cde017a105 0B 0B └─ linux/s390x 2b5b26e09ca2 0B 0B namespace/image beefdbd8a1da 13.6MB 4.09MB ├─ namespace/image:1 beefdbd8a1da - - ├─ namespace/image:1.0 beefdbd8a1da - - ├─ namespace/image:1.0.0 beefdbd8a1da - - └─ namespace/image:latest beefdbd8a1da - - ├─ linux/amd64 33735bd63cf8 0B 0B ├─ linux/arm/v6 50f635c8b04d 0B 0B ├─ linux/arm/v7 f2f82d424957 0B 0B ├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB ├─ linux/386 b3e87f642f5c 0B 0B ├─ linux/ppc64le c7a6800e3dc5 0B 0B ├─ linux/riscv64 80cde017a105 0B 0B └─ linux/s390x 2b5b26e09ca2 0B 0B internal.example.com/namespace/image beefdbd8a1da 13.6MB 4.09MB ├─ internal.example.com/namespace/image:1 beefdbd8a1da - - ├─ internal.example.com/namespace/image:1.0 beefdbd8a1da - - ├─ internal.example.com/namespace/image:1.0.0 beefdbd8a1da - - └─ internal.example.com/namespace/image:latest beefdbd8a1da - - ├─ linux/amd64 33735bd63cf8 0B 0B ├─ linux/arm/v6 50f635c8b04d 0B 0B ├─ linux/arm/v7 f2f82d424957 0B 0B ├─ linux/arm64/v8 9cee2b382fe2 13.6MB 4.09MB ├─ linux/386 b3e87f642f5c 0B 0B ├─ linux/ppc64le c7a6800e3dc5 0B 0B ├─ linux/riscv64 80cde017a105 0B 0B └─ linux/s390x 2b5b26e09ca2 0B 0B The second bit was to see if we could make the tree output more align with other output formats; - Most of our commands allow passing a `--format`, including for (e.g.) `table` - We want the tree view to also support, e.g. `--no-trunc`, which means that some columns will be wider. - If we use a tabwriter for printing, we can have it handle the column-sizing for us. - And if we do, we could let the user pass a custom format, and still print it as a tree. e.g., a format could be; --format 'tree {.Image}}\t{{.Digest}}\t{{.InUse}}' Which would output something like IMAGE ID USED alpine:latest beefdbd8a1da ✔ ├─ linux/amd64 33735bd63cf8 ├─ linux/arm/v6 50f635c8b04d ├─ linux/arm/v7 f2f82d424957 ├─ linux/arm64/v8 9cee2b382fe2 ✔ ├─ linux/386 b3e87f642f5c ├─ linux/ppc64le c7a6800e3dc5 ├─ linux/riscv64 80cde017a105 └─ linux/s390x 2b5b26e09ca2 The `TestTree` implementation is really quirky though, as it uses a `[][]string`, which won't work well if we want to make it more generic (with an "unknown" depth); probably needs some type defined that has an optional slice for child rows. Signed-off-by: Sebastiaan van Stijn --- .../formatter/treewriter/treewriter.go | 1 + .../formatter/treewriter/treewriter_test.go | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 cli/command/formatter/treewriter/treewriter.go create mode 100644 cli/command/formatter/treewriter/treewriter_test.go diff --git a/cli/command/formatter/treewriter/treewriter.go b/cli/command/formatter/treewriter/treewriter.go new file mode 100644 index 000000000000..b7349e483192 --- /dev/null +++ b/cli/command/formatter/treewriter/treewriter.go @@ -0,0 +1 @@ +package treewriter diff --git a/cli/command/formatter/treewriter/treewriter_test.go b/cli/command/formatter/treewriter/treewriter_test.go new file mode 100644 index 000000000000..1e5462ead8f6 --- /dev/null +++ b/cli/command/formatter/treewriter/treewriter_test.go @@ -0,0 +1,88 @@ +package treewriter + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/docker/cli/cli/command/formatter/tabwriter" +) + +func TestTree(t *testing.T) { + var header []string + var rows [][]string + + header = []string{"IMAGE/TAGS", "ID", "DISK USAGE", "CONTENT SIZE", "USED"} + + // TODO(thaJeztah): using [][]string doesn't work well for this; we probably + // need to create a type for this that has "optional" child records that + // we can recurse over to build the tree. + rows = append(rows, header) + rows = append(rows, [][]string{ + {"", "alpine:latest", "beefdbd8a1da", "13.6MB", "4.09MB"}, + {"├─", "linux/amd64", "33735bd63cf8", "0B", "0B"}, + {"├─", "linux/arm/v6", "50f635c8b04d", "0B", "0B"}, + {"├─", "linux/arm/v7", "f2f82d424957", "0B", "0B"}, + {"├─", "linux/arm64/v8", "9cee2b382fe2", "13.6MB", "4.09MB"}, + {"├─", "linux/386", "b3e87f642f5c", "0B", "0B"}, + {"├─", "linux/ppc64le", "c7a6800e3dc5", "0B", "0B"}, + {"├─", "linux/riscv64", "80cde017a105", "0B", "0B"}, + {"└─", "linux/s390x", "2b5b26e09ca2", "0B", "0B"}, + + {"", "namespace/image", "beefdbd8a1da", "13.6MB", "4.09MB"}, + {"├─", "namespace/image:1", "beefdbd8a1da", "-", "-"}, + {"├─", "namespace/image:1.0", "beefdbd8a1da", "-", "-"}, + {"├─", "namespace/image:1.0.0", "beefdbd8a1da", "-", "-"}, + {"└─", "namespace/image:latest", "beefdbd8a1da", "-", "-"}, + {" ├─", "linux/amd64", "33735bd63cf8", "0B", "0B"}, + {" ├─", "linux/arm/v6", "50f635c8b04d", "0B", "0B"}, + {" ├─", "linux/arm/v7", "f2f82d424957", "0B", "0B"}, + {" ├─", "linux/arm64/v8", "9cee2b382fe2", "13.6MB", "4.09MB"}, + {" ├─", "linux/386", "b3e87f642f5c", "0B", "0B"}, + {" ├─", "linux/ppc64le", "c7a6800e3dc5", "0B", "0B"}, + {" ├─", "linux/riscv64", "80cde017a105", "0B", "0B"}, + {" └─", "linux/s390x", "2b5b26e09ca2", "0B", "0B"}, + + {"", "internal.example.com/namespace/image", "beefdbd8a1da", "13.6MB", "4.09MB"}, + {"├─", "internal.example.com/namespace/image:1", "beefdbd8a1da", "-", "-"}, + {"├─", "internal.example.com/namespace/image:1.0", "beefdbd8a1da", "-", "-"}, + {"├─", "internal.example.com/namespace/image:1.0.0", "beefdbd8a1da", "-", "-"}, + {"└─", "internal.example.com/namespace/image:latest", "beefdbd8a1da", "-", "-"}, + {" ├─", "linux/amd64", "33735bd63cf8", "0B", "0B"}, + {" ├─", "linux/arm/v6", "50f635c8b04d", "0B", "0B"}, + {" ├─", "linux/arm/v7", "f2f82d424957", "0B", "0B"}, + {" ├─", "linux/arm64/v8", "9cee2b382fe2", "13.6MB", "4.09MB"}, + {" ├─", "linux/386", "b3e87f642f5c", "0B", "0B"}, + {" ├─", "linux/ppc64le", "c7a6800e3dc5", "0B", "0B"}, + {" ├─", "linux/riscv64", "80cde017a105", "0B", "0B"}, + {" └─", "linux/s390x", "2b5b26e09ca2", "0B", "0B"}, + }...) + + buf := bytes.NewBuffer(nil) + // buf.WriteString("Create a context\n\nDocker endpoint config:\n\n") + tw := tabwriter.NewWriter(buf, 20, 1, 3, ' ', 0) + for rowNum, cols := range rows { + if len(cols) == 0 { + continue + } + + var treeFix string + treeFix, cols = cols[0], cols[1:] + if treeFix == "" { + // Start of new group + if rowNum > 1 { + // Print an empty line between groups. We need to write a tab + // for each column for the tab-writer to give all groups equal + // widths. + _, _ = fmt.Fprintln(tw, strings.Repeat("\t", len(cols))) + } + _, _ = fmt.Fprintln(tw, strings.Join(cols, "\t")) + } else { + _, _ = fmt.Fprintln(tw, treeFix, strings.Join(cols, "\t")) + } + + } + _ = tw.Flush() + fmt.Println(buf.String()) +}