package ffmd import ( "fmt" "github.com/peterbourgon/ff/v3/ffcli" "sort" "strings" ) // Tree returns a tree-representation of a ffcli.Command func Tree(cmd *ffcli.Command) string { t := tree{} for _, path := range commandPaths(cmd, "") { t.add(path) } s := t.String(true, "") return strings.TrimSpace(s) } func commandPaths(cmd *ffcli.Command, path string) []string { root := fmt.Sprintf("%s%s", path, cmd.Name) s := []string{root} for _, sub := range cmd.Subcommands { s = append(s, commandPaths(sub, root+"/")...) } sort.SliceStable(s, func(i, j int) bool { return s[i] < s[j] }) return s } type tree map[string]tree func (t tree) add(path string) { t.addParts(strings.Split(path, "/")) } func (t tree) addParts(parts []string) { if len(parts) == 0 { return } next, ok := t[parts[0]] if !ok { next = tree{} t[parts[0]] = next } next.addParts(parts[1:]) } func (t tree) keys() []string { k := make([]string, 0, len(t)) for key, _ := range t { k = append(k, key) } sort.Strings(k) return k } func (t tree) String(root bool, padding string) string { var s strings.Builder index := 0 for _, k := range t.keys() { v := t[k] s.WriteString(fmt.Sprintf("%s%s\n", padding+pipePad(root, pipe(index, len(t))), k)) s.WriteString(v.String(false, padding+pipePad(root, outerPipe(index, len(t))))) index++ } return s.String() } func pipe(index int, len int) string { switch { case index+1 == len: return "└─" case index+1 > len: return " " default: return "├─" } } func outerPipe(index int, len int) string { switch { case index+1 == len: return " " default: return "│ " } } func pipePad(root bool, box string) string { if root { return "" } return box + " " }