Skip to content

Commit bf81bf7

Browse files
authored
feat: show available version if no version provided (version-fox#236)
* feat: show available version if no version provided * refactor: use pterm confirm * chore: update print * refactor: throw error in an elegant way * refactor: update code * chore: add license * feat: collect error * docs: update docs
1 parent 34d6779 commit bf81bf7

File tree

10 files changed

+178
-42
lines changed

10 files changed

+178
-42
lines changed

cmd/commands/install.go

+58-21
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ package commands
1919
import (
2020
"errors"
2121
"fmt"
22+
"os"
23+
"strings"
24+
2225
"github.com/pterm/pterm"
2326
"github.com/urfave/cli/v2"
2427
"github.com/version-fox/vfox/internal"
2528
"github.com/version-fox/vfox/internal/toolset"
26-
"os"
27-
"strings"
29+
"github.com/version-fox/vfox/internal/util"
2830
)
2931

3032
var Install = &cli.Command{
@@ -47,32 +49,67 @@ func installCmd(ctx *cli.Context) error {
4749
return installAll()
4850
}
4951

50-
sdkArg := ctx.Args().First()
51-
if sdkArg == "" {
52+
args := ctx.Args()
53+
if args.First() == "" {
5254
return cli.Exit("sdk name is required", 1)
5355
}
54-
argArr := strings.Split(sdkArg, "@")
55-
argsLen := len(argArr)
56+
5657
manager := internal.NewSdkManager()
5758
defer manager.Close()
58-
if argsLen > 2 {
59-
return cli.Exit("sdk version is invalid", 1)
60-
} else {
61-
var name string
62-
var version internal.Version
63-
if argsLen == 2 {
64-
name = strings.ToLower(argArr[0])
65-
version = internal.Version(argArr[1])
59+
60+
errorStore := util.NewErrorStore()
61+
62+
for i := 0; i < args.Len(); i++ {
63+
sdkArg := args.Get(i)
64+
argArr := strings.Split(sdkArg, "@")
65+
argsLen := len(argArr)
66+
67+
if argsLen > 2 {
68+
errorStore.AddAndShow(sdkArg, fmt.Errorf("your input is invalid: %s", sdkArg))
6669
} else {
67-
name = strings.ToLower(argArr[0])
68-
version = ""
69-
}
70-
source, err := manager.LookupSdkWithInstall(name)
71-
if err != nil {
72-
return err
70+
var name string
71+
var version internal.Version
72+
if argsLen == 2 {
73+
name = strings.ToLower(argArr[0])
74+
version = internal.Version(argArr[1])
75+
} else {
76+
name = strings.ToLower(argArr[0])
77+
version = ""
78+
}
79+
source, err := manager.LookupSdkWithInstall(name)
80+
if err != nil {
81+
errorStore.AddAndShow(name, err)
82+
continue
83+
}
84+
85+
err = source.Install(version)
86+
if errors.Is(err, internal.ErrNoVersionProvided) {
87+
showAvailable, _ := pterm.DefaultInteractiveConfirm.Show(fmt.Sprintf("No %s version provided, do you want to select a version to install?", name))
88+
if showAvailable {
89+
err := RunSearch(name, []string{})
90+
if err != nil {
91+
errorStore.AddAndShow(name, err)
92+
}
93+
continue
94+
}
95+
}
96+
97+
if err != nil {
98+
errorStore.AddAndShow(name, err)
99+
continue
100+
}
73101
}
74-
return source.Install(version)
75102
}
103+
104+
notes := errorStore.GetNotesSet()
105+
106+
if notes.Len() == 1 {
107+
return fmt.Errorf("failed to install %s", notes.Slice()[0])
108+
} else if notes.Len() > 1 {
109+
return fmt.Errorf("failed to install some SDKs: %s", strings.Join(notes.Slice(), ", "))
110+
}
111+
112+
return nil
76113
}
77114

78115
func installAll() error {

cmd/commands/search.go

+15-10
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,14 @@ var Search = &cli.Command{
3636
Category: CategorySDK,
3737
}
3838

39-
func searchCmd(ctx *cli.Context) error {
40-
sdkName := ctx.Args().First()
41-
if sdkName == "" {
42-
return cli.Exit("sdk name is required", 1)
43-
}
39+
func RunSearch(sdkName string, availableArgs []string) error {
4440
manager := internal.NewSdkManager()
4541
defer manager.Close()
4642
source, err := manager.LookupSdkWithInstall(sdkName)
4743
if err != nil {
4844
return fmt.Errorf("%s not supported, error: %w", sdkName, err)
4945
}
50-
result, err := source.Available(ctx.Args().Tail())
46+
result, err := source.Available(availableArgs)
5147
if err != nil {
5248
return fmt.Errorf("plugin [Available] method error: %w", err)
5349
}
@@ -76,17 +72,18 @@ func searchCmd(ctx *cli.Context) error {
7672
})
7773
}
7874

79-
highlightOptions := util.NewSet[string]()
75+
installedVersions := util.NewSet[string]()
8076
for _, version := range source.List() {
81-
highlightOptions.Add(string(version))
77+
installedVersions.Add(string(version))
8278
}
8379

8480
_, height, _ := terminal.GetSize(int(os.Stdout.Fd()))
8581
kvSelect := printer.PageKVSelect{
86-
TopText: "Please select a version of " + sdkName,
82+
TopText: "Please select a version of " + sdkName + " to install",
8783
Filter: true,
8884
Size: int(math.Min(math.Max(float64(height-3), 1), 20)),
89-
HighlightOptions: highlightOptions,
85+
HighlightOptions: installedVersions,
86+
DisabledOptions: installedVersions,
9087
Options: options,
9188
SourceFunc: func(page, size int, options []*printer.KV) ([]*printer.KV, error) {
9289
start := page * size
@@ -111,3 +108,11 @@ func searchCmd(ctx *cli.Context) error {
111108
}
112109
return source.Install(internal.Version(version.Key))
113110
}
111+
112+
func searchCmd(ctx *cli.Context) error {
113+
sdkName := ctx.Args().First()
114+
if sdkName == "" {
115+
return cli.Exit("sdk name is required", 1)
116+
}
117+
return RunSearch(sdkName, ctx.Args().Tail())
118+
}

docs/plugins/create/howto.md

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ directory
5757
in advance. If it is a compressed package such as `tar`, `tar.gz`, `tar.xz`, `zip`, `vfox` will help you to decompress
5858
it directly.
5959

60+
if the return value of version is empty, it means that the version is not found, and `vfox` will ask the user whether to perform a search operation.
61+
6062
```lua
6163
function PLUGIN:PreInstall(ctx)
6264
--- input parameters

docs/usage/core-commands.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ vfox i <sdk-name>@<version>
4242
- `-a, --all`: Install all SDK versions recorded in .tool-versions
4343

4444
::: tip
45-
`search` command will retrieve the plugin from the remote repository and add it locally if it is not installed locally.
45+
You can install multiple SDKs at the same time by separating them with space.
46+
47+
```shell
48+
vfox install nodejs@20 golang ...
49+
```
4650
:::
4751

4852
## Use

docs/zh-hans/plugins/create/howto.md

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252

5353
返回预安装信息, 例如具体版本号、下载源等信息。 `vfox`会帮你提前将这些文件下载到特定目录下。如果是压缩包如`tar``tar.gz``tar.xz``zip`这四种压缩包, `vfox`会直接帮你解压处理。
5454

55+
如果版本的返回值为空,表示未找到版本,`vfox`会询问用户是否进行搜索操作。
56+
5557
**位置**: `hooks/pre_install.lua`
5658
```lua
5759
function PLUGIN:PreInstall(ctx)

docs/zh-hans/usage/core-commands.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ vfox i <sdk-name>@<version>
4242
- `-a, --all`: 安装 .tool-versions 中记录的所有 SDK 版本
4343

4444
::: tip 自动安装
45-
如果本地没有安装 SDK,`install`命令会从远端仓库检索插件并安装到本地。
45+
你可以一次性安装多个 SDK,通过空格分隔。
46+
47+
```shell
48+
vfox install nodejs@20 golang ...
49+
```
4650
:::
4751

4852
## Use

internal/interfaces.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
package internal
1818

1919
import (
20-
"fmt"
20+
"errors"
21+
2122
lua "github.com/yuin/gopher-lua"
2223
)
2324

@@ -105,9 +106,11 @@ type PreInstallHookResult struct {
105106
Addition []*PreInstallHookResultAdditionItem `luai:"addition"`
106107
}
107108

109+
var ErrNoVersionProvided = errors.New("no version number provided")
110+
108111
func (i *PreInstallHookResult) Info() (*Info, error) {
109112
if i.Version == "" {
110-
return nil, fmt.Errorf("no version number provided")
113+
return nil, ErrNoVersionProvided
111114
}
112115

113116
sum := LuaCheckSum{

internal/plugin.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ import (
2020
_ "embed"
2121
"errors"
2222
"fmt"
23+
"path/filepath"
24+
"regexp"
25+
2326
"github.com/pterm/pterm"
2427
"github.com/version-fox/vfox/internal/env"
2528
"github.com/version-fox/vfox/internal/logger"
2629
"github.com/version-fox/vfox/internal/luai"
2730
"github.com/version-fox/vfox/internal/util"
2831
lua "github.com/yuin/gopher-lua"
29-
"path/filepath"
30-
"regexp"
3132
)
3233

3334
const (

internal/printer/select.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,25 @@
1717
package printer
1818

1919
import (
20+
"fmt"
21+
"sort"
22+
"strings"
23+
2024
"atomicgo.dev/cursor"
2125
"atomicgo.dev/keyboard"
2226
"atomicgo.dev/keyboard/keys"
23-
"fmt"
2427
"github.com/lithammer/fuzzysearch/fuzzy"
2528
"github.com/pterm/pterm"
2629
"github.com/version-fox/vfox/internal/logger"
2730
"github.com/version-fox/vfox/internal/util"
28-
"sort"
29-
"strings"
3031
)
3132

3233
type PageKVSelect struct {
33-
index int
34-
HighlightOptions util.Set[string]
34+
index int
35+
// Options to highlight with green color
36+
HighlightOptions util.Set[string]
37+
// Options to disable
38+
DisabledOptions util.Set[string]
3539
Options []*KV
3640
searchOptions []*KV
3741
pageOptions []*KV
@@ -217,6 +221,9 @@ func (s *PageKVSelect) Show() (*KV, error) {
217221
case keys.Enter:
218222
if s.index < len(s.pageOptions) {
219223
s.result = s.pageOptions[s.index]
224+
if (s.result != nil) && s.DisabledOptions.Contains(s.result.Key) {
225+
return false, nil
226+
}
220227
} else {
221228
s.result = nil
222229
logger.Info("No search, program stopped.")

internal/util/error_store.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2024 Han Li and contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package util
18+
19+
import "fmt"
20+
21+
type errorItem struct {
22+
Note string
23+
Err error
24+
}
25+
26+
// ErrorStore is a struct that stores errors
27+
type ErrorStore struct {
28+
errors []errorItem
29+
}
30+
31+
// NewErrorStore creates a new ErrorStore
32+
func NewErrorStore() *ErrorStore {
33+
return &ErrorStore{
34+
errors: make([]errorItem, 0),
35+
}
36+
}
37+
38+
// Add adds an error to the store
39+
func (e *ErrorStore) Add(note string, err error) {
40+
e.errors = append(e.errors, errorItem{Note: note, Err: err})
41+
}
42+
43+
// Add and show in the console
44+
func (e *ErrorStore) AddAndShow(note string, err error) {
45+
e.Add(note, err)
46+
fmt.Println(err)
47+
}
48+
49+
// get all error notes
50+
func (e *ErrorStore) GetNotes() []string {
51+
notes := make([]string, 0, len(e.errors))
52+
53+
for _, item := range e.errors {
54+
notes = append(notes, item.Note)
55+
}
56+
57+
return notes
58+
}
59+
60+
// get notes set
61+
func (e *ErrorStore) GetNotesSet() Set[string] {
62+
set := NewSet[string]()
63+
for _, item := range e.errors {
64+
set.Add(item.Note)
65+
}
66+
return set
67+
}
68+
69+
func (e *ErrorStore) HasError() bool {
70+
return len(e.errors) > 0
71+
}

0 commit comments

Comments
 (0)