Skip to content

Commit 1aad4c4

Browse files
committed
Add lets self doc command
1 parent e24933c commit 1aad4c4

8 files changed

Lines changed: 213 additions & 2 deletions

File tree

cmd/lets/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func getExitCode(err error, defaultCode int) int {
164164

165165
// do not fail on config error in it is help (-h, --help) or --init or completion command.
166166
func failOnConfigError(root *cobra.Command, current *cobra.Command, rootFlags *flags) bool {
167-
rootCommands := set.NewSet("completion", "help", "lsp")
167+
rootCommands := set.NewSet("completion", "help", "self", "lsp", "doc")
168168
return (root.Flags().NFlag() == 0 && !rootCommands.Contains(current.Name())) && !rootFlags.help && !rootFlags.init
169169
}
170170

docs/docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ title: Changelog
1313
* `[Added]` When a command or its `depends` chain fails, print an indented tree to stderr showing the full chain with the failing command highlighted
1414
* `[Added]` Support `env_file` in global config and commands. File names are expanded after `env` is resolved, and values loaded from env files override values from `env`.
1515
* `[Changed]` Migrate the LSP YAML parser from the CGO-based tree-sitter bindings to pure-Go [`gotreesitter`](https://github.com/odvcencio/gotreesitter), removing the C toolchain requirement from normal builds and release packaging.
16+
* `[Added]` Add `lets self doc` command to open the online documentation in a browser.
1617

1718
## [0.0.59](https://github.com/lets-cli/lets/releases/tag/v0.0.59)
1819

internal/cmd/doc.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
const letsDocsURL = "https://lets-cli.org/docs/quick_start"
10+
11+
func initDocCommand(openURL func(string) error) *cobra.Command {
12+
docCmd := &cobra.Command{
13+
Use: "doc",
14+
Aliases: []string{"docs"},
15+
Short: "Open lets documentation in browser",
16+
Args: cobra.NoArgs,
17+
RunE: func(cmd *cobra.Command, args []string) error {
18+
if err := openURL(letsDocsURL); err != nil {
19+
return fmt.Errorf("can not open documentation: %w", err)
20+
}
21+
22+
return nil
23+
},
24+
}
25+
26+
return docCmd
27+
}

internal/cmd/root_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,69 @@ func TestPrintVersionMessage(t *testing.T) {
176176
}
177177

178178
func TestSelfCmd(t *testing.T) {
179+
t.Run("should open documentation in browser", func(t *testing.T) {
180+
bufOut := new(bytes.Buffer)
181+
called := false
182+
gotURL := ""
183+
184+
prevOpenURL := openURL
185+
openURL = func(url string) error {
186+
called = true
187+
gotURL = url
188+
189+
return nil
190+
}
191+
defer func() {
192+
openURL = prevOpenURL
193+
}()
194+
195+
rootCmd := CreateRootCommand("v0.0.0-test", "")
196+
rootCmd.SetArgs([]string{"self", "doc"})
197+
rootCmd.SetOut(bufOut)
198+
rootCmd.SetErr(bufOut)
199+
InitSelfCmd(rootCmd, "v0.0.0-test")
200+
201+
err := rootCmd.Execute()
202+
if err != nil {
203+
t.Fatalf("unexpected error: %v", err)
204+
}
205+
206+
if !called {
207+
t.Fatal("expected documentation opener to be called")
208+
}
209+
210+
if gotURL != letsDocsURL {
211+
t.Fatalf("expected docs url %q, got %q", letsDocsURL, gotURL)
212+
}
213+
})
214+
215+
t.Run("should return opener error for documentation command", func(t *testing.T) {
216+
bufOut := new(bytes.Buffer)
217+
218+
prevOpenURL := openURL
219+
openURL = func(url string) error {
220+
return errors.New("open failed")
221+
}
222+
defer func() {
223+
openURL = prevOpenURL
224+
}()
225+
226+
rootCmd := CreateRootCommand("v0.0.0-test", "")
227+
rootCmd.SetArgs([]string{"self", "doc"})
228+
rootCmd.SetOut(bufOut)
229+
rootCmd.SetErr(bufOut)
230+
InitSelfCmd(rootCmd, "v0.0.0-test")
231+
232+
err := rootCmd.Execute()
233+
if err == nil {
234+
t.Fatal("expected documentation opener error")
235+
}
236+
237+
if !strings.Contains(err.Error(), "can not open documentation") {
238+
t.Fatalf("expected documentation error, got %q", err.Error())
239+
}
240+
})
241+
179242
t.Run("should return exit code 2 for unknown self subcommand", func(t *testing.T) {
180243
bufOut := new(bytes.Buffer)
181244

internal/cmd/self.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package cmd
22

33
import (
4+
"github.com/lets-cli/lets/internal/util"
45
"github.com/spf13/cobra"
56
)
67

8+
var openURL = util.OpenURL
9+
710
// InitSelfCmd intializes root 'self' subcommand.
811
func InitSelfCmd(rootCmd *cobra.Command, version string) {
912
selfCmd := &cobra.Command{
@@ -19,5 +22,6 @@ func InitSelfCmd(rootCmd *cobra.Command, version string) {
1922

2023
rootCmd.AddCommand(selfCmd)
2124

25+
selfCmd.AddCommand(initDocCommand(openURL))
2226
selfCmd.AddCommand(initLspCommand(version))
2327
}

internal/util/browser.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"runtime"
7+
)
8+
9+
func browserCommand(goos string, url string) (string, []string, error) {
10+
switch goos {
11+
case "darwin":
12+
return "open", []string{url}, nil
13+
case "linux":
14+
return "xdg-open", []string{url}, nil
15+
default:
16+
return "", nil, fmt.Errorf("unsupported platform %q", goos)
17+
}
18+
}
19+
20+
func OpenURL(url string) error {
21+
name, args, err := browserCommand(runtime.GOOS, url)
22+
if err != nil {
23+
return err
24+
}
25+
26+
cmd := exec.Command(name, args...)
27+
if err := cmd.Start(); err != nil {
28+
return fmt.Errorf("start %s: %w", name, err)
29+
}
30+
31+
if cmd.Process != nil {
32+
_ = cmd.Process.Release()
33+
}
34+
35+
return nil
36+
}

internal/util/browser_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package util
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestBrowserCommand(t *testing.T) {
9+
t.Run("darwin", func(t *testing.T) {
10+
name, args, err := browserCommand("darwin", "https://lets-cli.org")
11+
if err != nil {
12+
t.Fatalf("unexpected error: %v", err)
13+
}
14+
15+
if name != "open" {
16+
t.Fatalf("expected open, got %q", name)
17+
}
18+
19+
expectedArgs := []string{"https://lets-cli.org"}
20+
if !reflect.DeepEqual(args, expectedArgs) {
21+
t.Fatalf("expected args %v, got %v", expectedArgs, args)
22+
}
23+
})
24+
25+
t.Run("linux", func(t *testing.T) {
26+
name, args, err := browserCommand("linux", "https://lets-cli.org")
27+
if err != nil {
28+
t.Fatalf("unexpected error: %v", err)
29+
}
30+
31+
if name != "xdg-open" {
32+
t.Fatalf("expected xdg-open, got %q", name)
33+
}
34+
35+
expectedArgs := []string{"https://lets-cli.org"}
36+
if !reflect.DeepEqual(args, expectedArgs) {
37+
t.Fatalf("expected args %v, got %v", expectedArgs, args)
38+
}
39+
})
40+
41+
t.Run("unsupported", func(t *testing.T) {
42+
_, _, err := browserCommand("windows", "https://lets-cli.org")
43+
if err == nil {
44+
t.Fatal("expected unsupported platform error")
45+
}
46+
})
47+
}

tests/no_lets_file.bats

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,37 @@ NOT_EXISTED_LETS_FILE="lets-not-existed.yaml"
4949
LETS_CONFIG=${NOT_EXISTED_LETS_FILE} run lets -h
5050
assert_success
5151
assert_line --index 0 "A CLI task runner"
52-
}
52+
}
53+
54+
@test "no_lets_file: lets self doc opens docs without config" {
55+
fake_bin_dir="$(mktemp -d)"
56+
opened_url_file="$(mktemp)"
57+
rm -f "${opened_url_file}"
58+
59+
cat > "${fake_bin_dir}/xdg-open" <<'EOF'
60+
#!/usr/bin/env bash
61+
printf "%s" "$1" > "${LETS_TEST_OPENED_URL_FILE}"
62+
EOF
63+
chmod +x "${fake_bin_dir}/xdg-open"
64+
65+
PATH="${fake_bin_dir}:${PATH}" \
66+
LETS_CONFIG=${NOT_EXISTED_LETS_FILE} \
67+
LETS_TEST_OPENED_URL_FILE="${opened_url_file}" \
68+
run lets self doc
69+
70+
assert_success
71+
72+
for _ in $(seq 1 20); do
73+
if [[ -f "${opened_url_file}" ]]; then
74+
break
75+
fi
76+
sleep 0.1
77+
done
78+
79+
run cat "${opened_url_file}"
80+
assert_success
81+
assert_output "https://lets-cli.org/docs/quick_start"
82+
83+
rm -rf "${fake_bin_dir}"
84+
rm -f "${opened_url_file}"
85+
}

0 commit comments

Comments
 (0)