mirror of
https://github.com/jixishi/SerialTerminalForWindowsTerminal.git
synced 2026-06-15 16:42:46 +00:00
65c1a48f10
Replace complex ptrVal/Val/Flag type machinery with direct pflag calls. Move flag logic and interactive wizard to internal/flag package. Eliminate ~200 lines of flag boilerplate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
261 lines
9.0 KiB
Go
261 lines
9.0 KiB
Go
// Package flag provides CLI flag parsing and interactive configuration.
|
|
package flag
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/key"
|
|
inf "github.com/fzdwx/infinite"
|
|
"github.com/fzdwx/infinite/color"
|
|
"github.com/fzdwx/infinite/components"
|
|
"github.com/fzdwx/infinite/components/input/text"
|
|
"github.com/fzdwx/infinite/components/selection/confirm"
|
|
"github.com/fzdwx/infinite/components/selection/singleselect"
|
|
"github.com/fzdwx/infinite/style"
|
|
"github.com/fzdwx/infinite/theme"
|
|
"github.com/spf13/pflag"
|
|
"go.bug.st/serial"
|
|
|
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
|
|
)
|
|
|
|
// Init registers all CLI flags with pflag, binding them to the given config.
|
|
func Init(cfg *config.Config) {
|
|
pflag.StringVarP(&cfg.PortName, "port", "p", "", "serial port (/dev/ttyUSB0, COMx)")
|
|
pflag.IntVarP(&cfg.BaudRate, "baud", "b", 115200, "baud rate")
|
|
pflag.IntVarP(&cfg.DataBits, "data", "d", 8, "data bits")
|
|
pflag.IntVarP(&cfg.StopBits, "stop", "s", 0, "stop bits (0:1, 1:1.5, 2:2)")
|
|
pflag.StringVarP(&cfg.OutputCode, "out", "o", "UTF-8", "output charset")
|
|
pflag.StringVarP(&cfg.InputCode, "in", "i", "UTF-8", "input charset")
|
|
pflag.StringVarP(&cfg.EndStr, "end", "e", "\n", "line ending")
|
|
pflag.IntVarP(&cfg.FrameSize, "Frame", "F", 16, "hex frame size")
|
|
pflag.IntVarP(&cfg.ParityBit, "verify", "v", 0, "parity (0:none,1:odd,2:even,3:mark,4:space)")
|
|
pflag.BoolVarP(&cfg.EnableGUI, "gui", "g", false, "enable TUI mode")
|
|
pflag.StringVarP(&cfg.HotkeyMod, "hotkey-mod", "k", "ctrl+alt", "hotkey modifier (ctrl+alt|ctrl+shift)")
|
|
pflag.IntSliceVarP(&cfg.ForWard, "forward", "f", nil, "forward mode (0:none,1:TCP,2:UDP)")
|
|
pflag.StringArrayVarP(&cfg.Address, "address", "a", nil, "forward address")
|
|
pflag.StringVarP(&cfg.LogFilePath, "log", "l", "", "log file path")
|
|
_ = pflag.Lookup("log") // mark for NoOptDefVal
|
|
pflag.StringVarP(&cfg.TimesFmt, "time", "t", "", "timestamp format")
|
|
_ = pflag.Lookup("time") // mark for NoOptDefVal
|
|
}
|
|
|
|
// Normalize converts single-dash long flags (e.g. -port) to double-dash (--port).
|
|
func Normalize() {
|
|
known := map[string]bool{
|
|
"port": true, "baud": true, "data": true, "stop": true,
|
|
"out": true, "in": true, "end": true, "Frame": true,
|
|
"verify": true, "gui": true, "hotkey-mod": true,
|
|
"forward": true, "address": true, "log": true, "time": true,
|
|
}
|
|
for i, arg := range os.Args[1:] {
|
|
if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
|
|
name := strings.TrimPrefix(arg, "-")
|
|
if known[name] {
|
|
os.Args[i+1] = "--" + name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ext applies post-parse normalization to config values.
|
|
func Ext(cfg *config.Config) {
|
|
if cfg.LogFilePath != "" {
|
|
cfg.EnableLog = true
|
|
}
|
|
if cfg.TimesFmt != "" {
|
|
cfg.TimesTamp = true
|
|
}
|
|
if cfg.HotkeyMod == "" {
|
|
cfg.HotkeyMod = "ctrl+alt"
|
|
}
|
|
cfg.HotkeyMod = strings.ToLower(strings.TrimSpace(cfg.HotkeyMod))
|
|
if cfg.HotkeyMod != "ctrl+alt" && cfg.HotkeyMod != "ctrl+shift" {
|
|
cfg.HotkeyMod = "ctrl+alt"
|
|
}
|
|
}
|
|
|
|
// PrintUsage displays flag help and available ports.
|
|
func PrintUsage(ports []string) {
|
|
type flagInfo struct{ short, long, typ, help, def string }
|
|
flags := []flagInfo{
|
|
{"-p", "--port", "string", "serial port", ""},
|
|
{"-b", "--baud", "int", "baud rate", "115200"},
|
|
{"-d", "--data", "int", "data bits", "8"},
|
|
{"-s", "--stop", "int", "stop bits", "0"},
|
|
{"-o", "--out", "string", "output charset", "UTF-8"},
|
|
{"-i", "--in", "string", "input charset", "UTF-8"},
|
|
{"-e", "--end", "string", "line ending", "\\n"},
|
|
{"-F", "--Frame", "int", "hex frame size", "16"},
|
|
{"-v", "--verify", "int", "parity", "0"},
|
|
{"-g", "--gui", "bool", "enable TUI", "false"},
|
|
{"-k", "--hotkey-mod", "string", "hotkey modifier", "ctrl+alt"},
|
|
{"-f", "--forward", "[]int", "forward mode", "0"},
|
|
{"-a", "--address", "[]string", "forward address", "127.0.0.1:12345"},
|
|
{"-l", "--log", "string", "log path", "./%s-$s.txt"},
|
|
{"-t", "--time", "string", "timestamp format", "[06-01-02 15:04:05.000]"},
|
|
}
|
|
sort.Slice(flags, func(i, j int) bool { return flags[i].long < flags[j].long })
|
|
|
|
fmt.Printf("\nFlags:\n")
|
|
fmt.Printf(" %-6s %-14s %-8s %-44s %s\n", "Short", "Long", "Type", "Help", "Default")
|
|
fmt.Printf(" %-6s %-14s %-8s %-44s %s\n", "------", "------", "------", "------", "------")
|
|
for _, f := range flags {
|
|
fmt.Printf(" %-6s %-14s %-8s %-44s %q\n", f.short, f.long, f.typ, f.help, f.def)
|
|
}
|
|
fmt.Printf("\nAvailable ports: %v\n", strings.Join(ports, ", "))
|
|
}
|
|
|
|
var (
|
|
bauds = []string{"Custom", "300", "600", "1200", "2400", "4800", "9600",
|
|
"14400", "19200", "38400", "56000", "57600", "115200", "128000",
|
|
"256000", "460800", "512000", "750000", "921600", "1500000"}
|
|
datas = []string{"5", "6", "7", "8"}
|
|
stops = []string{"1", "1.5", "2"}
|
|
paritys = []string{"None", "Odd", "Even", "Mark", "Space"}
|
|
forwards = []string{"No", "TCP-C", "UDP-C"}
|
|
)
|
|
|
|
// GetCliFlag runs an interactive configuration wizard when no port is specified.
|
|
func GetCliFlag(cfg *config.Config) {
|
|
ports, err := serial.GetPortsList()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
inputs := components.NewInput()
|
|
inputs.Prompt = "Filtering: "
|
|
inputs.PromptStyle = style.New().Bold().Italic().Fg(color.LightBlue)
|
|
|
|
selectKeymap := singleselect.DefaultSingleKeyMap()
|
|
selectKeymap.Confirm = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "finish select"))
|
|
selectKeymap.Choice = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "finish select"))
|
|
selectKeymap.NextPage = key.NewBinding(key.WithKeys("right"), key.WithHelp("->", "next page"))
|
|
selectKeymap.PrevPage = key.NewBinding(key.WithKeys("left"), key.WithHelp("<-", "prev page"))
|
|
|
|
s, _ := inf.NewSingleSelect(ports,
|
|
singleselect.WithKeyBinding(selectKeymap),
|
|
singleselect.WithPageSize(4),
|
|
singleselect.WithFilterInput(inputs),
|
|
).Display("Select serial port")
|
|
cfg.PortName = ports[s]
|
|
|
|
s, _ = inf.NewSingleSelect(bauds,
|
|
singleselect.WithKeyBinding(selectKeymap),
|
|
singleselect.WithPageSize(4),
|
|
).Display("Select baud rate")
|
|
if s != 0 {
|
|
cfg.BaudRate, _ = strconv.Atoi(bauds[s])
|
|
} else {
|
|
b, _ := inf.NewText(
|
|
text.WithPrompt("BaudRate:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("115200"),
|
|
).Display()
|
|
cfg.BaudRate, _ = strconv.Atoi(b)
|
|
}
|
|
|
|
v, _ := inf.NewConfirmWithSelection(confirm.WithPrompt("Enable Hex")).Display()
|
|
if v {
|
|
cfg.InputCode = "hex"
|
|
b, _ := inf.NewText(
|
|
text.WithPrompt("Frames:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("16"),
|
|
).Display()
|
|
cfg.FrameSize, _ = strconv.Atoi(b)
|
|
}
|
|
|
|
v, _ = inf.NewConfirmWithSelection(confirm.WithPrompt("Enable Timestamp")).Display()
|
|
cfg.TimesTamp = v
|
|
if v {
|
|
b, _ := inf.NewText(
|
|
text.WithPrompt("Format:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("[06-01-02 15:04:05.000]"),
|
|
).Display()
|
|
cfg.TimesFmt = b
|
|
}
|
|
|
|
v, _ = inf.NewConfirmWithSelection(confirm.WithPrompt("Enable advanced config")).Display()
|
|
if v {
|
|
s, _ = inf.NewSingleSelect(datas,
|
|
singleselect.WithKeyBinding(selectKeymap),
|
|
singleselect.WithPageSize(4),
|
|
singleselect.WithFilterInput(inputs),
|
|
).Display("Select data bits")
|
|
cfg.DataBits, _ = strconv.Atoi(datas[s])
|
|
|
|
s, _ = inf.NewSingleSelect(stops,
|
|
singleselect.WithKeyBinding(selectKeymap),
|
|
singleselect.WithPageSize(4),
|
|
singleselect.WithFilterInput(inputs),
|
|
).Display("Select stop bits")
|
|
cfg.StopBits = s
|
|
|
|
s, _ = inf.NewSingleSelect(paritys,
|
|
singleselect.WithKeyBinding(selectKeymap),
|
|
singleselect.WithPageSize(4),
|
|
singleselect.WithFilterInput(inputs),
|
|
).Display("Select parity")
|
|
cfg.ParityBit = s
|
|
|
|
t, _ := inf.NewText(
|
|
text.WithPrompt("Line ending:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("\n"),
|
|
).Display()
|
|
cfg.EndStr = t
|
|
|
|
v, _ = inf.NewConfirmWithSelection(confirm.WithDefaultYes(), confirm.WithPrompt("Enable charset conversion")).Display()
|
|
if v {
|
|
t, _ = inf.NewText(
|
|
text.WithPrompt("Input charset:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("UTF-8"),
|
|
).Display()
|
|
cfg.InputCode = t
|
|
|
|
t, _ = inf.NewText(
|
|
text.WithPrompt("Output charset:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("UTF-8"),
|
|
).Display()
|
|
cfg.OutputCode = t
|
|
}
|
|
|
|
G_F_mode:
|
|
s, _ = inf.NewSingleSelect(forwards,
|
|
singleselect.WithKeyBinding(selectKeymap),
|
|
singleselect.WithPageSize(3),
|
|
singleselect.WithFilterInput(inputs),
|
|
).Display("Select forward mode")
|
|
if s != 0 {
|
|
cfg.ForWard = append(cfg.ForWard, s)
|
|
t, _ = inf.NewText(
|
|
text.WithPrompt("Address:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("127.0.0.1:12345"),
|
|
).Display()
|
|
cfg.Address = append(cfg.Address, t)
|
|
goto G_F_mode
|
|
}
|
|
|
|
e, _ := inf.NewConfirmWithSelection(confirm.WithDefaultYes(), confirm.WithPrompt("Enable logging")).Display()
|
|
cfg.EnableLog = e
|
|
if e {
|
|
t, _ = inf.NewText(
|
|
text.WithPrompt("Path:"),
|
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
|
text.WithDefaultValue("./%s-$s.txt"),
|
|
).Display()
|
|
cfg.LogFilePath = t
|
|
}
|
|
}
|
|
}
|