From d3eb2b7d9208af7a3368790c6be8e306e64b009f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sat, 11 Nov 2023 10:44:40 +0800 Subject: [PATCH 1/5] adds initial implementation for macro --- go.mod | 1 + go.sum | 1 + pkg/cli/cli.go | 6 ++++++ pkg/cli/commands.go | 10 ++++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2764fa1..8105976 100644 --- a/go.mod +++ b/go.mod @@ -17,4 +17,5 @@ require ( github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.13.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 95f22c6..13d523a 100644 --- a/go.sum +++ b/go.sum @@ -26,4 +26,5 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index bdb6218..0742062 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -80,6 +80,11 @@ func (c *CLI) Run(opts RunOptions) error { } }() + macro, err := LoadMacro("./example-macro-preset.yml") + if err != nil { + return err + } + fmt.Println(macro) c.hideCursor() keysEvents, err := c.input.GetKeys() @@ -98,6 +103,7 @@ func (c *CLI) Run(opts RunOptions) error { input: keysEvents, cli: c, outputFile: opts.OutputFile, + macro: macro, } for { diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 0ae82b0..79a6683 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -20,13 +20,14 @@ type ExecutionContext struct { input <-chan keyboard.KeyEvent cli *CLI outputFile io.Writer + macro *Macro } type Executer interface { Execute(*ExecutionContext) (Executer, error) } -func CommandFactory(raw string) (Executer, error) { +func CommandFactory(raw string, macro *Macro) (Executer, error) { if raw == "" { return nil, fmt.Errorf("empty command") } @@ -64,6 +65,11 @@ func CommandFactory(raw string) (Executer, error) { return NewCommandWaitForResp(timeout), nil default: + if macro != nil { + if command, ok := (*macro)[cmd]; ok { + return command, nil + } + } return nil, fmt.Errorf("unknown command: %s", cmd) } } @@ -205,7 +211,7 @@ func (c *CommandCmdEdit) Execute(exCtx *ExecutionContext) (Executer, error) { return nil, err } - cmd, err := CommandFactory(rawCmd) + cmd, err := CommandFactory(rawCmd, exCtx.macro) if err != nil { color.New(color.FgRed).Fprintln(exCtx.cli.output, err) From 67aca808c87bb3314aeac2217f818c22b0c28bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sat, 11 Nov 2023 10:44:49 +0800 Subject: [PATCH 2/5] adds initial implementation for macro --- example-macro-preset.yml | 9 ++++++++ pkg/cli/macro.go | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 example-macro-preset.yml create mode 100644 pkg/cli/macro.go diff --git a/example-macro-preset.yml b/example-macro-preset.yml new file mode 100644 index 0000000..3c18c27 --- /dev/null +++ b/example-macro-preset.yml @@ -0,0 +1,9 @@ +version: '1' + +domains: + - derivws.com + - binaryws.com + - deriv.dev + +macro: + ticks: ['edit {"ticks": "R_50"}'] \ No newline at end of file diff --git a/pkg/cli/macro.go b/pkg/cli/macro.go new file mode 100644 index 0000000..ab59151 --- /dev/null +++ b/pkg/cli/macro.go @@ -0,0 +1,50 @@ +package cli + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Version string `yaml:"version"` + Macro map[string][]string `yaml:"macro"` + Domains []string `yaml:"domains"` +} + +type Macro map[string]Executer + +func LoadMacro(path string) (*Macro, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var cfg Config + if err = yaml.Unmarshal(data, &cfg); err != nil { + return nil, err + } + + if cfg.Version != "1" { + return nil, fmt.Errorf("unsupported macro version: %s", cfg.Version) + } + + macro := make(Macro) + for name, rawCommands := range cfg.Macro { + var command Executer + for _, rawCommand := range rawCommands { + cmd, err := CommandFactory(rawCommand, nil) + if err != nil { + return nil, err + } + + command = cmd + break + } + + macro[name] = command + } + + return ¯o, nil +} From 72ba2636928ec33db1f140a93986c0a1643bcdc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sat, 11 Nov 2023 11:08:42 +0800 Subject: [PATCH 3/5] Makes linter happy --- pkg/cli/cli.go | 2 +- pkg/cli/commands.go | 1 + pkg/cli/macro.go | 15 ++++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 0742062..c71d590 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -84,7 +84,7 @@ func (c *CLI) Run(opts RunOptions) error { if err != nil { return err } - fmt.Println(macro) + c.hideCursor() keysEvents, err := c.input.GetKeys() diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 79a6683..ad6366e 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -70,6 +70,7 @@ func CommandFactory(raw string, macro *Macro) (Executer, error) { return command, nil } } + return nil, fmt.Errorf("unknown command: %s", cmd) } } diff --git a/pkg/cli/macro.go b/pkg/cli/macro.go index ab59151..354d2b9 100644 --- a/pkg/cli/macro.go +++ b/pkg/cli/macro.go @@ -22,7 +22,7 @@ func LoadMacro(path string) (*Macro, error) { } var cfg Config - if err = yaml.Unmarshal(data, &cfg); err != nil { + if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, err } @@ -31,19 +31,24 @@ func LoadMacro(path string) (*Macro, error) { } macro := make(Macro) + for name, rawCommands := range cfg.Macro { - var command Executer + var commands []Executer + for _, rawCommand := range rawCommands { cmd, err := CommandFactory(rawCommand, nil) if err != nil { return nil, err } - command = cmd - break + commands = append(commands, cmd) + } + + if len(commands) == 0 { + return nil, fmt.Errorf("empty macro: %s", name) } - macro[name] = command + macro[name] = commands[0] } return ¯o, nil From ac0f51c425112cf6994a7abe92ba79dbae39d71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sat, 11 Nov 2023 12:40:45 +0800 Subject: [PATCH 4/5] Adds support for macro with multiple commands --- pkg/cli/commands.go | 25 ++++++++++++++-- pkg/cli/macro.go | 69 +++++++++++++++++++++++++++++++++------------ 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index ad6366e..68e71b2 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -66,9 +66,7 @@ func CommandFactory(raw string, macro *Macro) (Executer, error) { return NewCommandWaitForResp(timeout), nil default: if macro != nil { - if command, ok := (*macro)[cmd]; ok { - return command, nil - } + return macro.Get(cmd) } return nil, fmt.Errorf("unknown command: %s", cmd) @@ -221,3 +219,24 @@ func (c *CommandCmdEdit) Execute(exCtx *ExecutionContext) (Executer, error) { return cmd, nil } + +type CommandSequence struct { + subCommands []Executer +} + +func NewCommandSequence(subCommands []Executer) *CommandSequence { + return &CommandSequence{subCommands} +} + +func (c *CommandSequence) Execute(exCtx *ExecutionContext) (Executer, error) { + for _, cmd := range c.subCommands { + for cmd != nil { + var err error + if cmd, err = cmd.Execute(exCtx); err != nil { + return nil, err + } + } + } + + return nil, nil +} diff --git a/pkg/cli/macro.go b/pkg/cli/macro.go index 354d2b9..32ccdb5 100644 --- a/pkg/cli/macro.go +++ b/pkg/cli/macro.go @@ -13,7 +13,53 @@ type Config struct { Domains []string `yaml:"domains"` } -type Macro map[string]Executer +type Macro struct { + macro map[string]Executer + domains []string +} + +func NewMacro(domains []string) *Macro { + return &Macro{ + macro: make(map[string]Executer), + domains: domains, + } +} + +func (m *Macro) AddCommands(name string, rawCommands []string) error { + if _, ok := m.macro[name]; ok { + return fmt.Errorf("macro already exists: %s", name) + } + + commands := []Executer{} + + for _, rawCommand := range rawCommands { + cmd, err := CommandFactory(rawCommand, nil) + if err != nil { + return err + } + + commands = append(commands, cmd) + } + + switch len(commands) { + case 0: + return fmt.Errorf("empty macro: %s", name) + case 1: + m.macro[name] = commands[0] + default: + m.macro[name] = NewCommandSequence(commands) + } + + return nil +} + +func (m *Macro) Get(name string) (Executer, error) { + if cmd, ok := m.macro[name]; ok { + return cmd, nil + } + + return nil, fmt.Errorf("unknown command: %s", name) +} func LoadMacro(path string) (*Macro, error) { data, err := os.ReadFile(path) @@ -30,26 +76,13 @@ func LoadMacro(path string) (*Macro, error) { return nil, fmt.Errorf("unsupported macro version: %s", cfg.Version) } - macro := make(Macro) + macroCfg := NewMacro(cfg.Domains) for name, rawCommands := range cfg.Macro { - var commands []Executer - - for _, rawCommand := range rawCommands { - cmd, err := CommandFactory(rawCommand, nil) - if err != nil { - return nil, err - } - - commands = append(commands, cmd) + if err := macroCfg.AddCommands(name, rawCommands); err != nil { + return nil, err } - - if len(commands) == 0 { - return nil, fmt.Errorf("empty macro: %s", name) - } - - macro[name] = commands[0] } - return ¯o, nil + return macroCfg, nil } From 6d9128f7706804507d3459e4e9ed50a3598fad91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sat, 11 Nov 2023 19:07:31 +0800 Subject: [PATCH 5/5] Add loading macro presets --- cmd/wsget/main.go | 1 + pkg/cli/cli.go | 23 +++++++++++------- pkg/cli/commands.go | 3 +-- pkg/cli/macro.go | 58 ++++++++++++++++++++++++++++++++++++++++++++- pkg/ws/ws.go | 13 +++++++--- 5 files changed, 84 insertions(+), 14 deletions(-) diff --git a/cmd/wsget/main.go b/cmd/wsget/main.go index 5bb33d3..4d9d80c 100644 --- a/cmd/wsget/main.go +++ b/cmd/wsget/main.go @@ -89,6 +89,7 @@ func run(cmd *cobra.Command, args []string) { client, err := cli.NewCLI(wsConn, input, os.Stdout) if err != nil { color.New(color.FgRed).Println("Unable to start CLI: ", err) + return } opts := cli.RunOptions{} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c71d590..09ae458 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -13,8 +13,11 @@ import ( ) const ( - HistoryFilename = ".wsget_history" - HistoryCmdFilename = ".wsget_cmd_history" + MacroDir = "macro" + ConfigDir = ".wsget" + HistoryFilename = ConfigDir + "/history" + HistoryCmdFilename = ConfigDir + "/cmd_history" + ConfigDirMode = 0o755 CommandsLimit = 100 HistoryLimit = 100 @@ -34,6 +37,7 @@ type CLI struct { input Inputer output io.Writer commands chan Executer + macro *Macro } type RunOptions struct { @@ -53,10 +57,18 @@ func NewCLI(wsConn *ws.Connection, input Inputer, output io.Writer) (*CLI, error } homeDir := currentUser.HomeDir + if err = os.MkdirAll(homeDir+"/"+ConfigDir+"/"+MacroDir, ConfigDirMode); err != nil { + return nil, fmt.Errorf("fail to get current user: %s", err) + } history := NewHistory(homeDir+"/"+HistoryFilename, HistoryLimit) cmdHistory := NewHistory(homeDir+"/"+HistoryCmdFilename, HistoryLimit) + macro, err := LoadMacroForDomain(homeDir+"/"+ConfigDir+"/"+MacroDir, wsConn.Hostname) + if err != nil { + return nil, fmt.Errorf("fail to load macro: %s", err) + } + commands := make(chan Executer, CommandsLimit) return &CLI{ @@ -67,6 +79,7 @@ func NewCLI(wsConn *ws.Connection, input Inputer, output io.Writer) (*CLI, error input: input, output: output, commands: commands, + macro: macro, }, nil } @@ -80,11 +93,6 @@ func (c *CLI) Run(opts RunOptions) error { } }() - macro, err := LoadMacro("./example-macro-preset.yml") - if err != nil { - return err - } - c.hideCursor() keysEvents, err := c.input.GetKeys() @@ -103,7 +111,6 @@ func (c *CLI) Run(opts RunOptions) error { input: keysEvents, cli: c, outputFile: opts.OutputFile, - macro: macro, } for { diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 68e71b2..3beea4a 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -20,7 +20,6 @@ type ExecutionContext struct { input <-chan keyboard.KeyEvent cli *CLI outputFile io.Writer - macro *Macro } type Executer interface { @@ -210,7 +209,7 @@ func (c *CommandCmdEdit) Execute(exCtx *ExecutionContext) (Executer, error) { return nil, err } - cmd, err := CommandFactory(rawCmd, exCtx.macro) + cmd, err := CommandFactory(rawCmd, exCtx.cli.macro) if err != nil { color.New(color.FgRed).Fprintln(exCtx.cli.output, err) diff --git a/pkg/cli/macro.go b/pkg/cli/macro.go index 32ccdb5..a96fd91 100644 --- a/pkg/cli/macro.go +++ b/pkg/cli/macro.go @@ -2,7 +2,9 @@ package cli import ( "fmt" + "log" "os" + "strings" "gopkg.in/yaml.v3" ) @@ -53,6 +55,18 @@ func (m *Macro) AddCommands(name string, rawCommands []string) error { return nil } +func (m *Macro) merge(macro *Macro) error { + for name, cmd := range macro.macro { + if _, ok := m.macro[name]; ok { + return fmt.Errorf("duplicate macro name: %s", name) + } + + m.macro[name] = cmd + } + + return nil +} + func (m *Macro) Get(name string) (Executer, error) { if cmd, ok := m.macro[name]; ok { return cmd, nil @@ -73,7 +87,7 @@ func LoadMacro(path string) (*Macro, error) { } if cfg.Version != "1" { - return nil, fmt.Errorf("unsupported macro version: %s", cfg.Version) + return nil, fmt.Errorf("unsupported macro file version: %s", path) } macroCfg := NewMacro(cfg.Domains) @@ -86,3 +100,45 @@ func LoadMacro(path string) (*Macro, error) { return macroCfg, nil } + +func LoadMacroForDomain(macroDir, domain string) (*Macro, error) { + files, err := os.ReadDir(macroDir) + if err != nil { + log.Fatal(err) + } + + var macro *Macro + + for _, file := range files { + fileMacro, err := LoadMacro(macroDir + "/" + file.Name()) + + if err != nil { + return nil, err + } + + hasDomain := false + + for _, fileDomain := range fileMacro.domains { + if strings.HasSuffix(domain, fileDomain) { + hasDomain = true + break + } + } + + if !hasDomain { + continue + } + + if macro == nil { + macro = fileMacro + } else { + err := macro.merge(fileMacro) + + if err != nil { + return nil, fmt.Errorf("fail to loading macro from file %s, %s ", file.Name(), err) + } + } + } + + return macro, nil +} diff --git a/pkg/ws/ws.go b/pkg/ws/ws.go index 52e62c8..0e60e3c 100644 --- a/pkg/ws/ws.go +++ b/pkg/ws/ws.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "fmt" "net/http" + "net/url" "strings" "sync" "sync/atomic" @@ -45,6 +46,7 @@ type Connection struct { ws *websocket.Conn Messages chan Message waitGroup *sync.WaitGroup + Hostname string isClosed atomic.Bool } @@ -53,8 +55,13 @@ type Options struct { SkipSSLVerification bool } -func NewWS(url string, opts Options) (*Connection, error) { - cfg, err := websocket.NewConfig(url, "http://localhost") +func NewWS(wsURL string, opts Options) (*Connection, error) { + parsedURL, err := url.Parse(wsURL) + if err != nil { + return nil, err + } + + cfg, err := websocket.NewConfig(wsURL, "http://localhost") if err != nil { return nil, err } @@ -94,7 +101,7 @@ func NewWS(url string, opts Options) (*Connection, error) { messages := make(chan Message, WSMessageBufferSize) - wsInsp := &Connection{ws: ws, Messages: messages, waitGroup: &waitGroup} + wsInsp := &Connection{ws: ws, Messages: messages, waitGroup: &waitGroup, Hostname: parsedURL.Hostname()} go func() { defer func() {