190 lines
3.5 KiB
Go
190 lines
3.5 KiB
Go
|
|
package termui
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bufio"
|
|||
|
|
"fmt"
|
|||
|
|
"os"
|
|||
|
|
"strings"
|
|||
|
|
"syscall"
|
|||
|
|
"unsafe"
|
|||
|
|
|
|||
|
|
"hub.gaomia.site/titor/YunShu/pkg/style"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
stdInputHandle = ^uintptr(9)
|
|||
|
|
stdOutputHandle = ^uintptr(10)
|
|||
|
|
enableProcessed = 0x0001
|
|||
|
|
enableLineInput = 0x0002
|
|||
|
|
enableEchoInput = 0x0004
|
|||
|
|
enableVtProcessing = 0x0004
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|||
|
|
procGetStdHandle = kernel32.NewProc("GetStdHandle")
|
|||
|
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|||
|
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func ensureLineMode() {
|
|||
|
|
h, _, _ := procGetStdHandle.Call(stdInputHandle)
|
|||
|
|
if h == 0 || h == uintptr(syscall.InvalidHandle) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
var mode uint32
|
|||
|
|
ret, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode)))
|
|||
|
|
if ret == 0 {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
need := uint32(enableProcessed | enableLineInput | enableEchoInput)
|
|||
|
|
if mode&need != need {
|
|||
|
|
procSetConsoleMode.Call(h, uintptr(need))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type InputConfig struct {
|
|||
|
|
Label string
|
|||
|
|
Help string
|
|||
|
|
Default string
|
|||
|
|
Required bool
|
|||
|
|
Validator Validator
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type InputOption func(*InputConfig)
|
|||
|
|
|
|||
|
|
func WithDefault(v string) InputOption {
|
|||
|
|
return func(c *InputConfig) { c.Default = v }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func WithHelp(v string) InputOption {
|
|||
|
|
return func(c *InputConfig) { c.Help = v }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func WithRequired(v bool) InputOption {
|
|||
|
|
return func(c *InputConfig) { c.Required = v }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func WithValidator(v Validator) InputOption {
|
|||
|
|
return func(c *InputConfig) { c.Validator = v }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func applyOpts(opts []InputOption) InputConfig {
|
|||
|
|
c := InputConfig{}
|
|||
|
|
for _, o := range opts {
|
|||
|
|
o(&c)
|
|||
|
|
}
|
|||
|
|
return c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func printLabel(label string, required bool) {
|
|||
|
|
s := style.New().Bold().Render(label)
|
|||
|
|
if required {
|
|||
|
|
s += "(必填)"
|
|||
|
|
}
|
|||
|
|
fmt.Println(s)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ReadLine() string {
|
|||
|
|
ensureLineMode()
|
|||
|
|
r := bufio.NewReader(os.Stdin)
|
|||
|
|
s, err := r.ReadString('\n')
|
|||
|
|
if err != nil {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return strings.TrimRight(s, "\r\n")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TextInput(label string, opts ...InputOption) string {
|
|||
|
|
cfg := applyOpts(opts)
|
|||
|
|
printLabel(label, cfg.Required)
|
|||
|
|
|
|||
|
|
for {
|
|||
|
|
fmt.Print(style.Cyan.Render("? "))
|
|||
|
|
|
|||
|
|
line := ReadLine()
|
|||
|
|
|
|||
|
|
if line == "" {
|
|||
|
|
line = cfg.Default
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var err error
|
|||
|
|
if cfg.Required && line == "" {
|
|||
|
|
err = fmt.Errorf("不能为空")
|
|||
|
|
} else if cfg.Validator != nil {
|
|||
|
|
err = cfg.Validator(line)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
fmt.Println(style.Red.Render("⚠ " + err.Error()))
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fmt.Println(style.Green.Render("✔ " + line))
|
|||
|
|
return line
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func PasswordInput(label string, opts ...InputOption) string {
|
|||
|
|
cfg := applyOpts(opts)
|
|||
|
|
printLabel(label, cfg.Required)
|
|||
|
|
|
|||
|
|
for {
|
|||
|
|
fmt.Print(style.Cyan.Render("? "))
|
|||
|
|
|
|||
|
|
line := ReadLine()
|
|||
|
|
|
|||
|
|
fmt.Print("\033[A\r\033[K")
|
|||
|
|
|
|||
|
|
if line == "" {
|
|||
|
|
line = cfg.Default
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var err error
|
|||
|
|
if cfg.Required && line == "" {
|
|||
|
|
err = fmt.Errorf("不能为空")
|
|||
|
|
} else if cfg.Validator != nil {
|
|||
|
|
err = cfg.Validator(line)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
fmt.Println(style.Red.Render("⚠ " + err.Error()))
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
masked := strings.Repeat("*", len(line))
|
|||
|
|
fmt.Println(style.Green.Render("✔ " + masked))
|
|||
|
|
return line
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func Confirm(label string, defaultValue bool) bool {
|
|||
|
|
hint := "Y/n"
|
|||
|
|
if !defaultValue {
|
|||
|
|
hint = "y/N"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fmt.Print(style.Cyan.Render("?"), label, "(", hint, ")")
|
|||
|
|
|
|||
|
|
line := ReadLine()
|
|||
|
|
line = strings.ToLower(line)
|
|||
|
|
|
|||
|
|
fmt.Print("\033[A\r\033[K")
|
|||
|
|
|
|||
|
|
ok := defaultValue
|
|||
|
|
if line == "y" || line == "yes" {
|
|||
|
|
ok = true
|
|||
|
|
} else if line == "n" || line == "no" {
|
|||
|
|
ok = false
|
|||
|
|
} else if line != "" {
|
|||
|
|
ok = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ok {
|
|||
|
|
fmt.Println(style.Green.Render("✔ " + label))
|
|||
|
|
} else {
|
|||
|
|
fmt.Println(style.Red.Render("✘ " + label))
|
|||
|
|
}
|
|||
|
|
return ok
|
|||
|
|
}
|