mirror of https://github.com/curusarn/resh
commit
d4a5ad8e6d
@ -1 +1,2 @@ |
||||
bin/* |
||||
.vscode/* |
||||
@ -0,0 +1,56 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
"os/user" |
||||
"path/filepath" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/curusarn/resh/pkg/cfg" |
||||
) |
||||
|
||||
func main() { |
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
configPath := filepath.Join(dir, ".config/resh.toml") |
||||
|
||||
var config cfg.Config |
||||
_, err := toml.DecodeFile(configPath, &config) |
||||
if err != nil { |
||||
fmt.Println("Error reading config", err) |
||||
os.Exit(1) |
||||
} |
||||
|
||||
configKey := flag.String("key", "", "Key of the requested config entry") |
||||
flag.Parse() |
||||
|
||||
if *configKey == "" { |
||||
fmt.Println("Error: expected option --key!") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
switch *configKey { |
||||
case "BindArrowKeysBash": |
||||
fallthrough |
||||
case "bindArrowKeysBash": |
||||
printBoolNormalized(config.BindArrowKeysBash) |
||||
case "BindArrowKeysZsh": |
||||
fallthrough |
||||
case "bindArrowKeysZsh": |
||||
printBoolNormalized(config.BindArrowKeysZsh) |
||||
default: |
||||
fmt.Println("Error: illegal --key!") |
||||
os.Exit(1) |
||||
} |
||||
} |
||||
|
||||
// this might be unnecessary but I'm too lazy to look it up
|
||||
func printBoolNormalized(x bool) { |
||||
if x { |
||||
fmt.Println("true") |
||||
} else { |
||||
fmt.Println("false") |
||||
} |
||||
} |
||||
@ -1,24 +1,107 @@ |
||||
package cmd |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/user" |
||||
"path/filepath" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/curusarn/resh/cmd/control/status" |
||||
"github.com/curusarn/resh/pkg/cfg" |
||||
"github.com/spf13/cobra" |
||||
) |
||||
|
||||
var enableCmd = &cobra.Command{ |
||||
Use: "enable", |
||||
Short: "enable RESH features", |
||||
Long: `Enables RESH bindings for arrows and C-R.`, |
||||
Short: "enable RESH features (arrow key bindings)", |
||||
} |
||||
|
||||
var enableArrowKeyBindingsCmd = &cobra.Command{ |
||||
Use: "arrow_key_bindings", |
||||
Short: "enable bindings for arrow keys (up/down) FOR THIS SHELL SESSION", |
||||
Run: func(cmd *cobra.Command, args []string) { |
||||
exitCode = status.EnableAll |
||||
exitCode = status.EnableArrowKeyBindings |
||||
}, |
||||
} |
||||
|
||||
// var enableRecallingCmd = &cobra.Command{
|
||||
// Use: "keybind",
|
||||
// Short: "Enables RESH bindings for arrows and C-R.",
|
||||
// Long: `Enables RESH bindings for arrows and C-R.`,
|
||||
// Run: func(cmd *cobra.Command, args []string) {
|
||||
// exitCode = status.EnableAll
|
||||
// },
|
||||
// }
|
||||
var enableArrowKeyBindingsGlobalCmd = &cobra.Command{ |
||||
Use: "arrow_key_bindings_global", |
||||
Short: "enable bindings for arrow keys (up/down) FOR FUTURE SHELL SESSIONS", |
||||
Long: "Enable bindings for arrow keys (up/down) FOR FUTURE SHELL SESSIONS.\n" + |
||||
"Note that this only affects sessions of the same shell.\n" + |
||||
"(e.g. running this in zsh will only affect future zsh sessions)", |
||||
Run: func(cmd *cobra.Command, args []string) { |
||||
exitCode = enableDisableArrowKeyBindingsGlobally(true) |
||||
}, |
||||
} |
||||
|
||||
func enableDisableArrowKeyBindingsGlobally(value bool) status.Code { |
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
configPath := filepath.Join(dir, ".config/resh.toml") |
||||
var config cfg.Config |
||||
if _, err := toml.DecodeFile(configPath, &config); err != nil { |
||||
fmt.Println("Error reading config", err) |
||||
return status.Fail |
||||
} |
||||
shell, found := os.LookupEnv("__RESH_ctl_shell") |
||||
// shell env variable must be set and must be equal to either bash or zsh
|
||||
if found == false || (shell != "bash" && shell != "zsh") { |
||||
fmt.Println("Error while determining a shell you are using - your RESH instalation is probably broken. Please reinstall RESH - exiting!") |
||||
fmt.Println("found=", found, "shell=", shell) |
||||
return status.Fail |
||||
} |
||||
if shell == "bash" { |
||||
err := setConfigBindArrowKey(configPath, &config, &config.BindArrowKeysBash, shell, value) |
||||
if err != nil { |
||||
return status.Fail |
||||
} |
||||
} else if shell == "zsh" { |
||||
err := setConfigBindArrowKey(configPath, &config, &config.BindArrowKeysZsh, shell, value) |
||||
if err != nil { |
||||
return status.Fail |
||||
} |
||||
} else { |
||||
fmt.Println("FATAL ERROR while determining a shell you are using - your RESH instalation is probably broken. Please reinstall RESH - exiting!") |
||||
} |
||||
return status.Success |
||||
} |
||||
|
||||
// I don't like the interface this function has - passing both config structure and a part of it feels wrong
|
||||
// It's ugly and could lead to future errors
|
||||
func setConfigBindArrowKey(configPath string, config *cfg.Config, configField *bool, shell string, value bool) error { |
||||
if *configField == value { |
||||
if value { |
||||
fmt.Println("The RESH arrow key bindings are ALREADY GLOBALLY ENABLED for all future " + shell + " sessions - nothing to do - exiting.") |
||||
} else { |
||||
fmt.Println("The RESH arrow key bindings are ALREADY GLOBALLY DISABLED for all future " + shell + " sessions - nothing to do - exiting.") |
||||
} |
||||
return nil |
||||
} |
||||
if value { |
||||
fmt.Println("ENABLING the RESH arrow key bindings GLOBALLY (in " + shell + ") ...") |
||||
} else { |
||||
fmt.Println("DISABLING the RESH arrow key bindings GLOBALLY (in " + shell + ") ...") |
||||
} |
||||
*configField = value |
||||
|
||||
f, err := os.Create(configPath) |
||||
if err != nil { |
||||
fmt.Println("Error: Failed to create/open file:", configPath, "; error:", err) |
||||
return err |
||||
} |
||||
defer f.Close() |
||||
if err := toml.NewEncoder(f).Encode(config); err != nil { |
||||
fmt.Println("Error: Failed to encode and write the config values to hdd. error:", err) |
||||
return err |
||||
} |
||||
if value { |
||||
fmt.Println("SUCCESSFULLY ENABLED the RESH arrow key bindings GLOBALLY (in " + shell + ") " + |
||||
"- every new (" + shell + ") session will start with enabled RESH arrow key bindings!") |
||||
} else { |
||||
fmt.Println("SUCCESSFULLY DISABLED the RESH arrow key bindings GLOBALLY (in " + shell + ") " + |
||||
"- every new (" + shell + ") session will start with " + shell + " default arrow key bindings!") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
@ -0,0 +1,84 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"net/http" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/curusarn/resh/pkg/cfg" |
||||
"github.com/curusarn/resh/pkg/msg" |
||||
|
||||
"os/user" |
||||
"path/filepath" |
||||
"strconv" |
||||
) |
||||
|
||||
// Version from git set during build
|
||||
var Version string |
||||
|
||||
// Revision from git set during build
|
||||
var Revision string |
||||
|
||||
func main() { |
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
configPath := filepath.Join(dir, "/.config/resh.toml") |
||||
|
||||
var config cfg.Config |
||||
if _, err := toml.DecodeFile(configPath, &config); err != nil { |
||||
log.Fatal("Error reading config:", err) |
||||
} |
||||
|
||||
sessionID := flag.String("sessionID", "", "resh generated session id") |
||||
count := flag.Uint("count", 10, "Number of cmdLines to return") |
||||
flag.Parse() |
||||
|
||||
if *sessionID == "" { |
||||
fmt.Println("Error: you need to specify sessionId") |
||||
} |
||||
|
||||
m := msg.InspectMsg{SessionID: *sessionID, Count: *count} |
||||
resp := SendInspectMsg(m, strconv.Itoa(config.Port)) |
||||
for _, cmdLine := range resp.CmdLines { |
||||
fmt.Println("`" + cmdLine + "'") |
||||
} |
||||
} |
||||
|
||||
// SendInspectMsg to daemon
|
||||
func SendInspectMsg(m msg.InspectMsg, port string) msg.MultiResponse { |
||||
recJSON, err := json.Marshal(m) |
||||
if err != nil { |
||||
log.Fatal("send err 1", err) |
||||
} |
||||
|
||||
req, err := http.NewRequest("POST", "http://localhost:"+port+"/inspect", |
||||
bytes.NewBuffer(recJSON)) |
||||
if err != nil { |
||||
log.Fatal("send err 2", err) |
||||
} |
||||
req.Header.Set("Content-Type", "application/json") |
||||
|
||||
client := &http.Client{} |
||||
resp, err := client.Do(req) |
||||
if err != nil { |
||||
log.Fatal("resh-daemon is not running :(") |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
log.Fatal("read response error") |
||||
} |
||||
// log.Println(string(body))
|
||||
response := msg.MultiResponse{} |
||||
err = json.Unmarshal(body, &response) |
||||
if err != nil { |
||||
log.Fatal("unmarshal resp error: ", err) |
||||
} |
||||
return response |
||||
} |
||||
@ -1,3 +1,6 @@ |
||||
port = 2627 |
||||
sesswatchPeriodSeconds = 120 |
||||
sesshistInitHistorySize = 1000 |
||||
debug = true |
||||
bindArrowKeysBash = false |
||||
bindArrowKeysZsh = true |
||||
|
||||
@ -0,0 +1,64 @@ |
||||
package histlist |
||||
|
||||
import "log" |
||||
|
||||
// Histlist is a deduplicated list of cmdLines
|
||||
type Histlist struct { |
||||
// list of commands lines (deduplicated)
|
||||
List []string |
||||
// lookup: cmdLine -> last index
|
||||
LastIndex map[string]int |
||||
} |
||||
|
||||
// New Histlist
|
||||
func New() Histlist { |
||||
return Histlist{LastIndex: make(map[string]int)} |
||||
} |
||||
|
||||
// Copy Histlist
|
||||
func Copy(hl Histlist) Histlist { |
||||
newHl := New() |
||||
// copy list
|
||||
newHl.List = make([]string, len(hl.List)) |
||||
copy(newHl.List, hl.List) |
||||
// copy map
|
||||
for k, v := range hl.LastIndex { |
||||
newHl.LastIndex[k] = v |
||||
} |
||||
return newHl |
||||
} |
||||
|
||||
// AddCmdLine to the histlist
|
||||
func (h *Histlist) AddCmdLine(cmdLine string) { |
||||
// lenBefore := len(h.List)
|
||||
// lookup
|
||||
idx, found := h.LastIndex[cmdLine] |
||||
if found { |
||||
// remove duplicate
|
||||
if cmdLine != h.List[idx] { |
||||
log.Println("histlist ERROR: Adding cmdLine:", cmdLine, " != LastIndex[cmdLine]:", h.List[idx]) |
||||
} |
||||
h.List = append(h.List[:idx], h.List[idx+1:]...) |
||||
// idx++
|
||||
for idx < len(h.List) { |
||||
cmdLn := h.List[idx] |
||||
h.LastIndex[cmdLn]-- |
||||
if idx != h.LastIndex[cmdLn] { |
||||
log.Println("histlist ERROR: Shifting LastIndex idx:", idx, " != LastIndex[cmdLn]:", h.LastIndex[cmdLn]) |
||||
} |
||||
idx++ |
||||
} |
||||
} |
||||
// update last index
|
||||
h.LastIndex[cmdLine] = len(h.List) |
||||
// append new cmdline
|
||||
h.List = append(h.List, cmdLine) |
||||
// log.Println("histlist: Added cmdLine:", cmdLine, "; history length:", lenBefore, "->", len(h.List))
|
||||
} |
||||
|
||||
// AddHistlist contents of another histlist to this histlist
|
||||
func (h *Histlist) AddHistlist(h2 Histlist) { |
||||
for _, cmdLine := range h2.List { |
||||
h.AddCmdLine(cmdLine) |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
package msg |
||||
|
||||
// InspectMsg struct
|
||||
type InspectMsg struct { |
||||
SessionID string `json:"sessionId"` |
||||
Count uint `json:"count"` |
||||
} |
||||
|
||||
// MultiResponse struct
|
||||
type MultiResponse struct { |
||||
CmdLines []string `json:"cmdlines"` |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
package signalhandler |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"net/http" |
||||
"os" |
||||
"os/signal" |
||||
"strconv" |
||||
"syscall" |
||||
"time" |
||||
) |
||||
|
||||
func sendSignals(sig os.Signal, subscribers []chan os.Signal, done chan string) { |
||||
for _, sub := range subscribers { |
||||
sub <- sig |
||||
} |
||||
chanCount := len(subscribers) |
||||
start := time.Now() |
||||
delay := time.Millisecond * 100 |
||||
timeout := time.Millisecond * 2000 |
||||
|
||||
for { |
||||
select { |
||||
case _ = <-done: |
||||
chanCount-- |
||||
if chanCount == 0 { |
||||
log.Println("signalhandler: All boxes shut down successfully") |
||||
return |
||||
} |
||||
default: |
||||
time.Sleep(delay) |
||||
} |
||||
if time.Since(start) > timeout { |
||||
log.Println("signalhandler: Timouted while waiting for proper shutdown - " + strconv.Itoa(chanCount) + " boxes are up after " + timeout.String()) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Run catches and handles signals
|
||||
func Run(subscribers []chan os.Signal, done chan string, server *http.Server) { |
||||
signals := make(chan os.Signal, 1) |
||||
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) |
||||
|
||||
sig := <-signals |
||||
log.Println("signalhandler: Got signal " + sig.String()) |
||||
|
||||
log.Println("signalhandler: Sending signals to Subscribers") |
||||
sendSignals(sig, subscribers, done) |
||||
|
||||
log.Println("signalhandler: Shutting down the server") |
||||
if err := server.Shutdown(context.Background()); err != nil { |
||||
log.Printf("HTTP server Shutdown: %v", err) |
||||
} |
||||
} |
||||
@ -1 +1 @@ |
||||
Subproject commit 9811ba8b7694cdbd9debed931e922b67e439197a |
||||
Subproject commit f558191d74ae33654d63a0f091034ef27f4b44f9 |
||||
@ -1 +1 @@ |
||||
Subproject commit 7dde81eaa09cbed11ebc70ea892bcae24ea1606c |
||||
Subproject commit c3077fcdf2e20efb95dd27a53766b78533ab7bc4 |
||||
Loading…
Reference in new issue