Skip to content

Commit 1c97e2a

Browse files
authored
Merge pull request #373 from lets-cli/self-config
Add self config commands
2 parents c095457 + c7794ad commit 1c97e2a

6 files changed

Lines changed: 184 additions & 5 deletions

File tree

docs/docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ title: Changelog
55

66
## [Unreleased](https://github.com/lets-cli/lets/releases/tag/v0.0.X)
77

8+
* `[Added]` Add `lets self config path` and `lets self config edit` for user settings. Issue [#370](https://github.com/lets-cli/lets/issues/370)
89
* `[Added]` Show interactive download progress for remote configs and remote mixins. Issue [#360](https://github.com/lets-cli/lets/issues/360)
910
* `[Fixed]` Make `--no-cache` re-download remote mixins for local and remote configs. Issue [#365](https://github.com/lets-cli/lets/issues/365)
1011
* `[Added]` Remote config support: `lets -c https://url` downloads and caches config to `~/.config/lets/remote-configs/`. Use `--no-cache` to force re-download.

docs/docs/settings.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ Use settings for things like colored output, theming, or update notifications. D
1717

1818
This file is per-user and applies to all projects on the machine.
1919

20+
Print the path:
21+
22+
```bash
23+
lets self config path
24+
```
25+
26+
Open the file in `$EDITOR`:
27+
28+
```bash
29+
lets self config edit
30+
```
31+
2032
## Precedence
2133

2234
Settings are resolved in this order:

internal/cmd/root_test.go

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bytes"
55
"context"
66
"errors"
7+
"os"
8+
"path/filepath"
79
"strings"
810
"testing"
911

@@ -218,7 +220,7 @@ func TestSelfCmd(t *testing.T) {
218220
t.Run("should use help func when run without args", func(t *testing.T) {
219221
rootCmd := CreateRootCommand("v0.0.0-test", "")
220222
rootCmd.SetArgs([]string{"self"})
221-
initSelfCmd(rootCmd, "v0.0.0-test", func(string) error { return nil })
223+
initSelfCmd(rootCmd, "v0.0.0-test", func(string) error { return nil }, func(string) error { return nil })
222224

223225
called := false
224226
rootCmd.SetHelpFunc(func(c *cobra.Command, args []string) {
@@ -236,6 +238,68 @@ func TestSelfCmd(t *testing.T) {
236238
}
237239
})
238240

241+
t.Run("should print user config path", func(t *testing.T) {
242+
home := t.TempDir()
243+
t.Setenv("HOME", home)
244+
bufOut := new(bytes.Buffer)
245+
246+
rootCmd := CreateRootCommand("v0.0.0-test", "")
247+
rootCmd.SetArgs([]string{"self", "config", "path"})
248+
rootCmd.SetOut(bufOut)
249+
rootCmd.SetErr(bufOut)
250+
initSelfCmd(rootCmd, "v0.0.0-test", func(string) error { return nil }, func(string) error { return nil })
251+
252+
if err := rootCmd.Execute(); err != nil {
253+
t.Fatalf("unexpected error: %v", err)
254+
}
255+
256+
expected := filepath.Join(home, ".config", "lets", "config.yaml") + "\n"
257+
if bufOut.String() != expected {
258+
t.Fatalf("expected %q, got %q", expected, bufOut.String())
259+
}
260+
})
261+
262+
t.Run("should open user config in editor", func(t *testing.T) {
263+
home := t.TempDir()
264+
t.Setenv("HOME", home)
265+
bufOut := new(bytes.Buffer)
266+
called := false
267+
gotPath := ""
268+
269+
rootCmd := CreateRootCommand("v0.0.0-test", "")
270+
rootCmd.SetArgs([]string{"self", "config", "edit"})
271+
rootCmd.SetOut(bufOut)
272+
rootCmd.SetErr(bufOut)
273+
initSelfCmd(rootCmd, "v0.0.0-test", func(string) error { return nil }, func(path string) error {
274+
called = true
275+
gotPath = path
276+
277+
return nil
278+
})
279+
280+
if err := rootCmd.Execute(); err != nil {
281+
t.Fatalf("unexpected error: %v", err)
282+
}
283+
284+
expected := filepath.Join(home, ".config", "lets", "config.yaml")
285+
if !called {
286+
t.Fatal("expected editor to be called")
287+
}
288+
289+
if gotPath != expected {
290+
t.Fatalf("expected editor path %q, got %q", expected, gotPath)
291+
}
292+
293+
info, err := os.Stat(filepath.Dir(expected))
294+
if err != nil {
295+
t.Fatalf("expected config directory to exist: %v", err)
296+
}
297+
298+
if perm := info.Mode().Perm(); perm != 0o700 {
299+
t.Fatalf("expected config directory permissions 0700, got %o", perm)
300+
}
301+
})
302+
239303
t.Run("should open documentation in browser", func(t *testing.T) {
240304
bufOut := new(bytes.Buffer)
241305
called := false
@@ -252,7 +316,7 @@ func TestSelfCmd(t *testing.T) {
252316
rootCmd.SetArgs([]string{"self", "doc"})
253317
rootCmd.SetOut(bufOut)
254318
rootCmd.SetErr(bufOut)
255-
initSelfCmd(rootCmd, "v0.0.0-test", openURL)
319+
initSelfCmd(rootCmd, "v0.0.0-test", openURL, func(string) error { return nil })
256320

257321
err := rootCmd.Execute()
258322
if err != nil {
@@ -279,7 +343,7 @@ func TestSelfCmd(t *testing.T) {
279343
rootCmd.SetArgs([]string{"self", "doc"})
280344
rootCmd.SetOut(bufOut)
281345
rootCmd.SetErr(bufOut)
282-
initSelfCmd(rootCmd, "v0.0.0-test", openURL)
346+
initSelfCmd(rootCmd, "v0.0.0-test", openURL, func(string) error { return nil })
283347

284348
err := rootCmd.Execute()
285349
if err == nil {

internal/cmd/self.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import (
77

88
// InitSelfCmd intializes root 'self' subcommand.
99
func InitSelfCmd(rootCmd *cobra.Command, version string) {
10-
initSelfCmd(rootCmd, version, util.OpenURL)
10+
initSelfCmd(rootCmd, version, util.OpenURL, util.OpenEditor)
1111
}
1212

13-
func initSelfCmd(rootCmd *cobra.Command, version string, openURL func(string) error) {
13+
func initSelfCmd(
14+
rootCmd *cobra.Command, version string, openURL func(string) error, openEditor func(string) error,
15+
) {
1416
selfCmd := &cobra.Command{
1517
Use: "self",
1618
Hidden: false,
@@ -24,6 +26,7 @@ func initSelfCmd(rootCmd *cobra.Command, version string, openURL func(string) er
2426

2527
rootCmd.AddCommand(selfCmd)
2628

29+
selfCmd.AddCommand(initConfigCommand(openEditor))
2730
selfCmd.AddCommand(initDocCommand(openURL))
2831
selfCmd.AddCommand(initLspCommand(version))
2932
selfCmd.AddCommand(initSkillsCommand())

internal/cmd/self_config.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/lets-cli/lets/internal/util"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func initConfigCommand(openEditor func(string) error) *cobra.Command {
14+
configCmd := &cobra.Command{
15+
Use: "config <command>",
16+
Short: "Manage lets user config",
17+
Long: strings.TrimSpace(`Manage the per-user lets settings file.
18+
19+
The user config is stored at ~/.config/lets/config.yaml and applies to all
20+
projects on the machine.`),
21+
Args: validateCommandArgs,
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
return cmd.Help()
24+
},
25+
}
26+
27+
configCmd.AddCommand(initConfigPathCommand())
28+
configCmd.AddCommand(initConfigEditCommand(openEditor))
29+
30+
return configCmd
31+
}
32+
33+
func initConfigPathCommand() *cobra.Command {
34+
return &cobra.Command{
35+
Use: "path",
36+
Short: "Print lets user config path",
37+
Args: cobra.NoArgs,
38+
RunE: func(cmd *cobra.Command, args []string) error {
39+
path, err := util.LetsUserFile("config.yaml")
40+
if err != nil {
41+
return err
42+
}
43+
44+
_, err = fmt.Fprintln(cmd.OutOrStdout(), path)
45+
46+
return err
47+
},
48+
}
49+
}
50+
51+
func initConfigEditCommand(openEditor func(string) error) *cobra.Command {
52+
return &cobra.Command{
53+
Use: "edit",
54+
Short: "Open lets user config in EDITOR",
55+
Args: cobra.NoArgs,
56+
RunE: func(cmd *cobra.Command, args []string) error {
57+
path, err := util.LetsUserFile("config.yaml")
58+
if err != nil {
59+
return err
60+
}
61+
62+
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
63+
return fmt.Errorf("creating config directory: %w", err)
64+
}
65+
66+
if err := openEditor(path); err != nil {
67+
return fmt.Errorf("can not open config: %w", err)
68+
}
69+
70+
return nil
71+
},
72+
}
73+
}

internal/util/editor.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package util
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
)
9+
10+
func OpenEditor(path string) error {
11+
editor := os.Getenv("EDITOR")
12+
if editor == "" {
13+
return errors.New("EDITOR is not set")
14+
}
15+
16+
cmd := exec.Command(editor, path) //nolint:gosec
17+
cmd.Stdin = os.Stdin
18+
cmd.Stdout = os.Stdout
19+
cmd.Stderr = os.Stderr
20+
21+
if err := cmd.Run(); err != nil {
22+
return fmt.Errorf("run %s: %w", editor, err)
23+
}
24+
25+
return nil
26+
}

0 commit comments

Comments
 (0)