mirror of
https://github.com/jixishi/SerialTerminalForWindowsTerminal.git
synced 2026-06-15 16:42:46 +00:00
refactor: extract internal/config and eliminate global config var
Move Config struct to internal/config with exported fields. Replace global var config with package-level cfg pointer. Add OpenLogFile to config package. Add type alias Config = appconfig.Config in main package for backward compatibility. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
|
||||||
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/event"
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/event"
|
||||||
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/charset"
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/charset"
|
||||||
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
|
||||||
@@ -35,7 +36,7 @@ type App struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(cfg *Config) (*App, error) {
|
func NewApp(cfg *Config) (*App, error) {
|
||||||
f, err := openLogFile()
|
f, err := appconfig.OpenLogFile(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -168,16 +169,16 @@ func (a *App) waitDone() <-chan struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) loadConfiguredForwards() {
|
func (a *App) loadConfiguredForwards() {
|
||||||
for i, mode := range config.forWard {
|
for i, mode := range a.cfg.ForWard {
|
||||||
m := forward.Mode(mode)
|
m := forward.Mode(mode)
|
||||||
if m == forward.None {
|
if m == forward.None {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if i >= len(config.address) {
|
if i >= len(a.cfg.Address) {
|
||||||
a.Notifyf("[forward] skip #%d: missing address", i)
|
a.Notifyf("[forward] skip #%d: missing address", i)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
addr := strings.TrimSpace(config.address[i])
|
addr := strings.TrimSpace(a.cfg.Address[i])
|
||||||
if addr == "" {
|
if addr == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -192,12 +193,12 @@ func (a *App) reportForwardIngress(id int, chunk []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.EqualFold(a.cfg.inputCode, "hex") {
|
if strings.EqualFold(a.cfg.InputCode, "hex") {
|
||||||
a.Notifyf("[forward#%d -> serial] % X\n", id, chunk)
|
a.Notifyf("[forward#%d -> serial] % X\n", id, chunk)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
converted, err := charset.ConvertChunk(chunk, a.cfg.inputCode, a.cfg.outputCode)
|
converted, err := charset.ConvertChunk(chunk, a.cfg.InputCode, a.cfg.OutputCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
converted = bytes.Clone(chunk)
|
converted = bytes.Clone(chunk)
|
||||||
}
|
}
|
||||||
@@ -236,7 +237,7 @@ func (a *App) sendLine(line string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := append([]byte(line), []byte(a.cfg.endStr)...)
|
payload := append([]byte(line), []byte(a.cfg.EndStr)...)
|
||||||
return a.writeToSession(payload)
|
return a.writeToSession(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +284,7 @@ func (a *App) handleLine(line string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) startOutputLoop() {
|
func (a *App) startOutputLoop() {
|
||||||
if strings.EqualFold(a.cfg.inputCode, "hex") {
|
if strings.EqualFold(a.cfg.InputCode, "hex") {
|
||||||
go a.readHexOutput()
|
go a.readHexOutput()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -292,7 +293,7 @@ func (a *App) startOutputLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) readHexOutput() {
|
func (a *App) readHexOutput() {
|
||||||
frameSize := a.cfg.frameSize
|
frameSize := a.cfg.FrameSize
|
||||||
if frameSize <= 0 {
|
if frameSize <= 0 {
|
||||||
frameSize = 16
|
frameSize = 16
|
||||||
}
|
}
|
||||||
@@ -312,7 +313,7 @@ func (a *App) readHexOutput() {
|
|||||||
if len(outChunk) == 0 {
|
if len(outChunk) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: charset.FormatHexFrame(outChunk, a.cfg.timesTamp, a.cfg.timesFmt)})
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: charset.FormatHexFrame(outChunk, a.cfg.TimesTamp, a.cfg.TimesFmt)})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
@@ -347,15 +348,15 @@ func (a *App) readTextOutput() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
converted, convErr := charset.ConvertChunk(outChunk, a.cfg.inputCode, a.cfg.outputCode)
|
converted, convErr := charset.ConvertChunk(outChunk, a.cfg.InputCode, a.cfg.OutputCode)
|
||||||
if convErr != nil {
|
if convErr != nil {
|
||||||
a.Notifyf("[output] convert failed: %v", convErr)
|
a.Notifyf("[output] convert failed: %v", convErr)
|
||||||
converted = bytes.Clone(outChunk)
|
converted = bytes.Clone(outChunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := string(converted)
|
text := string(converted)
|
||||||
if a.cfg.timesTamp {
|
if a.cfg.TimesTamp {
|
||||||
text = prefixLines(text, time.Now().Format(a.cfg.timesFmt)+" ")
|
text = prefixLines(text, time.Now().Format(a.cfg.TimesFmt)+" ")
|
||||||
}
|
}
|
||||||
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: text})
|
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: text})
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-11
@@ -61,7 +61,7 @@ func TestAppUIEvents(t *testing.T) {
|
|||||||
func TestSendLine(t *testing.T) {
|
func TestSendLine(t *testing.T) {
|
||||||
setupTestPipes()
|
setupTestPipes()
|
||||||
a := &App{
|
a := &App{
|
||||||
cfg: &Config{endStr: "\r\n"},
|
cfg: &Config{EndStr: "\r\n"},
|
||||||
plugins: luaplugin.NewManager(),
|
plugins: luaplugin.NewManager(),
|
||||||
uiEvents: make(chan event.UIEvent, 8),
|
uiEvents: make(chan event.UIEvent, 8),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
@@ -83,7 +83,7 @@ func TestSendLine(t *testing.T) {
|
|||||||
func TestHandleLine(t *testing.T) {
|
func TestHandleLine(t *testing.T) {
|
||||||
setupTestPipes()
|
setupTestPipes()
|
||||||
a := &App{
|
a := &App{
|
||||||
cfg: &Config{endStr: "\n", inputCode: "UTF-8", outputCode: "UTF-8"},
|
cfg: &Config{EndStr: "\n", InputCode: "UTF-8", OutputCode: "UTF-8"},
|
||||||
plugins: luaplugin.NewManager(),
|
plugins: luaplugin.NewManager(),
|
||||||
uiEvents: make(chan event.UIEvent, 8),
|
uiEvents: make(chan event.UIEvent, 8),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
@@ -159,22 +159,19 @@ func TestAppClose(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfiguredForwards(t *testing.T) {
|
func TestLoadConfiguredForwards(t *testing.T) {
|
||||||
oldCfg := config
|
|
||||||
defer func() { config = oldCfg }()
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen failed: %v", err)
|
t.Fatalf("listen failed: %v", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
config = Config{
|
testCfg := &Config{
|
||||||
forWard: []int{int(forward.TCP), int(forward.None), int(forward.UDP)},
|
ForWard: []int{int(forward.TCP), int(forward.None), int(forward.UDP)},
|
||||||
address: []string{listener.Addr().String(), "", ""},
|
Address: []string{listener.Addr().String(), "", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &App{
|
a := &App{
|
||||||
cfg: &config,
|
cfg: testCfg,
|
||||||
forward: forward.NewManager(func([]byte) error { return nil }, func(string, ...any) {}),
|
forward: forward.NewManager(func([]byte) error { return nil }, func(string, ...any) {}),
|
||||||
uiEvents: make(chan event.UIEvent, 8),
|
uiEvents: make(chan event.UIEvent, 8),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
@@ -191,7 +188,7 @@ func TestLoadConfiguredForwards(t *testing.T) {
|
|||||||
|
|
||||||
func TestReportForwardIngress(t *testing.T) {
|
func TestReportForwardIngress(t *testing.T) {
|
||||||
a := &App{
|
a := &App{
|
||||||
cfg: &Config{inputCode: "UTF-8", outputCode: "UTF-8"},
|
cfg: &Config{InputCode: "UTF-8", OutputCode: "UTF-8"},
|
||||||
uiEvents: make(chan event.UIEvent, 4),
|
uiEvents: make(chan event.UIEvent, 4),
|
||||||
}
|
}
|
||||||
a.SetUIEnabled(true)
|
a.SetUIEnabled(true)
|
||||||
@@ -199,7 +196,7 @@ func TestReportForwardIngress(t *testing.T) {
|
|||||||
a.reportForwardIngress(1, []byte("test"))
|
a.reportForwardIngress(1, []byte("test"))
|
||||||
|
|
||||||
// Hex mode
|
// Hex mode
|
||||||
a.cfg.inputCode = "hex"
|
a.cfg.InputCode = "hex"
|
||||||
a.reportForwardIngress(2, []byte{0x41, 0x42})
|
a.reportForwardIngress(2, []byte{0x41, 0x42})
|
||||||
|
|
||||||
// Empty chunk
|
// Empty chunk
|
||||||
|
|||||||
+13
-13
@@ -414,13 +414,13 @@ func (d *CommandDispatcher) handleModeCommand(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.app.Notifyf("[mode] input=%s output=%s end=%q hex=%v frame=%d timestamp=%v timefmt=%q forwardTargets=%d plugins=%d",
|
d.app.Notifyf("[mode] input=%s output=%s end=%q hex=%v frame=%d timestamp=%v timefmt=%q forwardTargets=%d plugins=%d",
|
||||||
d.app.cfg.inputCode,
|
d.app.cfg.InputCode,
|
||||||
d.app.cfg.outputCode,
|
d.app.cfg.OutputCode,
|
||||||
d.app.cfg.endStr,
|
d.app.cfg.EndStr,
|
||||||
strings.EqualFold(d.app.cfg.inputCode, "hex"),
|
strings.EqualFold(d.app.cfg.InputCode, "hex"),
|
||||||
d.app.cfg.frameSize,
|
d.app.cfg.FrameSize,
|
||||||
d.app.cfg.timesTamp,
|
d.app.cfg.TimesTamp,
|
||||||
d.app.cfg.timesFmt,
|
d.app.cfg.TimesFmt,
|
||||||
len(d.app.forward.List()),
|
len(d.app.forward.List()),
|
||||||
len(d.app.plugins.List()),
|
len(d.app.plugins.List()),
|
||||||
)
|
)
|
||||||
@@ -439,25 +439,25 @@ func (d *CommandDispatcher) handleModeCommand(args []string) error {
|
|||||||
|
|
||||||
switch field {
|
switch field {
|
||||||
case "in":
|
case "in":
|
||||||
d.app.cfg.inputCode = value
|
d.app.cfg.InputCode = value
|
||||||
case "out":
|
case "out":
|
||||||
d.app.cfg.outputCode = value
|
d.app.cfg.OutputCode = value
|
||||||
case "end":
|
case "end":
|
||||||
d.app.cfg.endStr = value
|
d.app.cfg.EndStr = value
|
||||||
case "frame":
|
case "frame":
|
||||||
n, err := strconv.Atoi(value)
|
n, err := strconv.Atoi(value)
|
||||||
if err != nil || n <= 0 {
|
if err != nil || n <= 0 {
|
||||||
return fmt.Errorf("frame must be a positive integer")
|
return fmt.Errorf("frame must be a positive integer")
|
||||||
}
|
}
|
||||||
d.app.cfg.frameSize = n
|
d.app.cfg.FrameSize = n
|
||||||
case "timestamp":
|
case "timestamp":
|
||||||
enabled, ok := parseOnOff(value)
|
enabled, ok := parseOnOff(value)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("timestamp value must be on/off")
|
return fmt.Errorf("timestamp value must be on/off")
|
||||||
}
|
}
|
||||||
d.app.cfg.timesTamp = enabled
|
d.app.cfg.TimesTamp = enabled
|
||||||
case "timefmt":
|
case "timefmt":
|
||||||
d.app.cfg.timesFmt = value
|
d.app.cfg.TimesFmt = value
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown mode field: %s", field)
|
return fmt.Errorf("unknown mode field: %s", field)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -26,7 +26,7 @@ func setupTestPipes() {
|
|||||||
|
|
||||||
func newTestAppForCommand() *App {
|
func newTestAppForCommand() *App {
|
||||||
a := &App{
|
a := &App{
|
||||||
cfg: &Config{inputCode: "UTF-8", outputCode: "UTF-8", endStr: "\n"},
|
cfg: &Config{InputCode: "UTF-8", OutputCode: "UTF-8", EndStr: "\n"},
|
||||||
plugins: luaplugin.NewManager(),
|
plugins: luaplugin.NewManager(),
|
||||||
uiEvents: make(chan event.UIEvent, 32),
|
uiEvents: make(chan event.UIEvent, 32),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
@@ -149,15 +149,15 @@ func TestCommandExecuteModeSet(t *testing.T) {
|
|||||||
if err != nil || !handled {
|
if err != nil || !handled {
|
||||||
t.Fatalf(".mode set end failed handled=%v err=%v", handled, err)
|
t.Fatalf(".mode set end failed handled=%v err=%v", handled, err)
|
||||||
}
|
}
|
||||||
if a.cfg.endStr != "\\r\\n" {
|
if a.cfg.EndStr != "\\r\\n" {
|
||||||
t.Fatalf("mode set end not applied, got=%q", a.cfg.endStr)
|
t.Fatalf("mode set end not applied, got=%q", a.cfg.EndStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
handled, err = a.dispatcher.Execute(".mode set timestamp on")
|
handled, err = a.dispatcher.Execute(".mode set timestamp on")
|
||||||
if err != nil || !handled {
|
if err != nil || !handled {
|
||||||
t.Fatalf(".mode set timestamp failed handled=%v err=%v", handled, err)
|
t.Fatalf(".mode set timestamp failed handled=%v err=%v", handled, err)
|
||||||
}
|
}
|
||||||
if !a.cfg.timesTamp {
|
if !a.cfg.TimesTamp {
|
||||||
t.Fatalf("mode set timestamp should enable timesTamp")
|
t.Fatalf("mode set timestamp should enable timesTamp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,32 +298,32 @@ func TestCommandExecuteModeSetAll(t *testing.T) {
|
|||||||
if err != nil || !handled {
|
if err != nil || !handled {
|
||||||
t.Fatalf(".mode set frame failed: handled=%v err=%v", handled, err)
|
t.Fatalf(".mode set frame failed: handled=%v err=%v", handled, err)
|
||||||
}
|
}
|
||||||
if a.cfg.frameSize != 32 {
|
if a.cfg.FrameSize != 32 {
|
||||||
t.Fatalf("frameSize not set, got=%d", a.cfg.frameSize)
|
t.Fatalf("frameSize not set, got=%d", a.cfg.FrameSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
handled, err = a.dispatcher.Execute(".mode set timefmt 2006")
|
handled, err = a.dispatcher.Execute(".mode set timefmt 2006")
|
||||||
if err != nil || !handled {
|
if err != nil || !handled {
|
||||||
t.Fatalf(".mode set timefmt failed: handled=%v err=%v", handled, err)
|
t.Fatalf(".mode set timefmt failed: handled=%v err=%v", handled, err)
|
||||||
}
|
}
|
||||||
if a.cfg.timesFmt != "2006" {
|
if a.cfg.TimesFmt != "2006" {
|
||||||
t.Fatalf("timesFmt not set, got=%q", a.cfg.timesFmt)
|
t.Fatalf("timesFmt not set, got=%q", a.cfg.TimesFmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
handled, err = a.dispatcher.Execute(".mode set out GBK")
|
handled, err = a.dispatcher.Execute(".mode set out GBK")
|
||||||
if err != nil || !handled {
|
if err != nil || !handled {
|
||||||
t.Fatalf(".mode set out failed: handled=%v err=%v", handled, err)
|
t.Fatalf(".mode set out failed: handled=%v err=%v", handled, err)
|
||||||
}
|
}
|
||||||
if a.cfg.outputCode != "GBK" {
|
if a.cfg.OutputCode != "GBK" {
|
||||||
t.Fatalf("outputCode not set, got=%q", a.cfg.outputCode)
|
t.Fatalf("outputCode not set, got=%q", a.cfg.OutputCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
handled, err = a.dispatcher.Execute(".mode set in GBK")
|
handled, err = a.dispatcher.Execute(".mode set in GBK")
|
||||||
if err != nil || !handled {
|
if err != nil || !handled {
|
||||||
t.Fatalf(".mode set in failed: handled=%v err=%v", handled, err)
|
t.Fatalf(".mode set in failed: handled=%v err=%v", handled, err)
|
||||||
}
|
}
|
||||||
if a.cfg.inputCode != "GBK" {
|
if a.cfg.InputCode != "GBK" {
|
||||||
t.Fatalf("inputCode not set, got=%q", a.cfg.inputCode)
|
t.Fatalf("inputCode not set, got=%q", a.cfg.InputCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
// Config is an alias for appconfig.Config to keep main-package code concise.
|
||||||
portName string
|
type Config = appconfig.Config
|
||||||
baudRate int
|
|
||||||
dataBits int
|
|
||||||
stopBits int
|
|
||||||
parityBit int
|
|
||||||
outputCode string
|
|
||||||
inputCode string
|
|
||||||
endStr string
|
|
||||||
enableLog bool
|
|
||||||
logFilePath string
|
|
||||||
forWard []int
|
|
||||||
frameSize int
|
|
||||||
timesTamp bool
|
|
||||||
timesFmt string
|
|
||||||
address []string
|
|
||||||
enableGUI bool
|
|
||||||
hotkeyMod string
|
|
||||||
}
|
|
||||||
|
|
||||||
var config Config
|
var cfg = &Config{}
|
||||||
|
|
||||||
func openLogFile() (*os.File, error) {
|
|
||||||
if config.enableLog {
|
|
||||||
path := fmt.Sprintf(config.logFilePath, config.portName, time.Now().Format("2006_01_02T150405"))
|
|
||||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|||||||
+31
-30
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
|
||||||
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
|
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,16 +56,16 @@ func TestParseForwardMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenLogFile(t *testing.T) {
|
func TestOpenLogFile(t *testing.T) {
|
||||||
old := config
|
old := *cfg
|
||||||
defer func() { config = old }()
|
defer func() { *cfg = old }()
|
||||||
|
|
||||||
config = Config{
|
*cfg = Config{
|
||||||
enableLog: true,
|
EnableLog: true,
|
||||||
portName: "COM1",
|
PortName: "COM1",
|
||||||
logFilePath: filepath.Join(t.TempDir(), "%s-%s.log"),
|
LogFilePath: filepath.Join(t.TempDir(), "%s-%s.log"),
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := openLogFile()
|
f, err := appconfig.OpenLogFile(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("openLogFile() unexpected error: %v", err)
|
t.Fatalf("openLogFile() unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -73,8 +74,8 @@ func TestOpenLogFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
|
|
||||||
config.enableLog = false
|
cfg.EnableLog = false
|
||||||
f, err = openLogFile()
|
f, err = appconfig.OpenLogFile(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("openLogFile() unexpected error with enableLog=false: %v", err)
|
t.Fatalf("openLogFile() unexpected error with enableLog=false: %v", err)
|
||||||
}
|
}
|
||||||
@@ -114,55 +115,55 @@ func TestFlagFindValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFlagExt(t *testing.T) {
|
func TestFlagExt(t *testing.T) {
|
||||||
old := config
|
old := *cfg
|
||||||
defer func() { config = old }()
|
defer func() { *cfg = old }()
|
||||||
|
|
||||||
config = Config{}
|
*cfg = Config{}
|
||||||
flagExt()
|
flagExt()
|
||||||
if config.enableLog {
|
if cfg.EnableLog {
|
||||||
t.Fatalf("expected enableLog=false when logFilePath empty")
|
t.Fatalf("expected enableLog=false when logFilePath empty")
|
||||||
}
|
}
|
||||||
if config.timesTamp {
|
if cfg.TimesTamp {
|
||||||
t.Fatalf("expected timesTamp=false when timesFmt empty")
|
t.Fatalf("expected timesTamp=false when timesFmt empty")
|
||||||
}
|
}
|
||||||
if config.hotkeyMod != "ctrl+alt" {
|
if cfg.HotkeyMod != "ctrl+alt" {
|
||||||
t.Fatalf("expected default hotkeyMod=ctrl+alt, got=%q", config.hotkeyMod)
|
t.Fatalf("expected default hotkeyMod=ctrl+alt, got=%q", cfg.HotkeyMod)
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{logFilePath: "/tmp/log.txt"}
|
*cfg = Config{LogFilePath: "/tmp/log.txt"}
|
||||||
flagExt()
|
flagExt()
|
||||||
if !config.enableLog {
|
if !cfg.EnableLog {
|
||||||
t.Fatalf("expected enableLog=true when logFilePath set")
|
t.Fatalf("expected enableLog=true when logFilePath set")
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{timesFmt: "2006-01-02"}
|
*cfg = Config{TimesFmt: "2006-01-02"}
|
||||||
flagExt()
|
flagExt()
|
||||||
if !config.timesTamp {
|
if !cfg.TimesTamp {
|
||||||
t.Fatalf("expected timesTamp=true when timesFmt set")
|
t.Fatalf("expected timesTamp=true when timesFmt set")
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{hotkeyMod: ""}
|
*cfg = Config{HotkeyMod: ""}
|
||||||
flagExt()
|
flagExt()
|
||||||
if config.hotkeyMod != "ctrl+alt" {
|
if cfg.HotkeyMod != "ctrl+alt" {
|
||||||
t.Fatalf("empty hotkeyMod should default to ctrl+alt")
|
t.Fatalf("empty hotkeyMod should default to ctrl+alt")
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{hotkeyMod: "ctrl+shift"}
|
*cfg = Config{HotkeyMod: "ctrl+shift"}
|
||||||
flagExt()
|
flagExt()
|
||||||
if config.hotkeyMod != "ctrl+shift" {
|
if cfg.HotkeyMod != "ctrl+shift" {
|
||||||
t.Fatalf("expected ctrl+shift preserved")
|
t.Fatalf("expected ctrl+shift preserved")
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{hotkeyMod: " CTRL+SHIFT "}
|
*cfg = Config{HotkeyMod: " CTRL+SHIFT "}
|
||||||
flagExt()
|
flagExt()
|
||||||
if config.hotkeyMod != "ctrl+shift" {
|
if cfg.HotkeyMod != "ctrl+shift" {
|
||||||
t.Fatalf("expected whitespace+case normalization, got=%q", config.hotkeyMod)
|
t.Fatalf("expected whitespace+case normalization, got=%q", cfg.HotkeyMod)
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{hotkeyMod: "invalid"}
|
*cfg = Config{HotkeyMod: "invalid"}
|
||||||
flagExt()
|
flagExt()
|
||||||
if config.hotkeyMod != "ctrl+alt" {
|
if cfg.HotkeyMod != "ctrl+alt" {
|
||||||
t.Fatalf("invalid hotkeyMod should default to ctrl+alt, got=%q", config.hotkeyMod)
|
t.Fatalf("invalid hotkeyMod should default to ctrl+alt, got=%q", cfg.HotkeyMod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -60,10 +60,10 @@ func TestParseCSIu(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIsExitHotkeySeq(t *testing.T) {
|
func TestIsExitHotkeySeq(t *testing.T) {
|
||||||
oldCfg := config
|
oldCfg := *cfg
|
||||||
defer func() { config = oldCfg }()
|
defer func() { *cfg = oldCfg }()
|
||||||
|
|
||||||
config = Config{hotkeyMod: "ctrl+alt"}
|
*cfg = Config{HotkeyMod: "ctrl+alt"}
|
||||||
|
|
||||||
// CSI u Ctrl+Alt+C (mod=6)
|
// CSI u Ctrl+Alt+C (mod=6)
|
||||||
if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '6', 'u'}) {
|
if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '6', 'u'}) {
|
||||||
@@ -88,7 +88,7 @@ func TestIsExitHotkeySeq(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Switch to ctrl+shift
|
// Switch to ctrl+shift
|
||||||
config = Config{hotkeyMod: "ctrl+shift"}
|
*cfg = Config{HotkeyMod: "ctrl+shift"}
|
||||||
|
|
||||||
if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '5', 'u'}) {
|
if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '5', 'u'}) {
|
||||||
t.Fatalf("Ctrl+Shift+C should exit with ctrl+shift config")
|
t.Fatalf("Ctrl+Shift+C should exit with ctrl+shift config")
|
||||||
@@ -111,7 +111,7 @@ func TestIsExitHotkeySeq(t *testing.T) {
|
|||||||
t.Fatalf("plain bytes should not exit")
|
t.Fatalf("plain bytes should not exit")
|
||||||
}
|
}
|
||||||
|
|
||||||
config = Config{hotkeyMod: "ctrl+alt"}
|
*cfg = Config{HotkeyMod: "ctrl+alt"}
|
||||||
// Ctrl only (mod=4) should not exit (requires Alt too)
|
// Ctrl only (mod=4) should not exit (requires Alt too)
|
||||||
if isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '4', 'u'}) {
|
if isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '4', 'u'}) {
|
||||||
t.Fatalf("Ctrl+C (without Alt) should not exit")
|
t.Fatalf("Ctrl+C (without Alt) should not exit")
|
||||||
|
|||||||
@@ -47,21 +47,21 @@ type Flag struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
portName = Flag{ptrVal{string: &config.portName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"}
|
portName = Flag{ptrVal{string: &cfg.PortName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"}
|
||||||
baudRate = Flag{ptrVal{int: &config.baudRate}, "b", "baud", Val{int: 115200}, "波特率"}
|
baudRate = Flag{ptrVal{int: &cfg.BaudRate}, "b", "baud", Val{int: 115200}, "波特率"}
|
||||||
dataBits = Flag{ptrVal{int: &config.dataBits}, "d", "data", Val{int: 8}, "数据位"}
|
dataBits = Flag{ptrVal{int: &cfg.DataBits}, "d", "data", Val{int: 8}, "数据位"}
|
||||||
stopBits = Flag{ptrVal{int: &config.stopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"}
|
stopBits = Flag{ptrVal{int: &cfg.StopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"}
|
||||||
outputCode = Flag{ptrVal{string: &config.outputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"}
|
outputCode = Flag{ptrVal{string: &cfg.OutputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"}
|
||||||
inputCode = Flag{ptrVal{string: &config.inputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"}
|
inputCode = Flag{ptrVal{string: &cfg.InputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"}
|
||||||
endStr = Flag{ptrVal{string: &config.endStr}, "e", "end", Val{string: "\n"}, "终端换行符"}
|
endStr = Flag{ptrVal{string: &cfg.EndStr}, "e", "end", Val{string: "\n"}, "终端换行符"}
|
||||||
logExt = Flag{v: ptrVal{ext: &config.logFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"}
|
logExt = Flag{v: ptrVal{ext: &cfg.LogFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"}
|
||||||
timeExt = Flag{v: ptrVal{ext: &config.timesFmt}, sStr: "t", lStr: "time", dv: Val{extdef: "[06-01-02 15:04:05.000]", 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: &config.forWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"}
|
forWard = Flag{ptrVal{il: &cfg.ForWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"}
|
||||||
address = Flag{ptrVal{sl: &config.address}, "a", "address", Val{string: "127.0.0.1:12345"}, "转发服务地址(支持多次传入)"}
|
address = Flag{ptrVal{sl: &cfg.Address}, "a", "address", Val{string: "127.0.0.1:12345"}, "转发服务地址(支持多次传入)"}
|
||||||
frameSize = Flag{ptrVal{int: &config.frameSize}, "F", "Frame", Val{int: 16}, "帧大小"}
|
frameSize = Flag{ptrVal{int: &cfg.FrameSize}, "F", "Frame", Val{int: 16}, "帧大小"}
|
||||||
parityBit = Flag{ptrVal{int: &config.parityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"}
|
parityBit = Flag{ptrVal{int: &cfg.ParityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"}
|
||||||
guiMode = Flag{ptrVal{bool: &config.enableGUI}, "g", "gui", Val{bool: false}, "启用TUI交互界面"}
|
guiMode = Flag{ptrVal{bool: &cfg.EnableGUI}, "g", "gui", Val{bool: false}, "启用TUI交互界面"}
|
||||||
hotkeyMod = Flag{ptrVal{string: &config.hotkeyMod}, "k", "hotkey-mod", Val{string: "ctrl+alt"}, "本地快捷键修饰(ctrl+alt|ctrl+shift)"}
|
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}
|
flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, forWard, address, frameSize, parityBit, logExt, timeExt, guiMode, hotkeyMod}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -182,18 +182,18 @@ func flagInit(f *Flag) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func flagExt() {
|
func flagExt() {
|
||||||
if config.logFilePath != "" {
|
if cfg.LogFilePath != "" {
|
||||||
config.enableLog = true
|
cfg.EnableLog = true
|
||||||
}
|
}
|
||||||
if config.timesFmt != "" {
|
if cfg.TimesFmt != "" {
|
||||||
config.timesTamp = true
|
cfg.TimesTamp = true
|
||||||
}
|
}
|
||||||
if config.hotkeyMod == "" {
|
if cfg.HotkeyMod == "" {
|
||||||
config.hotkeyMod = "ctrl+alt"
|
cfg.HotkeyMod = "ctrl+alt"
|
||||||
}
|
}
|
||||||
config.hotkeyMod = strings.ToLower(strings.TrimSpace(config.hotkeyMod))
|
cfg.HotkeyMod = strings.ToLower(strings.TrimSpace(cfg.HotkeyMod))
|
||||||
if config.hotkeyMod != "ctrl+alt" && config.hotkeyMod != "ctrl+shift" {
|
if cfg.HotkeyMod != "ctrl+alt" && cfg.HotkeyMod != "ctrl+shift" {
|
||||||
config.hotkeyMod = "ctrl+alt"
|
cfg.HotkeyMod = "ctrl+alt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func getCliFlag() {
|
func getCliFlag() {
|
||||||
@@ -230,7 +230,7 @@ func getCliFlag() {
|
|||||||
singleselect.WithPageSize(4),
|
singleselect.WithPageSize(4),
|
||||||
singleselect.WithFilterInput(inputs),
|
singleselect.WithFilterInput(inputs),
|
||||||
).Display("选择串口")
|
).Display("选择串口")
|
||||||
config.portName = ports[s]
|
cfg.PortName = ports[s]
|
||||||
|
|
||||||
s, _ = inf.NewSingleSelect(
|
s, _ = inf.NewSingleSelect(
|
||||||
bauds,
|
bauds,
|
||||||
@@ -238,38 +238,38 @@ func getCliFlag() {
|
|||||||
singleselect.WithPageSize(4),
|
singleselect.WithPageSize(4),
|
||||||
).Display("选择波特率")
|
).Display("选择波特率")
|
||||||
if s != 0 {
|
if s != 0 {
|
||||||
config.baudRate, _ = strconv.Atoi(bauds[s])
|
cfg.BaudRate, _ = strconv.Atoi(bauds[s])
|
||||||
} else {
|
} else {
|
||||||
b, _ := inf.NewText(
|
b, _ := inf.NewText(
|
||||||
text.WithPrompt("BaudRate:"),
|
text.WithPrompt("BaudRate:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue("115200"),
|
text.WithDefaultValue("115200"),
|
||||||
).Display()
|
).Display()
|
||||||
config.baudRate, _ = strconv.Atoi(b)
|
cfg.BaudRate, _ = strconv.Atoi(b)
|
||||||
}
|
}
|
||||||
v, _ := inf.NewConfirmWithSelection(
|
v, _ := inf.NewConfirmWithSelection(
|
||||||
confirm.WithPrompt("启用Hex"),
|
confirm.WithPrompt("启用Hex"),
|
||||||
).Display()
|
).Display()
|
||||||
if v {
|
if v {
|
||||||
config.inputCode = "hex"
|
cfg.InputCode = "hex"
|
||||||
b, _ := inf.NewText(
|
b, _ := inf.NewText(
|
||||||
text.WithPrompt("Frames:"),
|
text.WithPrompt("Frames:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue("16"),
|
text.WithDefaultValue("16"),
|
||||||
).Display()
|
).Display()
|
||||||
config.frameSize, _ = strconv.Atoi(b)
|
cfg.FrameSize, _ = strconv.Atoi(b)
|
||||||
}
|
}
|
||||||
v, _ = inf.NewConfirmWithSelection(
|
v, _ = inf.NewConfirmWithSelection(
|
||||||
confirm.WithPrompt("启用时间戳"),
|
confirm.WithPrompt("启用时间戳"),
|
||||||
).Display()
|
).Display()
|
||||||
config.timesTamp = v
|
cfg.TimesTamp = v
|
||||||
if v {
|
if v {
|
||||||
b, _ := inf.NewText(
|
b, _ := inf.NewText(
|
||||||
text.WithPrompt("格式化字段:"),
|
text.WithPrompt("格式化字段:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue(timeExt.dv.extdef),
|
text.WithDefaultValue(timeExt.dv.extdef),
|
||||||
).Display()
|
).Display()
|
||||||
config.timesFmt = b
|
cfg.TimesFmt = b
|
||||||
}
|
}
|
||||||
v, _ = inf.NewConfirmWithSelection(
|
v, _ = inf.NewConfirmWithSelection(
|
||||||
confirm.WithPrompt("启用高级配置"),
|
confirm.WithPrompt("启用高级配置"),
|
||||||
@@ -281,7 +281,7 @@ func getCliFlag() {
|
|||||||
singleselect.WithPageSize(4),
|
singleselect.WithPageSize(4),
|
||||||
singleselect.WithFilterInput(inputs),
|
singleselect.WithFilterInput(inputs),
|
||||||
).Display("选择数据位")
|
).Display("选择数据位")
|
||||||
config.dataBits, _ = strconv.Atoi(datas[s])
|
cfg.DataBits, _ = strconv.Atoi(datas[s])
|
||||||
|
|
||||||
s, _ = inf.NewSingleSelect(
|
s, _ = inf.NewSingleSelect(
|
||||||
stops,
|
stops,
|
||||||
@@ -289,7 +289,7 @@ func getCliFlag() {
|
|||||||
singleselect.WithPageSize(4),
|
singleselect.WithPageSize(4),
|
||||||
singleselect.WithFilterInput(inputs),
|
singleselect.WithFilterInput(inputs),
|
||||||
).Display("选择停止位")
|
).Display("选择停止位")
|
||||||
config.stopBits = s
|
cfg.StopBits = s
|
||||||
|
|
||||||
s, _ = inf.NewSingleSelect(
|
s, _ = inf.NewSingleSelect(
|
||||||
paritys,
|
paritys,
|
||||||
@@ -297,14 +297,14 @@ func getCliFlag() {
|
|||||||
singleselect.WithPageSize(4),
|
singleselect.WithPageSize(4),
|
||||||
singleselect.WithFilterInput(inputs),
|
singleselect.WithFilterInput(inputs),
|
||||||
).Display("选择校验位")
|
).Display("选择校验位")
|
||||||
config.parityBit = s
|
cfg.ParityBit = s
|
||||||
|
|
||||||
t, _ := inf.NewText(
|
t, _ := inf.NewText(
|
||||||
text.WithPrompt("换行符:"),
|
text.WithPrompt("换行符:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue(endStr.dv.string),
|
text.WithDefaultValue(endStr.dv.string),
|
||||||
).Display()
|
).Display()
|
||||||
config.endStr = t
|
cfg.EndStr = t
|
||||||
|
|
||||||
v, _ = inf.NewConfirmWithSelection(
|
v, _ = inf.NewConfirmWithSelection(
|
||||||
confirm.WithDefaultYes(),
|
confirm.WithDefaultYes(),
|
||||||
@@ -317,14 +317,14 @@ func getCliFlag() {
|
|||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue(inputCode.dv.string),
|
text.WithDefaultValue(inputCode.dv.string),
|
||||||
).Display()
|
).Display()
|
||||||
config.inputCode = t
|
cfg.InputCode = t
|
||||||
|
|
||||||
t, _ = inf.NewText(
|
t, _ = inf.NewText(
|
||||||
text.WithPrompt("输出编码:"),
|
text.WithPrompt("输出编码:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue(outputCode.dv.string),
|
text.WithDefaultValue(outputCode.dv.string),
|
||||||
).Display()
|
).Display()
|
||||||
config.outputCode = t
|
cfg.OutputCode = t
|
||||||
}
|
}
|
||||||
G_F_mode:
|
G_F_mode:
|
||||||
s, _ = inf.NewSingleSelect(
|
s, _ = inf.NewSingleSelect(
|
||||||
@@ -334,13 +334,13 @@ func getCliFlag() {
|
|||||||
singleselect.WithFilterInput(inputs),
|
singleselect.WithFilterInput(inputs),
|
||||||
).Display("选择转发模式")
|
).Display("选择转发模式")
|
||||||
if s != 0 {
|
if s != 0 {
|
||||||
config.forWard = append(config.forWard, s)
|
cfg.ForWard = append(cfg.ForWard, s)
|
||||||
t, _ = inf.NewText(
|
t, _ = inf.NewText(
|
||||||
text.WithPrompt("地址:"),
|
text.WithPrompt("地址:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue(address.dv.string),
|
text.WithDefaultValue(address.dv.string),
|
||||||
).Display()
|
).Display()
|
||||||
config.address = append(config.address, t)
|
cfg.Address = append(cfg.Address, t)
|
||||||
goto G_F_mode
|
goto G_F_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,14 +348,14 @@ func getCliFlag() {
|
|||||||
confirm.WithDefaultYes(),
|
confirm.WithDefaultYes(),
|
||||||
confirm.WithPrompt("启用日志"),
|
confirm.WithPrompt("启用日志"),
|
||||||
).Display()
|
).Display()
|
||||||
config.enableLog = e
|
cfg.EnableLog = e
|
||||||
if e {
|
if e {
|
||||||
t, _ = inf.NewText(
|
t, _ = inf.NewText(
|
||||||
text.WithPrompt("Path:"),
|
text.WithPrompt("Path:"),
|
||||||
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
|
||||||
text.WithDefaultValue("./%s-$s.txt"),
|
text.WithDefaultValue("./%s-$s.txt"),
|
||||||
).Display()
|
).Display()
|
||||||
config.logFilePath = t
|
cfg.LogFilePath = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Package config holds the application configuration.
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds all application settings.
|
||||||
|
type Config struct {
|
||||||
|
PortName string
|
||||||
|
BaudRate int
|
||||||
|
DataBits int
|
||||||
|
StopBits int
|
||||||
|
ParityBit int
|
||||||
|
OutputCode string
|
||||||
|
InputCode string
|
||||||
|
EndStr string
|
||||||
|
EnableLog bool
|
||||||
|
LogFilePath string
|
||||||
|
ForWard []int
|
||||||
|
FrameSize int
|
||||||
|
TimesTamp bool
|
||||||
|
TimesFmt string
|
||||||
|
Address []string
|
||||||
|
EnableGUI bool
|
||||||
|
HotkeyMod string
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenLogFile opens the configured log file for writing, or returns nil if logging is disabled.
|
||||||
|
func OpenLogFile(cfg *Config) (*os.File, error) {
|
||||||
|
if cfg.EnableLog {
|
||||||
|
path := fmt.Sprintf(cfg.LogFilePath, cfg.PortName, time.Now().Format("2006_01_02T150405"))
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
@@ -32,10 +32,10 @@ func main() {
|
|||||||
normalizeFlags()
|
normalizeFlags()
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
flagExt()
|
flagExt()
|
||||||
if config.portName == "" {
|
if cfg.PortName == "" {
|
||||||
getCliFlag()
|
getCliFlag()
|
||||||
}
|
}
|
||||||
ports, err := checkPortAvailability(config.portName)
|
ports, err := checkPortAvailability(cfg.PortName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
printUsage(ports)
|
printUsage(ports)
|
||||||
@@ -52,7 +52,7 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := NewApp(&config)
|
app, err := NewApp(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "create app failed: %v\n", err)
|
fmt.Fprintf(os.Stderr, "create app failed: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -63,9 +63,9 @@ func main() {
|
|||||||
app.startOutputLoop()
|
app.startOutputLoop()
|
||||||
|
|
||||||
go forwardInterruptToRemote(app)
|
go forwardInterruptToRemote(app)
|
||||||
app.SetUIEnabled(config.enableGUI)
|
app.SetUIEnabled(cfg.EnableGUI)
|
||||||
|
|
||||||
if config.enableGUI {
|
if cfg.EnableGUI {
|
||||||
model := newUIModel(app)
|
model := newUIModel(app)
|
||||||
p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithoutSignalHandler())
|
p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithoutSignalHandler())
|
||||||
if _, err = p.Run(); err != nil {
|
if _, err = p.Run(); err != nil {
|
||||||
@@ -287,7 +287,7 @@ func runConsole(app *App) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b == '\r' || b == '\n' {
|
if b == '\r' || b == '\n' {
|
||||||
if err = app.writeToSession([]byte(config.endStr)); err != nil {
|
if err = app.writeToSession([]byte(cfg.EndStr)); err != nil {
|
||||||
app.Statusf("[send] %v", err)
|
app.Statusf("[send] %v", err)
|
||||||
}
|
}
|
||||||
lineStart = true
|
lineStart = true
|
||||||
@@ -328,7 +328,7 @@ func parseCSIu(seq []byte) (cp int, mod int, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isAltKeyExit(b byte) bool {
|
func isAltKeyExit(b byte) bool {
|
||||||
if normalizeHotkeyPrefix(config.hotkeyMod) != "ctrl+alt" {
|
if normalizeHotkeyPrefix(cfg.HotkeyMod) != "ctrl+alt" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// 0x2E = scan code for 'C', 0x03 = Ctrl+C, 0x63 = 'c', 0x43 = 'C'
|
// 0x2E = scan code for 'C', 0x03 = Ctrl+C, 0x63 = 'c', 0x43 = 'C'
|
||||||
@@ -336,7 +336,7 @@ func isAltKeyExit(b byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isExitHotkeySeq(seq []byte) bool {
|
func isExitHotkeySeq(seq []byte) bool {
|
||||||
mod := normalizeHotkeyPrefix(config.hotkeyMod)
|
mod := normalizeHotkeyPrefix(cfg.HotkeyMod)
|
||||||
|
|
||||||
// CSI u format: ESC [ codepoint ; modifier u
|
// CSI u format: ESC [ codepoint ; modifier u
|
||||||
// Only matches when the Ctrl modifier bit (4) is present,
|
// Only matches when the Ctrl modifier bit (4) is present,
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func handleLocalHotkey(m *uiModel, key string) bool {
|
func handleLocalHotkey(m *uiModel, key string) bool {
|
||||||
if m.isLocalHotkey(key, "h") {
|
if m.isLocalHotkey(key, "h") {
|
||||||
modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.hotkeyMod))
|
modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.HotkeyMod))
|
||||||
m.app.ShowModal("Shortcuts", modifier+"+C => local exit\nCtrl+C => remote interrupt\n"+modifier+"+F => forward panel\n"+modifier+"+P => plugin panel\n"+modifier+"+M => mode panel\nF1 => shortcut help")
|
m.app.ShowModal("Shortcuts", modifier+"+C => local exit\nCtrl+C => remote interrupt\n"+modifier+"+F => forward panel\n"+modifier+"+P => plugin panel\n"+modifier+"+M => mode panel\nF1 => shortcut help")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ func (m *uiModel) isLocalHotkey(key, action string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := normalizeHotkeyPrefix(m.app.cfg.hotkeyMod)
|
mod := normalizeHotkeyPrefix(m.app.cfg.HotkeyMod)
|
||||||
if mod == "ctrl+shift" {
|
if mod == "ctrl+shift" {
|
||||||
return hasCtrl && hasShift
|
return hasCtrl && hasShift
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -160,7 +160,7 @@ func (m *uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if m.isLocalHotkey(keyStr, "c") {
|
if m.isLocalHotkey(keyStr, "c") {
|
||||||
m.app.Statusf("[local] exiting by %s+C", strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.hotkeyMod)))
|
m.app.Statusf("[local] exiting by %s+C", strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.HotkeyMod)))
|
||||||
m.app.Close()
|
m.app.Close()
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ func (m *uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
// Some terminals can't encode Ctrl+Alt/Shift+H distinctly and report Ctrl+H.
|
// Some terminals can't encode Ctrl+Alt/Shift+H distinctly and report Ctrl+H.
|
||||||
if keyStr == "ctrl+h" {
|
if keyStr == "ctrl+h" {
|
||||||
handleLocalHotkey(m, hotkeyWith(m.app.cfg.hotkeyMod, "h"))
|
handleLocalHotkey(m, hotkeyWith(m.app.cfg.HotkeyMod, "h"))
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ func (m *uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
switch keyStr {
|
switch keyStr {
|
||||||
case "f1":
|
case "f1":
|
||||||
handleLocalHotkey(m, hotkeyWith(m.app.cfg.hotkeyMod, "h"))
|
handleLocalHotkey(m, hotkeyWith(m.app.cfg.HotkeyMod, "h"))
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case "tab", "shift+tab":
|
case "tab", "shift+tab":
|
||||||
@@ -245,7 +245,7 @@ func (m *uiModel) View() string {
|
|||||||
} else if len(m.suggestions) == 1 {
|
} else if len(m.suggestions) == 1 {
|
||||||
suggest = "Tab: " + m.suggestions[0]
|
suggest = "Tab: " + m.suggestions[0]
|
||||||
}
|
}
|
||||||
modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.hotkeyMod))
|
modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.HotkeyMod))
|
||||||
hotkeys := "Hotkeys: Ctrl+C remote | " + modifier + "+C local | " + modifier + "+F forward | " + modifier + "+P plugins | " + modifier + "+M mode | F1 help"
|
hotkeys := "Hotkeys: Ctrl+C remote | " + modifier + "+C local | " + modifier + "+F forward | " + modifier + "+P plugins | " + modifier + "+M mode | F1 help"
|
||||||
hotkeys = lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("245")).Render(hotkeys)
|
hotkeys = lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("245")).Render(hotkeys)
|
||||||
status := m.statusLine
|
status := m.statusLine
|
||||||
|
|||||||
+2
-2
@@ -71,7 +71,7 @@ func (m *uiModel) refreshPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *uiModel) buildModeItems() []modeItem {
|
func (m *uiModel) buildModeItems() []modeItem {
|
||||||
return []modeItem{{"in", "Input Charset", m.app.cfg.inputCode}, {"out", "Output Charset", m.app.cfg.outputCode}, {"end", "Line End", fmt.Sprintf("%q", m.app.cfg.endStr)}, {"frame", "Hex Frame Size", fmt.Sprintf("%d", m.app.cfg.frameSize)}, {"timestamp", "Timestamp", fmt.Sprintf("%v", m.app.cfg.timesTamp)}, {"timefmt", "Timestamp Format", m.app.cfg.timesFmt}}
|
return []modeItem{{"in", "Input Charset", m.app.cfg.InputCode}, {"out", "Output Charset", m.app.cfg.OutputCode}, {"end", "Line End", fmt.Sprintf("%q", m.app.cfg.EndStr)}, {"frame", "Hex Frame Size", fmt.Sprintf("%d", m.app.cfg.FrameSize)}, {"timestamp", "Timestamp", fmt.Sprintf("%v", m.app.cfg.TimesTamp)}, {"timefmt", "Timestamp Format", m.app.cfg.TimesFmt}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *uiModel) handleForwardPanelKey(key string) bool {
|
func (m *uiModel) handleForwardPanelKey(key string) bool {
|
||||||
@@ -213,7 +213,7 @@ func (m *uiModel) handleModePanelKey(key string) bool {
|
|||||||
switch key {
|
switch key {
|
||||||
case " ":
|
case " ":
|
||||||
if sel.key == "timestamp" {
|
if sel.key == "timestamp" {
|
||||||
if m.app.cfg.timesTamp {
|
if m.app.cfg.TimesTamp {
|
||||||
m.app.handleLine(".mode set timestamp off")
|
m.app.handleLine(".mode set timestamp off")
|
||||||
} else {
|
} else {
|
||||||
m.app.handleLine(".mode set timestamp on")
|
m.app.handleLine(".mode set timestamp on")
|
||||||
|
|||||||
+7
-7
@@ -42,7 +42,7 @@ func TestRenderModal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleCtrlShiftLocalHelp(t *testing.T) {
|
func TestHandleCtrlShiftLocalHelp(t *testing.T) {
|
||||||
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}}
|
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
|
||||||
a.SetUIEnabled(true)
|
a.SetUIEnabled(true)
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ func TestIsLocalHotkeyAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
a := &App{cfg: &Config{hotkeyMod: tt.mod}}
|
a := &App{cfg: &Config{HotkeyMod: tt.mod}}
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
got := m.isLocalHotkey(tt.key, tt.action)
|
got := m.isLocalHotkey(tt.key, tt.action)
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
@@ -216,7 +216,7 @@ func TestMaxIntFunc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleLocalHotkeyForward(t *testing.T) {
|
func TestHandleLocalHotkeyForward(t *testing.T) {
|
||||||
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}}
|
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
|
||||||
a.SetUIEnabled(true)
|
a.SetUIEnabled(true)
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ func TestHandleLocalHotkeyForward(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleLocalHotkeyPlugin(t *testing.T) {
|
func TestHandleLocalHotkeyPlugin(t *testing.T) {
|
||||||
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}}
|
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
|
||||||
a.SetUIEnabled(true)
|
a.SetUIEnabled(true)
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ func TestHandleLocalHotkeyPlugin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleLocalHotkeyMode(t *testing.T) {
|
func TestHandleLocalHotkeyMode(t *testing.T) {
|
||||||
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}}
|
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
|
||||||
a.SetUIEnabled(true)
|
a.SetUIEnabled(true)
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ func TestHandleLocalHotkeyMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleLocalHotkeyUnknown(t *testing.T) {
|
func TestHandleLocalHotkeyUnknown(t *testing.T) {
|
||||||
a := &App{cfg: &Config{hotkeyMod: "ctrl+alt"}}
|
a := &App{cfg: &Config{HotkeyMod: "ctrl+alt"}}
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
|
|
||||||
if handleLocalHotkey(&m, "ctrl+alt+x") {
|
if handleLocalHotkey(&m, "ctrl+alt+x") {
|
||||||
@@ -267,7 +267,7 @@ func TestHandleLocalHotkeyUnknown(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleLocalHotkeyCtrlShift(t *testing.T) {
|
func TestHandleLocalHotkeyCtrlShift(t *testing.T) {
|
||||||
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+shift"}}
|
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+shift"}}
|
||||||
a.SetUIEnabled(true)
|
a.SetUIEnabled(true)
|
||||||
m := uiModel{app: a}
|
m := uiModel{app: a}
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ func checkPortAvailability(name string) ([]string, error) {
|
|||||||
|
|
||||||
func OpenSerial() error {
|
func OpenSerial() error {
|
||||||
mode := &serial.Mode{
|
mode := &serial.Mode{
|
||||||
BaudRate: config.baudRate,
|
BaudRate: cfg.BaudRate,
|
||||||
StopBits: serial.StopBits(config.stopBits),
|
StopBits: serial.StopBits(cfg.StopBits),
|
||||||
DataBits: config.dataBits,
|
DataBits: cfg.DataBits,
|
||||||
Parity: serial.Parity(config.parityBit),
|
Parity: serial.Parity(cfg.ParityBit),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
serialPort, err = serial.Open(config.portName, mode)
|
serialPort, err = serial.Open(cfg.PortName, mode)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user