From 65c1a48f103df9d97dcb10d4b3b6ff7ba0798c43 Mon Sep 17 00:00:00 2001 From: JiXieShi Date: Sat, 23 May 2026 21:52:40 +0800 Subject: [PATCH] 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 --- config_test.go | 159 ------------------- flag.go | 362 ------------------------------------------ internal/flag/flag.go | 260 ++++++++++++++++++++++++++++++ main.go | 13 +- 4 files changed, 266 insertions(+), 528 deletions(-) delete mode 100644 flag.go create mode 100644 internal/flag/flag.go diff --git a/config_test.go b/config_test.go index c88bcc9..c262727 100644 --- a/config_test.go +++ b/config_test.go @@ -1,12 +1,9 @@ package main import ( - "os" "path/filepath" "testing" - "github.com/spf13/pflag" - appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config" "github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward" ) @@ -83,159 +80,3 @@ func TestOpenLogFile(t *testing.T) { t.Fatalf("openLogFile() expected nil file when enableLog=false") } } - -func TestFlagFindValue(t *testing.T) { - s := "str" - sl := []string{"a"} - n := 1 - il := []int{1} - b := true - ext := "ext" - - tests := []struct { - name string - v ptrVal - want ValType - }{ - {name: "string", v: ptrVal{string: &s}, want: stringVal}, - {name: "stringSlice", v: ptrVal{sl: &sl}, want: sliceStrVal}, - {name: "bool", v: ptrVal{bool: &b}, want: boolVal}, - {name: "int", v: ptrVal{int: &n}, want: intVal}, - {name: "intSlice", v: ptrVal{il: &il}, want: sliceIntVal}, - {name: "ext", v: ptrVal{ext: &ext}, want: extVal}, - {name: "none", v: ptrVal{}, want: notVal}, - } - - for _, tt := range tests { - got := flagFindValue(tt.v) - if got != tt.want { - t.Fatalf("%s: flagFindValue got=%v want=%v", tt.name, got, tt.want) - } - } -} - -func TestFlagExt(t *testing.T) { - old := *cfg - defer func() { *cfg = old }() - - *cfg = Config{} - flagExt() - if cfg.EnableLog { - t.Fatalf("expected enableLog=false when logFilePath empty") - } - if cfg.TimesTamp { - t.Fatalf("expected timesTamp=false when timesFmt empty") - } - if cfg.HotkeyMod != "ctrl+alt" { - t.Fatalf("expected default hotkeyMod=ctrl+alt, got=%q", cfg.HotkeyMod) - } - - *cfg = Config{LogFilePath: "/tmp/log.txt"} - flagExt() - if !cfg.EnableLog { - t.Fatalf("expected enableLog=true when logFilePath set") - } - - *cfg = Config{TimesFmt: "2006-01-02"} - flagExt() - if !cfg.TimesTamp { - t.Fatalf("expected timesTamp=true when timesFmt set") - } - - *cfg = Config{HotkeyMod: ""} - flagExt() - if cfg.HotkeyMod != "ctrl+alt" { - t.Fatalf("empty hotkeyMod should default to ctrl+alt") - } - - *cfg = Config{HotkeyMod: "ctrl+shift"} - flagExt() - if cfg.HotkeyMod != "ctrl+shift" { - t.Fatalf("expected ctrl+shift preserved") - } - - *cfg = Config{HotkeyMod: " CTRL+SHIFT "} - flagExt() - if cfg.HotkeyMod != "ctrl+shift" { - t.Fatalf("expected whitespace+case normalization, got=%q", cfg.HotkeyMod) - } - - *cfg = Config{HotkeyMod: "invalid"} - flagExt() - if cfg.HotkeyMod != "ctrl+alt" { - t.Fatalf("invalid hotkeyMod should default to ctrl+alt, got=%q", cfg.HotkeyMod) - } -} - -func TestFlagInit(t *testing.T) { - var testStr string - var testBool bool - var testInt int - var testExt string - var testSl []string - var testIl []int - - f := Flag{ - v: ptrVal{string: &testStr}, - sStr: "X", lStr: "test-str", dv: Val{string: "hello"}, help: "test string", - } - flagInit(&f) - if pflag.Lookup("test-str") == nil { - t.Fatalf("string flag not registered") - } - - boolF := Flag{ - v: ptrVal{bool: &testBool}, - sStr: "Y", lStr: "test-bool", dv: Val{bool: true}, help: "test bool", - } - flagInit(&boolF) - - intF := Flag{ - v: ptrVal{int: &testInt}, - sStr: "Z", lStr: "test-int", dv: Val{int: 42}, help: "test int", - } - flagInit(&intF) - - extF := Flag{ - v: ptrVal{ext: &testExt}, - sStr: "E", lStr: "test-ext", dv: Val{extdef: "default-val", string: ""}, help: "test ext", - } - flagInit(&extF) - - slF := Flag{ - v: ptrVal{sl: &testSl}, - sStr: "1", lStr: "test-sl", dv: Val{string: "a"}, help: "test sl", - } - flagInit(&slF) - - ilF := Flag{ - v: ptrVal{il: &testIl}, - sStr: "2", lStr: "test-il", dv: Val{int: 1}, help: "test il", - } - flagInit(&ilF) -} - -func TestNormalizeFlags(t *testing.T) { - oldArgs := os.Args - defer func() { os.Args = oldArgs }() - - os.Args = []string{"COM.exe", "-port", "COM17", "-baud", "9600", "-p", "COM1", "--gui", "COM17"} - normalizeFlags() - - args := os.Args - if args[1] != "--port" { - t.Fatalf("expected -port -> --port, got %q", args[1]) - } - if args[3] != "--baud" { - t.Fatalf("expected -baud -> --baud, got %q", args[3]) - } - if args[5] != "-p" { - t.Fatalf("expected -p unchanged, got %q", args[5]) - } - if args[7] != "--gui" { - t.Fatalf("expected --gui unchanged, got %q", args[7]) - } - if args[8] != "COM17" { - t.Fatalf("expected value unchanged, got %q", args[8]) - } -} diff --git a/flag.go b/flag.go deleted file mode 100644 index 9253db8..0000000 --- a/flag.go +++ /dev/null @@ -1,362 +0,0 @@ -package main - -import ( - "fmt" - "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" - "log" - "os" - "sort" - "strconv" - "strings" -) - -type ptrVal struct { - *string - sl *[]string - *int - il *[]int - *bool - *float64 - *float32 - ext *string -} -type Val struct { - string - int - bool - float64 - float32 - extdef string -} -type Flag struct { - v ptrVal - sStr string - lStr string - dv Val - help string -} - -var ( - portName = Flag{ptrVal{string: &cfg.PortName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"} - baudRate = Flag{ptrVal{int: &cfg.BaudRate}, "b", "baud", Val{int: 115200}, "波特率"} - dataBits = Flag{ptrVal{int: &cfg.DataBits}, "d", "data", Val{int: 8}, "数据位"} - stopBits = Flag{ptrVal{int: &cfg.StopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"} - outputCode = Flag{ptrVal{string: &cfg.OutputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"} - inputCode = Flag{ptrVal{string: &cfg.InputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"} - endStr = Flag{ptrVal{string: &cfg.EndStr}, "e", "end", Val{string: "\n"}, "终端换行符"} - logExt = Flag{v: ptrVal{ext: &cfg.LogFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"} - timeExt = Flag{v: ptrVal{ext: &cfg.TimesFmt}, sStr: "t", lStr: "time", dv: Val{extdef: "[06-01-02 15:04:05.000]", string: ""}, help: "时间戳格式化字段"} - forWard = Flag{ptrVal{il: &cfg.ForWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"} - address = Flag{ptrVal{sl: &cfg.Address}, "a", "address", Val{string: "127.0.0.1:12345"}, "转发服务地址(支持多次传入)"} - frameSize = Flag{ptrVal{int: &cfg.FrameSize}, "F", "Frame", Val{int: 16}, "帧大小"} - parityBit = Flag{ptrVal{int: &cfg.ParityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"} - guiMode = Flag{ptrVal{bool: &cfg.EnableGUI}, "g", "gui", Val{bool: false}, "启用TUI交互界面"} - hotkeyMod = Flag{ptrVal{string: &cfg.HotkeyMod}, "k", "hotkey-mod", Val{string: "ctrl+alt"}, "本地快捷键修饰(ctrl+alt|ctrl+shift)"} - flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, forWard, address, frameSize, parityBit, logExt, timeExt, guiMode, hotkeyMod} -) - -var ( - bauds = []string{"自定义", "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{"无校验", "奇校验", "偶校验", "1校验", "0校验"} - forwards = []string{"No", "TCP-C", "UDP-C"} -) - -type ValType int - -const ( - notVal ValType = iota - stringVal - intVal - boolVal - extVal - sliceStrVal - sliceIntVal -) - -func normalizeFlags() { - known := make(map[string]bool, len(flags)) - for _, f := range flags { - known[f.lStr] = 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 - } - } - } -} - -func printUsage(ports []string) { - sorted := make([]Flag, len(flags)) - copy(sorted, flags) - sort.Slice(sorted, func(i, j int) bool { - return sorted[i].lStr < sorted[j].lStr - }) - - fmt.Printf("\n参数帮助:\n") - fmt.Printf(" %-6s %-14s %-8s %-44s %s\n", "短参", "长参", "类型", "说明", "默认值") - fmt.Printf(" %-6s %-14s %-8s %-44s %s\n", "------", "------", "------", "------", "------") - for _, f := range sorted { - flagprint(f) - } - fmt.Printf("\n在线串口: %v\n", strings.Join(ports, ", ")) -} - -func flagFindValue(v ptrVal) ValType { - if v.string != nil { - return stringVal - } - if v.sl != nil { - return sliceStrVal - } - if v.bool != nil { - return boolVal - } - if v.int != nil { - return intVal - } - if v.il != nil { - return sliceIntVal - } - if v.ext != nil { - return extVal - } - return notVal -} - -func flagprint(f Flag) { - short := "-" + f.sStr - long := "--" + f.lStr - help := f.help - - switch flagFindValue(f.v) { - case stringVal: - fmt.Printf(" %-6s %-14s %-8s %-44s %q\n", short, long, "string", help, f.dv.string) - case intVal: - fmt.Printf(" %-6s %-14s %-8s %-44s %v\n", short, long, "int", help, f.dv.int) - case boolVal: - fmt.Printf(" %-6s %-14s %-8s %-44s %v\n", short, long, "bool", help, f.dv.bool) - case extVal: - fmt.Printf(" %-6s %-14s %-8s %-44s %v\n", short, long, "string", help, f.dv.extdef) - case sliceStrVal: - fmt.Printf(" %-6s %-14s %-8s %-44s %q\n", short, long, "[]string", help, f.dv.string) - case sliceIntVal: - fmt.Printf(" %-6s %-14s %-8s %-44s %v\n", short, long, "[]int", help, f.dv.int) - } -} -func flagInit(f *Flag) { - if f.v.string != nil { - pflag.StringVarP(f.v.string, f.lStr, f.sStr, f.dv.string, f.help) - } - if f.v.bool != nil { - pflag.BoolVarP(f.v.bool, f.lStr, f.sStr, f.dv.bool, f.help) - } - if f.v.int != nil { - pflag.IntVarP(f.v.int, f.lStr, f.sStr, f.dv.int, f.help) - } - if f.v.ext != nil { - pflag.StringVarP(f.v.ext, f.lStr, f.sStr, f.dv.string, f.help) - pflag.Lookup(f.lStr).NoOptDefVal = f.dv.extdef - } - if f.v.sl != nil { - pflag.StringArrayVarP(f.v.sl, f.lStr, f.sStr, []string{f.dv.string}, f.help) - } - if f.v.il != nil { - pflag.IntSliceVarP(f.v.il, f.lStr, f.sStr, []int{f.dv.int}, f.help) - } -} -func flagExt() { - 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" - } -} -func getCliFlag() { - 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("选择串口") - cfg.PortName = ports[s] - - s, _ = inf.NewSingleSelect( - bauds, - singleselect.WithKeyBinding(selectKeymap), - singleselect.WithPageSize(4), - ).Display("选择波特率") - 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("启用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("启用时间戳"), - ).Display() - cfg.TimesTamp = v - if v { - b, _ := inf.NewText( - text.WithPrompt("格式化字段:"), - text.WithPromptStyle(theme.DefaultTheme.PromptStyle), - text.WithDefaultValue(timeExt.dv.extdef), - ).Display() - cfg.TimesFmt = b - } - v, _ = inf.NewConfirmWithSelection( - confirm.WithPrompt("启用高级配置"), - ).Display() - if v { - s, _ = inf.NewSingleSelect( - datas, - singleselect.WithKeyBinding(selectKeymap), - singleselect.WithPageSize(4), - singleselect.WithFilterInput(inputs), - ).Display("选择数据位") - cfg.DataBits, _ = strconv.Atoi(datas[s]) - - s, _ = inf.NewSingleSelect( - stops, - singleselect.WithKeyBinding(selectKeymap), - singleselect.WithPageSize(4), - singleselect.WithFilterInput(inputs), - ).Display("选择停止位") - cfg.StopBits = s - - s, _ = inf.NewSingleSelect( - paritys, - singleselect.WithKeyBinding(selectKeymap), - singleselect.WithPageSize(4), - singleselect.WithFilterInput(inputs), - ).Display("选择校验位") - cfg.ParityBit = s - - t, _ := inf.NewText( - text.WithPrompt("换行符:"), - text.WithPromptStyle(theme.DefaultTheme.PromptStyle), - text.WithDefaultValue(endStr.dv.string), - ).Display() - cfg.EndStr = t - - v, _ = inf.NewConfirmWithSelection( - confirm.WithDefaultYes(), - confirm.WithPrompt("启用编码转换"), - ).Display() - - if v { - t, _ = inf.NewText( - text.WithPrompt("输入编码:"), - text.WithPromptStyle(theme.DefaultTheme.PromptStyle), - text.WithDefaultValue(inputCode.dv.string), - ).Display() - cfg.InputCode = t - - t, _ = inf.NewText( - text.WithPrompt("输出编码:"), - text.WithPromptStyle(theme.DefaultTheme.PromptStyle), - text.WithDefaultValue(outputCode.dv.string), - ).Display() - cfg.OutputCode = t - } - G_F_mode: - s, _ = inf.NewSingleSelect( - forwards, - singleselect.WithKeyBinding(selectKeymap), - singleselect.WithPageSize(3), - singleselect.WithFilterInput(inputs), - ).Display("选择转发模式") - if s != 0 { - cfg.ForWard = append(cfg.ForWard, s) - t, _ = inf.NewText( - text.WithPrompt("地址:"), - text.WithPromptStyle(theme.DefaultTheme.PromptStyle), - text.WithDefaultValue(address.dv.string), - ).Display() - cfg.Address = append(cfg.Address, t) - goto G_F_mode - } - - e, _ := inf.NewConfirmWithSelection( - confirm.WithDefaultYes(), - confirm.WithPrompt("启用日志"), - ).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 - } - } - -} diff --git a/internal/flag/flag.go b/internal/flag/flag.go new file mode 100644 index 0000000..d30e351 --- /dev/null +++ b/internal/flag/flag.go @@ -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 + } + } +} diff --git a/main.go b/main.go index 99721bc..0d975f5 100644 --- a/main.go +++ b/main.go @@ -11,15 +11,14 @@ import ( "strconv" "strings" + "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/flag" "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/session" "golang.org/x/term" ) func init() { log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile | log.Lmsgprefix) - for _, f := range flags { - flagInit(&f) - } + flag.Init(cfg) } func main() { @@ -30,16 +29,16 @@ func main() { } }() - normalizeFlags() + flag.Normalize() pflag.Parse() - flagExt() + flag.Ext(cfg) if cfg.PortName == "" { - getCliFlag() + flag.GetCliFlag(cfg) } ports, err := session.CheckPortAvailability(cfg.PortName) if err != nil { fmt.Println(err) - printUsage(ports) + flag.PrintUsage(ports) os.Exit(0) }