on main help sign in
feat(dirs): add some styling to the dirs command
ce08b22Kieran Klukas
internal/cmd/dirs.go+55−74
@@ -1,110 +1,91 @@
1package cmd
1package cmd
2
2
3import (
3import (
4− "fmt"
5 "os"
4 "os"
6 "path/filepath"
5 "path/filepath"
7 "strings"
6 "strings"
8
7
9 "charm.land/lipgloss/v2"
8 "charm.land/lipgloss/v2"
10− "charm.land/lipgloss/v2/table"
11 "github.com/charmbracelet/crush/internal/config"
9 "github.com/charmbracelet/crush/internal/config"
10+ "github.com/charmbracelet/x/exp/charmtone"
12 "github.com/charmbracelet/x/term"
11 "github.com/charmbracelet/x/term"
13 "github.com/spf13/cobra"
12 "github.com/spf13/cobra"
14)
13)
15
14
16var dirsCmd = &cobra.Command{
15var dirsCmd = &cobra.Command{
17 Use: "dirs",
16 Use: "dirs",
18− Short: "Print directories used by Crush",
17+ Short: "Show config and data directories",
19− Long: `Print the directories where Crush stores its configuration and data files.
18+ Long: `Show where Crush stores its configuration and data,
20−This includes the global configuration directory and data directory.`,
19+including any project-level config files discovered
20+from the current directory up to the project root.`,
21 Example: `
21 Example: `
22−# Print all global directories
22+# Show all directories
23crush dirs
23crush dirs
24−
25−# Print data directory and all project specific config directories
26−crush dirs -p
27−
28−# Print only global config directory
29−crush dirs config
30−
31−# Print only project specific config directories
32−crush dirs -p config
33−
34−# Print only the data directory
35−crush dirs data
36 `,
24 `,
37− RunE: func(cmd *cobra.Command, args []string) error {
25+ Run: func(cmd *cobra.Command, args []string) {
38− dirs, err := configDirs(cmd)
26+ entries := collectDirs(cmd)
39− if err != nil {
40− return fmt.Errorf("cannot collect config directories: %w", err)
41− }
42−
43 if term.IsTerminal(os.Stdout.Fd()) {
27 if term.IsTerminal(os.Stdout.Fd()) {
44− // We're in a TTY: make it fancy.
28+ printDirs(cmd, entries)
45− t := table.New().
46− Border(lipgloss.RoundedBorder()).
47− StyleFunc(func(row, col int) lipgloss.Style {
48− return lipgloss.NewStyle().Padding(0, 2)
49− }).
50− Row("Config", dirs).
51− Row("Data", filepath.Dir(config.GlobalConfigData()))
52− lipgloss.Println(t)
53− return nil
29+ return
54 }
30 }
55− // Not a TTY.
31+ for _, e := range entries {
56− cmd.Println(dirs)
32+ cmd.Println(e)
57− cmd.Println(filepath.Dir(config.GlobalConfigData()))
58−
59− return nil
60− },
61−}
62−
63−var configDirCmd = &cobra.Command{
64− Use: "config",
65− Short: "Print the configuration directory used by Crush",
66− RunE: func(cmd *cobra.Command, args []string) error {
67− dirs, err := configDirs(cmd)
68− if err != nil {
69− return fmt.Errorf("cannot collect config directories: %w", err)
70 }
33 }
71− cmd.Println(dirs)
72− return nil
73 },
34 },
74}
35}
75
36
76−var dataDirCmd = &cobra.Command{
77− Use: "data",
78− Short: "Print the datauration directory used by Crush",
79− Run: func(cmd *cobra.Command, args []string) {
37+func collectDirs(cmd *cobra.Command) []string {
80− cmd.Println(filepath.Dir(config.GlobalConfigData()))
38+ var dirs []string
81− },
82−}
83
39
84−// configDirs returns formatted string with one or more project config paths
85−func configDirs(cmd *cobra.Command) (string, error) {
86− configDir := filepath.Dir(config.GlobalConfig())
40+ dirs = append(dirs, filepath.Dir(config.GlobalConfig()))
87− if ok, _ := cmd.Flags().GetBool("project"); !ok {
41+ dirs = append(dirs, filepath.Dir(config.GlobalConfigData()))
88− return configDir, nil
89− }
90
42
91 cwd, err := ResolveCwd(cmd)
43 cwd, err := ResolveCwd(cmd)
92 if err != nil {
44 if err != nil {
93− return "", fmt.Errorf("cannot resolve current working directory: %w", err)
45+ return dirs
94 }
46 }
95
47
96− var sb strings.Builder
97− for i, path := range config.ProjectConfigs(cwd) {
48+ for _, p := range config.ProjectConfigs(cwd) {
98− if i > 0 {
49+ d := filepath.Dir(p)
99− sb.WriteByte('\n')
50+ // Skip global paths, already shown.
51+ if d == filepath.Dir(config.GlobalConfig()) || d == filepath.Dir(config.GlobalConfigData()) {
52+ continue
100 }
53 }
101− sb.WriteString(filepath.Dir(path))
54+ dirs = append(dirs, d)
102 }
55 }
56+
103− return sb.String(), nil
57+ return dirs
104}
58}
105
59
106−func init() {
60+func printDirs(cmd *cobra.Command, dirs []string) {
107− dirsCmd.PersistentFlags().BoolP("project", "p", false, "Print project specific configs")
61+ labelStyle := lipgloss.NewStyle().Bold(true).Foreground(charmtone.Charple)
62+
63+ labels := make([]string, len(dirs))
64+ longest := 0
65+ for i := range dirs {
66+ l := dirLabel(i)
67+ labels[i] = l + ":"
68+ if len(labels[i]) > longest {
69+ longest = len(labels[i])
70+ }
71+ }
72+
73+ for i, d := range dirs {
74+ lipgloss.Println(labelStyle.Render(labels[i]) +
75+ strings.Repeat(" ", longest-len(labels[i])) +
76+ " " + d)
77+ }
78+
79+ lipgloss.Println(lipgloss.NewStyle().Foreground(charmtone.Squid).Render("Configs merge from top to bottom"))
80+}
108
81
109− dirsCmd.AddCommand(configDirCmd, dataDirCmd)
82+func dirLabel(i int) string {
83+ switch i {
84+ case 0:
85+ return "Config"
86+ case 1:
87+ return "Data"
88+ default:
89+ return "Project"
90+ }
110}
91}
internal/cmd/dirs_test.godeleted−48
@@ -1,48 +0,0 @@
1−package cmd
2−
3−import (
4− "bytes"
5− "os"
6− "path/filepath"
7− "testing"
8−
9− "github.com/stretchr/testify/require"
10−)
11−
12−func init() {
13− os.Setenv("XDG_CONFIG_HOME", "/tmp/fakeconfig")
14− os.Setenv("XDG_DATA_HOME", "/tmp/fakedata")
15− os.Unsetenv("CRUSH_GLOBAL_CONFIG")
16− os.Unsetenv("CRUSH_GLOBAL_DATA")
17−}
18−
19−func TestDirs(t *testing.T) {
20− var b bytes.Buffer
21− dirsCmd.SetOut(&b)
22− dirsCmd.SetErr(&b)
23− dirsCmd.SetIn(bytes.NewReader(nil))
24− dirsCmd.Run(dirsCmd, nil)
25− expected := filepath.FromSlash("/tmp/fakeconfig/crush") + "\n" +
26− filepath.FromSlash("/tmp/fakedata/crush") + "\n"
27− require.Equal(t, expected, b.String())
28−}
29−
30−func TestConfigDir(t *testing.T) {
31− var b bytes.Buffer
32− configDirCmd.SetOut(&b)
33− configDirCmd.SetErr(&b)
34− configDirCmd.SetIn(bytes.NewReader(nil))
35− configDirCmd.Run(configDirCmd, nil)
36− expected := filepath.FromSlash("/tmp/fakeconfig/crush") + "\n"
37− require.Equal(t, expected, b.String())
38−}
39−
40−func TestDataDir(t *testing.T) {
41− var b bytes.Buffer
42− dataDirCmd.SetOut(&b)
43− dataDirCmd.SetErr(&b)
44− dataDirCmd.SetIn(bytes.NewReader(nil))
45− dataDirCmd.Run(dataDirCmd, nil)
46− expected := filepath.FromSlash("/tmp/fakedata/crush") + "\n"
47− require.Equal(t, expected, b.String())
48−}