mirror of
https://github.com/jixishi/SerialTerminalForWindowsTerminal.git
synced 2026-06-15 16:42:46 +00:00
refactor: simplify flag system and extract to internal/flag
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>
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user