Merge pull request #18 from curusarn/arrow_widgets

Implement arrow widgets, add reshctl
pull/30/head
Šimon Let 6 years ago committed by GitHub
commit 414a65ad36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitmodules
  2. 34
      Makefile
  3. 2
      VERSION
  4. 180
      cmd/collect/main.go
  5. 48
      cmd/control/cmd/completion.go
  6. 61
      cmd/control/cmd/debug.go
  7. 24
      cmd/control/cmd/disable.go
  8. 24
      cmd/control/cmd/enable.go
  9. 38
      cmd/control/cmd/root.go
  10. 17
      cmd/control/main.go
  11. 17
      cmd/control/status/status.go
  12. 74
      cmd/daemon/main.go
  13. 48
      cmd/daemon/recall.go
  14. 47
      cmd/daemon/record.go
  15. 48
      cmd/daemon/run-server.go
  16. 38
      cmd/daemon/session-init.go
  17. 7
      cmd/event/main.go
  18. 149
      cmd/postcollect/main.go
  19. 2
      cmd/sanitize/main.go
  20. 186
      cmd/session-init/main.go
  21. 2
      conf/config.toml
  22. 2
      go.mod
  23. 32
      go.sum
  24. 2
      pkg/cfg/cfg.go
  25. 118
      pkg/collect/collect.go
  26. 2
      pkg/histanal/histload.go
  27. 137
      pkg/histfile/histfile.go
  28. 102
      pkg/records/records.go
  29. 7
      pkg/sess/sess.go
  30. 201
      pkg/sesshist/sesshist.go
  31. 64
      pkg/sesswatch/sesswatch.go
  32. 161
      scripts/hooks.sh
  33. 70
      scripts/reshctl.sh
  34. 172
      scripts/shellrc.sh
  35. 4
      scripts/test.sh
  36. 136
      scripts/util.sh
  37. 93
      scripts/widgets.sh
  38. 1
      submodules/bash-zsh-compat-widgets

3
.gitmodules vendored

@ -1,3 +1,6 @@
[submodule "submodules/bash-preexec"]
path = submodules/bash-preexec
url = https://github.com/rcaloras/bash-preexec.git
[submodule "submodules/bash-zsh-compat-widgets"]
path = submodules/bash-zsh-compat-widgets
url = git@github.com:curusarn/bash-zsh-compat-widgets.git

@ -41,7 +41,7 @@ sanitize:
#
#
build: test_go submodules bin/resh-collect bin/resh-daemon bin/resh-evaluate bin/resh-sanitize
build: submodules bin/resh-session-init bin/resh-collect bin/resh-postcollect bin/resh-daemon bin/resh-evaluate bin/resh-sanitize bin/resh-control
test_go:
# Running tests
@ -50,7 +50,7 @@ test_go:
go test $$dir/*.go ; \
done
test:
test: test_go
scripts/test.sh
rebuild:
@ -60,15 +60,24 @@ rebuild:
clean:
rm resh-*
install: build submodules/bash-preexec/bash-preexec.sh scripts/shellrc.sh conf/config.toml scripts/uuid.sh | $(HOME)/.resh $(HOME)/.resh/bin $(HOME)/.config
install: build submodules/bash-preexec/bash-preexec.sh scripts/shellrc.sh conf/config.toml scripts/uuid.sh \
| $(HOME)/.resh $(HOME)/.resh/bin $(HOME)/.config $(HOME)/.resh/bash_completion.d $(HOME)/.resh/zsh_completion.d
# Copying files to resh directory ...
cp -f submodules/bash-preexec/bash-preexec.sh ~/.bash-preexec.sh
cp -f submodules/bash-zsh-compat-widgets/bindfunc.sh ~/.resh/bindfunc.sh
cp -f conf/config.toml ~/.config/resh.toml
cp -f scripts/shellrc.sh ~/.resh/shellrc
cp -f scripts/reshctl.sh scripts/widgets.sh scripts/hooks.sh scripts/util.sh ~/.resh/
bin/resh-control completion bash > ~/.resh/bash_completion.d/_reshctl
bin/resh-control completion zsh > ~/.resh/zsh_completion.d/_reshctl
cp -f scripts/uuid.sh ~/.resh/bin/resh-uuid
cp -f bin/* ~/.resh/bin/
cp -f scripts/resh-evaluate-plot.py ~/.resh/bin/
cp -fr data/sanitizer ~/.resh/
cp -fr data/sanitizer ~/.resh/sanitizer_data
# backward compatibility: We have a new location for resh history file
[ ! -f ~/.resh/history.json ] || mv ~/.resh/history.json ~/.resh_history.json
# Adding resh shellrc to .bashrc ...
@ -81,8 +90,14 @@ install: build submodules/bash-preexec/bash-preexec.sh scripts/shellrc.sh conf/c
grep '[ -f ~/.resh/shellrc ] && source ~/.resh/shellrc' ~/.zshrc ||\
echo '[ -f ~/.resh/shellrc ] && source ~/.resh/shellrc' >> ~/.zshrc
# Restarting resh daemon ...
[ ! -f ~/.resh/resh.pid ] || kill -SIGTERM $$(cat ~/.resh/resh.pid)
-[ ! -f ~/.resh/resh.pid ] || kill -SIGTERM $$(cat ~/.resh/resh.pid)
nohup resh-daemon &>/dev/null & disown
# Reloading rc files
. ~/.resh/shellrc
# Generating resh-uuid
[ -e "$(HOME)/.resh/resh-uuid" ] \
|| cat /proc/sys/kernel/random/uuid > "$(HOME)/.resh/resh-uuid" 2>/dev/null \
|| ./uuid.sh > "$(HOME)/.resh/resh-uuid" 2>/dev/null
# Final touch
touch ~/.resh_history.json
#
@ -116,10 +131,12 @@ uninstall:
# Uninstalling ...
-rm -rf ~/.resh/
bin/resh-%: cmd/%/main.go pkg/*/*.go VERSION
bin/resh-control: cmd/control/cmd/*.go
bin/resh-%: cmd/%/*.go pkg/*/*.go VERSION
go build ${GOFLAGS} -o $@ cmd/$*/*.go
$(HOME)/.resh $(HOME)/.resh/bin $(HOME)/.config:
$(HOME)/.resh $(HOME)/.resh/bin $(HOME)/.config $(HOME)/.resh/bash_completion.d $(HOME)/.resh/zsh_completion.d:
# Creating dirs ...
mkdir -p $@
@ -129,7 +146,8 @@ $(HOME)/.resh/resh-uuid:
.PHONY: submodules build install rebuild uninstall clean autoinstall
submodules: | submodules/bash-preexec/bash-preexec.sh
submodules: | submodules/bash-preexec/bash-preexec.sh submodules/bash-zsh-compat-widgets/bindfunc.sh
@# sets submodule.recurse to true if unset
@# sets status.submoduleSummary to true if unset
@git config --get submodule.recurse >/dev/null || git config --global submodule.recurse true

@ -1 +1 @@
1.1.3
1.2.1

@ -1,24 +1,20 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/BurntSushi/toml"
"github.com/curusarn/resh/pkg/cfg"
"github.com/curusarn/resh/pkg/collect"
"github.com/curusarn/resh/pkg/records"
// "os/exec"
"os/user"
"path/filepath"
"strconv"
"strings"
)
// Version from git set during build
@ -39,18 +35,29 @@ func main() {
if _, err := toml.DecodeFile(configPath, &config); err != nil {
log.Fatal("Error reading config:", err)
}
// recall command
recall := flag.Bool("recall", false, "Recall command on position --histno")
recallHistno := flag.Int("histno", 0, "Recall command on position --histno")
recallPrefix := flag.String("prefix-search", "", "Recall command based on prefix --prefix-search")
// version
showVersion := flag.Bool("version", false, "Show version and exit")
showRevision := flag.Bool("revision", false, "Show git revision and exit")
requireVersion := flag.String("requireVersion", "", "abort if version doesn't match")
requireRevision := flag.String("requireRevision", "", "abort if revision doesn't match")
// core
cmdLine := flag.String("cmdLine", "", "command line")
exitCode := flag.Int("exitCode", -1, "exit code")
shell := flag.String("shell", "", "actual shell")
uname := flag.String("uname", "", "uname")
sessionID := flag.String("sessionId", "", "resh generated session id")
// recall metadata
recallActions := flag.String("recall-actions", "", "recall actions that took place before executing the command")
recallStrategy := flag.String("recall-strategy", "", "recall strategy used during recall actions")
// posix variables
cols := flag.String("cols", "-1", "$COLUMNS")
lines := flag.String("lines", "-1", "$LINES")
@ -60,7 +67,6 @@ func main() {
login := flag.String("login", "", "$LOGIN")
// path := flag.String("path", "", "$PATH")
pwd := flag.String("pwd", "", "$PWD - present working directory")
pwdAfter := flag.String("pwdAfter", "", "$PWD after command")
shellEnv := flag.String("shellEnv", "", "$SHELL")
term := flag.String("term", "", "$TERM")
@ -81,7 +87,6 @@ func main() {
// before after
timezoneBefore := flag.String("timezoneBefore", "", "")
timezoneAfter := flag.String("timezoneAfter", "", "")
osReleaseID := flag.String("osReleaseId", "", "/etc/os-release ID")
osReleaseVersionID := flag.String("osReleaseVersionId", "",
@ -92,7 +97,6 @@ func main() {
"/etc/os-release ID")
rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME")
rta := flag.String("realtimeAfter", "-1", "after $EPOCHREALTIME")
rtsess := flag.String("realtimeSession", "-1",
"on session start $EPOCHREALTIME")
rtsessboot := flag.String("realtimeSessSinceBoot", "-1",
@ -121,10 +125,11 @@ func main() {
")")
os.Exit(3)
}
realtimeAfter, err := strconv.ParseFloat(*rta, 64)
if err != nil {
log.Fatal("Flag Parsing error (rta):", err)
if *recallPrefix != "" && *recall == false {
log.Println("Option '--prefix-search' only works with '--recall' option - exiting!")
os.Exit(4)
}
realtimeBefore, err := strconv.ParseFloat(*rtb, 64)
if err != nil {
log.Fatal("Flag Parsing error (rtb):", err)
@ -137,40 +142,32 @@ func main() {
if err != nil {
log.Fatal("Flag Parsing error (rt sess boot):", err)
}
realtimeDuration := realtimeAfter - realtimeBefore
realtimeSinceSessionStart := realtimeBefore - realtimeSessionStart
realtimeSinceBoot := realtimeSessSinceBoot + realtimeSinceSessionStart
timezoneBeforeOffset := getTimezoneOffsetInSeconds(*timezoneBefore)
timezoneAfterOffset := getTimezoneOffsetInSeconds(*timezoneAfter)
timezoneBeforeOffset := collect.GetTimezoneOffsetInSeconds(*timezoneBefore)
realtimeBeforeLocal := realtimeBefore + timezoneBeforeOffset
realtimeAfterLocal := realtimeAfter + timezoneAfterOffset
realPwd, err := filepath.EvalSymlinks(*pwd)
if err != nil {
log.Println("err while handling pwd realpath:", err)
realPwd = ""
}
realPwdAfter, err := filepath.EvalSymlinks(*pwdAfter)
if err != nil {
log.Println("err while handling pwdAfter realpath:", err)
realPwd = ""
}
gitDir, gitRealDir := getGitDirs(*gitCdup, *gitCdupExitCode, *pwd)
gitDir, gitRealDir := collect.GetGitDirs(*gitCdup, *gitCdupExitCode, *pwd)
if *gitRemoteExitCode != 0 {
*gitRemote = ""
}
if *osReleaseID == "" {
*osReleaseID = "linux"
}
if *osReleaseName == "" {
*osReleaseName = "Linux"
}
if *osReleasePrettyName == "" {
*osReleasePrettyName = "Linux"
}
// if *osReleaseID == "" {
// *osReleaseID = "linux"
// }
// if *osReleaseName == "" {
// *osReleaseName = "Linux"
// }
// if *osReleasePrettyName == "" {
// *osReleasePrettyName = "Linux"
// }
rec := records.Record{
// posix
@ -178,6 +175,8 @@ func main() {
Lines: *lines,
// core
BaseRecord: records.BaseRecord{
RecallHistno: *recallHistno,
CmdLine: *cmdLine,
ExitCode: *exitCode,
Shell: *shell,
@ -191,15 +190,13 @@ func main() {
Login: *login,
// Path: *path,
Pwd: *pwd,
PwdAfter: *pwdAfter,
ShellEnv: *shellEnv,
Term: *term,
// non-posix
RealPwd: realPwd,
RealPwdAfter: realPwdAfter,
Pid: *pid,
SessionPid: *sessionPid,
SessionPID: *sessionPid,
Host: *host,
Hosttype: *hosttype,
Ostype: *ostype,
@ -208,21 +205,17 @@ func main() {
// before after
TimezoneBefore: *timezoneBefore,
TimezoneAfter: *timezoneAfter,
RealtimeBefore: realtimeBefore,
RealtimeAfter: realtimeAfter,
RealtimeBeforeLocal: realtimeBeforeLocal,
RealtimeAfterLocal: realtimeAfterLocal,
RealtimeDuration: realtimeDuration,
RealtimeSinceSessionStart: realtimeSinceSessionStart,
RealtimeSinceBoot: realtimeSinceBoot,
GitDir: gitDir,
GitRealDir: gitRealDir,
GitOriginRemote: *gitRemote,
MachineID: readFileContent(machineIDPath),
MachineID: collect.ReadFileContent(machineIDPath),
OsReleaseID: *osReleaseID,
OsReleaseVersionID: *osReleaseVersionID,
@ -230,109 +223,20 @@ func main() {
OsReleaseName: *osReleaseName,
OsReleasePrettyName: *osReleasePrettyName,
ReshUUID: readFileContent(reshUUIDPath),
PartOne: true,
ReshUUID: collect.ReadFileContent(reshUUIDPath),
ReshVersion: Version,
ReshRevision: Revision,
},
}
sendRecord(rec, strconv.Itoa(config.Port))
}
func sendRecord(r records.Record, port string) {
recJSON, err := json.Marshal(r)
if err != nil {
log.Fatal("send err 1", err)
}
req, err := http.NewRequest("POST", "http://localhost:"+port+"/record",
bytes.NewBuffer(recJSON))
if err != nil {
log.Fatal("send err 2", err)
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
_, err = client.Do(req)
if err != nil {
log.Fatal("resh-daemon is not running :(")
}
}
func readFileContent(path string) string {
dat, err := ioutil.ReadFile(path)
if err != nil {
return ""
//log.Fatal("failed to open " + path)
}
return strings.TrimSuffix(string(dat), "\n")
}
func getGitDirs(cdup string, exitCode int, pwd string) (string, string) {
if exitCode != 0 {
return "", ""
}
abspath := filepath.Clean(filepath.Join(pwd, cdup))
realpath, err := filepath.EvalSymlinks(abspath)
if err != nil {
log.Println("err while handling git dir paths:", err)
return "", ""
}
return abspath, realpath
}
func getTimezoneOffsetInSeconds(zone string) float64 {
// date +%z -> "+0200"
hoursStr := zone[:3]
minsStr := zone[3:]
hours, err := strconv.Atoi(hoursStr)
if err != nil {
log.Println("err while parsing hours in timezone offset:", err)
return -1
RecallActionsRaw: *recallActions,
RecallPrefix: *recallPrefix,
RecallStrategy: *recallStrategy,
},
}
mins, err := strconv.Atoi(minsStr)
if err != nil {
log.Println("err while parsing mins in timezone offset:", err)
return -1
if *recall {
fmt.Print(collect.SendRecallRequest(rec, strconv.Itoa(config.Port)))
} else {
collect.SendRecord(rec, strconv.Itoa(config.Port), "/record")
}
secs := ((hours * 60) + mins) * 60
return float64(secs)
}
// func getGitRemote() string {
// out, err := exec.Command("git", "remote", "get-url", "origin").Output()
// if err != nil {
// if exitError, ok := err.(*exec.ExitError); ok {
// if exitError.ExitCode() == 128 {
// return ""
// }
// log.Fatal("git remote cmd failed")
// } else {
// log.Fatal("git remote cmd failed w/o exit code")
// }
// }
// return strings.TrimSuffix(string(out), "\n")
// }
//
// func getGitDir() string {
// // assume we are in pwd
// gitWorkTree := os.Getenv("GIT_WORK_TREE")
//
// if gitWorkTree != "" {
// return gitWorkTree
// }
// // we should look up the git directory ourselves
// // OR leave it to resh daemon to not slow down user
// out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
// if err != nil {
// if exitError, ok := err.(*exec.ExitError); ok {
// if exitError.ExitCode() == 128 {
// return ""
// }
// log.Fatal("git rev-parse cmd failed")
// } else {
// log.Fatal("git rev-parse cmd failed w/o exit code")
// }
// }
// return strings.TrimSuffix(string(out), "\n")
// }
// }

@ -0,0 +1,48 @@
package cmd
import (
"os"
"github.com/curusarn/resh/cmd/control/status"
"github.com/spf13/cobra"
)
// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion",
Short: "Generates bash/zsh completion scripts",
Long: `To load completion run
. <(reshctl completion bash)
OR
. <(reshctl completion zsh)
`,
}
var completionBashCmd = &cobra.Command{
Use: "bash",
Short: "Generates bash completion scripts",
Long: `To load completion run
. <(reshctl completion bash)
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenBashCompletion(os.Stdout)
exitCode = status.Success
},
}
var completionZshCmd = &cobra.Command{
Use: "zsh",
Short: "Generates zsh completion scripts",
Long: `To load completion run
. <(reshctl completion zsh)
`,
Run: func(cmd *cobra.Command, args []string) {
rootCmd.GenZshCompletion(os.Stdout)
exitCode = status.Success
},
}

@ -0,0 +1,61 @@
package cmd
import (
"fmt"
"io/ioutil"
"os/user"
"path/filepath"
"github.com/curusarn/resh/cmd/control/status"
"github.com/spf13/cobra"
)
var debugCmd = &cobra.Command{
Use: "debug",
Short: "Debug utils for resh",
Long: "Reloads resh rc files. Shows logs and output from last runs of resh",
}
var debugReloadCmd = &cobra.Command{
Use: "reload",
Short: "Reload resh rc files",
Long: "Reload resh rc files",
Run: func(cmd *cobra.Command, args []string) {
exitCode = status.ReloadRcFiles
},
}
var debugOutputCmd = &cobra.Command{
Use: "output",
Short: "Shows output from last runs of resh",
Long: "Shows output from last runs of resh",
Run: func(cmd *cobra.Command, args []string) {
files := []string{
"daemon_last_run_out.txt",
"collect_last_run_out.txt",
"postcollect_last_run_out.txt",
"session_init_last_run_out.txt",
"arrow_up_last_run_out.txt",
"arrow_down_last_run_out.txt",
}
usr, _ := user.Current()
dir := usr.HomeDir
reshdir := filepath.Join(dir, ".resh")
for _, fpath := range files {
fpath := filepath.Join(reshdir, fpath)
debugReadFile(fpath)
}
exitCode = status.Success
},
}
func debugReadFile(path string) {
fmt.Println("============================================================")
fmt.Println(" filepath:", path)
fmt.Println("============================================================")
dat, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println("ERROR while reading file:", err)
}
fmt.Println(string(dat))
}

@ -0,0 +1,24 @@
package cmd
import (
"github.com/curusarn/resh/cmd/control/status"
"github.com/spf13/cobra"
)
var disableCmd = &cobra.Command{
Use: "disable",
Short: "disable RESH features",
Long: `Disables RESH bindings for arrows and C-R.`,
Run: func(cmd *cobra.Command, args []string) {
exitCode = status.DisableAll
},
}
// var disableRecallingCmd = &cobra.Command{
// Use: "keybind",
// Short: "Disables RESH bindings for arrows and C-R.",
// Long: `Disables RESH bindings for arrows and C-R.`,
// Run: func(cmd *cobra.Command, args []string) {
// exitCode = status.DisableAll
// },
// }

@ -0,0 +1,24 @@
package cmd
import (
"github.com/curusarn/resh/cmd/control/status"
"github.com/spf13/cobra"
)
var enableCmd = &cobra.Command{
Use: "enable",
Short: "enable RESH features",
Long: `Enables RESH bindings for arrows and C-R.`,
Run: func(cmd *cobra.Command, args []string) {
exitCode = status.EnableAll
},
}
// 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
// },
// }

@ -0,0 +1,38 @@
package cmd
import (
"fmt"
"github.com/curusarn/resh/cmd/control/status"
"github.com/spf13/cobra"
)
var exitCode status.Code
var rootCmd = &cobra.Command{
Use: "reshctl",
Short: "Reshctl (RESH control) - enables you to enable/disable features and more.",
Long: `Enables you to enable/disable RESH bindings for arrows and C-R.`,
}
// Execute reshctl
func Execute() status.Code {
rootCmd.AddCommand(disableCmd)
// disableCmd.AddCommand(disableRecallingCmd)
rootCmd.AddCommand(enableCmd)
// enableCmd.AddCommand(enableRecallingCmd)
rootCmd.AddCommand(completionCmd)
completionCmd.AddCommand(completionBashCmd)
completionCmd.AddCommand(completionZshCmd)
rootCmd.AddCommand(debugCmd)
debugCmd.AddCommand(debugReloadCmd)
debugCmd.AddCommand(debugOutputCmd)
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
return status.Fail
}
return exitCode
}

@ -0,0 +1,17 @@
package main
import (
"os"
"github.com/curusarn/resh/cmd/control/cmd"
)
// Version from git set during build
var Version string
// Revision from git set during build
var Revision string
func main() {
os.Exit(int(cmd.Execute()))
}

@ -0,0 +1,17 @@
package status
// Code - exit code of the resh-control command
type Code int
const (
// Success exit code
Success Code = 0
// Fail exit code
Fail = 1
// EnableAll exit code - tells reshctl() wrapper to enable_all
EnableAll = 100
// DisableAll exit code - tells reshctl() wrapper to disable_all
DisableAll = 110
// ReloadRcFiles exit code - tells reshctl() wrapper to reload shellrc resh file
ReloadRcFiles = 200
)

@ -1,7 +1,7 @@
package main
import (
"encoding/json"
//"flag"
"io/ioutil"
"log"
@ -15,7 +15,6 @@ import (
"github.com/BurntSushi/toml"
"github.com/curusarn/resh/pkg/cfg"
"github.com/curusarn/resh/pkg/records"
)
// Version from git set during build
@ -32,7 +31,7 @@ func main() {
dir := usr.HomeDir
pidfilePath := filepath.Join(dir, ".resh/resh.pid")
configPath := filepath.Join(dir, ".config/resh.toml")
outputPath := filepath.Join(dir, ".resh_history.json")
historyPath := filepath.Join(dir, ".resh_history.json")
logPath := filepath.Join(dir, ".resh/daemon.log")
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
@ -70,7 +69,7 @@ func main() {
if err != nil {
log.Fatal("Could not create pidfile", err)
}
runServer(config.Port, outputPath)
runServer(config, historyPath)
err = os.Remove(pidfilePath)
if err != nil {
log.Println("Could not delete pidfile", err)
@ -83,52 +82,6 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Status OK")
}
type recordHandler struct {
OutputPath string
}
func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK\n"))
record := records.Record{}
jsn, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println("Error reading the body", err)
return
}
err = json.Unmarshal(jsn, &record)
if err != nil {
log.Println("Decoding error: ", err)
log.Println("Payload: ", jsn)
return
}
f, err := os.OpenFile(h.OutputPath,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println("Could not open file", err)
return
}
defer f.Close()
_, err = f.Write(append(jsn, []byte("\n")...))
if err != nil {
log.Printf("Error while writing: %v, %s\n", record, err)
return
}
log.Println("Received: ", record.CmdLine)
// fmt.Println("cmd:", r.CmdLine)
// fmt.Println("pwd:", r.Pwd)
// fmt.Println("git:", r.GitWorkTree)
// fmt.Println("exit_code:", r.ExitCode)
}
func runServer(port int, outputPath string) {
http.HandleFunc("/status", statusHandler)
http.Handle("/record", &recordHandler{OutputPath: outputPath})
http.ListenAndServe(":"+strconv.Itoa(port), nil)
}
func killDaemon(pidfile string) error {
dat, err := ioutil.ReadFile(pidfile)
if err != nil {
@ -158,25 +111,4 @@ func isDaemonRunning(port int) (bool, error) {
}
defer resp.Body.Close()
return true, nil
//body, err := ioutil.ReadAll(resp.Body)
// dat, err := ioutil.ReadFile(pidfile)
// if err != nil {
// log.Println("Reading pid file failed", err)
// return false, err
// }
// log.Print(string(dat))
// pid, err := strconv.ParseInt(string(dat), 10, 64)
// if err != nil {
// log.Fatal(err)
// }
// process, err := os.FindProcess(int(pid))
// if err != nil {
// log.Printf("Failed to find process: %s\n", err)
// return false, err
// } else {
// err := process.Signal(syscall.Signal(0))
// log.Printf("process.Signal on pid %d returned: %v\n", pid, err)
// }
// return true, nil
}

@ -0,0 +1,48 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"github.com/curusarn/resh/pkg/collect"
"github.com/curusarn/resh/pkg/records"
"github.com/curusarn/resh/pkg/sesshist"
)
type recallHandler struct {
sesshistDispatch *sesshist.Dispatch
}
func (h *recallHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
jsn, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println("Error reading the body", err)
return
}
rec := records.Record{}
err = json.Unmarshal(jsn, &rec)
if err != nil {
log.Println("Decoding error:", err)
log.Println("Payload:", jsn)
return
}
cmd, err := h.sesshistDispatch.Recall(rec.SessionID, rec.RecallHistno, rec.RecallPrefix)
if err != nil {
log.Println("/recall - sess id:", rec.SessionID, " - histno:", rec.RecallHistno, " -> ERROR")
log.Println("Recall error:", err)
return
}
resp := collect.SingleResponse{cmd}
jsn, err = json.Marshal(&resp)
if err != nil {
log.Println("Encoding error:", err)
log.Println("Response:", resp)
return
}
log.Println(string(jsn))
w.Write(jsn)
log.Println("/recall - sess id:", rec.SessionID, " - histno:", rec.RecallHistno, " -> ", cmd)
}

@ -0,0 +1,47 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"github.com/curusarn/resh/pkg/records"
)
type recordHandler struct {
subscribers []chan records.Record
}
func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK\n"))
jsn, err := ioutil.ReadAll(r.Body)
// run rest of the handler as goroutine to prevent any hangups
go func() {
if err != nil {
log.Println("Error reading the body", err)
return
}
record := records.Record{}
err = json.Unmarshal(jsn, &record)
if err != nil {
log.Println("Decoding error: ", err)
log.Println("Payload: ", jsn)
return
}
for _, sub := range h.subscribers {
sub <- record
}
part := "2"
if record.PartOne {
part = "1"
}
log.Println("/record - ", record.CmdLine, " - part", part)
}()
// fmt.Println("cmd:", r.CmdLine)
// fmt.Println("pwd:", r.Pwd)
// fmt.Println("git:", r.GitWorkTree)
// fmt.Println("exit_code:", r.ExitCode)
}

@ -0,0 +1,48 @@
package main
import (
"net/http"
"strconv"
"github.com/curusarn/resh/pkg/cfg"
"github.com/curusarn/resh/pkg/histfile"
"github.com/curusarn/resh/pkg/records"
"github.com/curusarn/resh/pkg/sesshist"
"github.com/curusarn/resh/pkg/sesswatch"
)
func runServer(config cfg.Config, historyPath string) {
var recordSubscribers []chan records.Record
var sessionInitSubscribers []chan records.Record
var sessionDropSubscribers []chan string
// sessshist
sesshistSessionsToInit := make(chan records.Record)
sessionInitSubscribers = append(sessionInitSubscribers, sesshistSessionsToInit)
sesshistSessionsToDrop := make(chan string)
sessionDropSubscribers = append(sessionDropSubscribers, sesshistSessionsToDrop)
sesshistRecords := make(chan records.Record)
recordSubscribers = append(recordSubscribers, sesshistRecords)
// histfile
histfileRecords := make(chan records.Record)
recordSubscribers = append(recordSubscribers, histfileRecords)
histfileSessionsToDrop := make(chan string)
sessionDropSubscribers = append(sessionDropSubscribers, histfileSessionsToDrop)
histfileBox := histfile.New(histfileRecords, historyPath, 10000, histfileSessionsToDrop)
// sesshist New
sesshistDispatch := sesshist.NewDispatch(sesshistSessionsToInit, sesshistSessionsToDrop, sesshistRecords, histfileBox, config.SesshistInitHistorySize)
// sesswatch
sesswatchSessionsToWatch := make(chan records.Record)
sessionInitSubscribers = append(sessionInitSubscribers, sesswatchSessionsToWatch)
sesswatch.Go(sesswatchSessionsToWatch, sessionDropSubscribers, config.SesswatchPeriodSeconds)
// handlers
http.HandleFunc("/status", statusHandler)
http.Handle("/record", &recordHandler{subscribers: recordSubscribers})
http.Handle("/session_init", &sessionInitHandler{subscribers: sessionInitSubscribers})
http.Handle("/recall", &recallHandler{sesshistDispatch: sesshistDispatch})
http.ListenAndServe(":"+strconv.Itoa(config.Port), nil)
}

@ -0,0 +1,38 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"github.com/curusarn/resh/pkg/records"
)
type sessionInitHandler struct {
subscribers []chan records.Record
}
func (h *sessionInitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK\n"))
jsn, err := ioutil.ReadAll(r.Body)
// run rest of the handler as goroutine to prevent any hangups
go func() {
if err != nil {
log.Println("Error reading the body", err)
return
}
record := records.Record{}
err = json.Unmarshal(jsn, &record)
if err != nil {
log.Println("Decoding error: ", err)
log.Println("Payload: ", jsn)
return
}
for _, sub := range h.subscribers {
sub <- record
}
log.Println("/session_init - id:", record.SessionID, " - pid:", record.SessionPID)
}()
}

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("Hell world")
}

@ -0,0 +1,149 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"github.com/BurntSushi/toml"
"github.com/curusarn/resh/pkg/cfg"
"github.com/curusarn/resh/pkg/collect"
"github.com/curusarn/resh/pkg/records"
// "os/exec"
"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")
reshUUIDPath := filepath.Join(dir, "/.resh/resh-uuid")
machineIDPath := "/etc/machine-id"
var config cfg.Config
if _, err := toml.DecodeFile(configPath, &config); err != nil {
log.Fatal("Error reading config:", err)
}
showVersion := flag.Bool("version", false, "Show version and exit")
showRevision := flag.Bool("revision", false, "Show git revision and exit")
requireVersion := flag.String("requireVersion", "", "abort if version doesn't match")
requireRevision := flag.String("requireRevision", "", "abort if revision doesn't match")
cmdLine := flag.String("cmdLine", "", "command line")
exitCode := flag.Int("exitCode", -1, "exit code")
sessionID := flag.String("sessionId", "", "resh generated session id")
shlvl := flag.Int("shlvl", -1, "$SHLVL")
// posix variables
pwdAfter := flag.String("pwdAfter", "", "$PWD after command")
// non-posix
// sessionPid := flag.Int("sessionPid", -1, "$$ at session start")
gitCdupAfter := flag.String("gitCdupAfter", "", "git rev-parse --show-cdup")
gitRemoteAfter := flag.String("gitRemoteAfter", "", "git remote get-url origin")
gitCdupExitCodeAfter := flag.Int("gitCdupExitCodeAfter", -1, "... $?")
gitRemoteExitCodeAfter := flag.Int("gitRemoteExitCodeAfter", -1, "... $?")
// before after
timezoneAfter := flag.String("timezoneAfter", "", "")
rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME")
rta := flag.String("realtimeAfter", "-1", "after $EPOCHREALTIME")
flag.Parse()
if *showVersion == true {
fmt.Println(Version)
os.Exit(0)
}
if *showRevision == true {
fmt.Println(Revision)
os.Exit(0)
}
if *requireVersion != "" && *requireVersion != Version {
fmt.Println("Please restart/reload this terminal session " +
"(resh version: " + Version +
"; resh version of this terminal session: " + *requireVersion +
")")
os.Exit(3)
}
if *requireRevision != "" && *requireRevision != Revision {
fmt.Println("Please restart/reload this terminal session " +
"(resh revision: " + Revision +
"; resh revision of this terminal session: " + *requireRevision +
")")
os.Exit(3)
}
realtimeAfter, err := strconv.ParseFloat(*rta, 64)
if err != nil {
log.Fatal("Flag Parsing error (rta):", err)
}
realtimeBefore, err := strconv.ParseFloat(*rtb, 64)
if err != nil {
log.Fatal("Flag Parsing error (rtb):", err)
}
realtimeDuration := realtimeAfter - realtimeBefore
timezoneAfterOffset := collect.GetTimezoneOffsetInSeconds(*timezoneAfter)
realtimeAfterLocal := realtimeAfter + timezoneAfterOffset
realPwdAfter, err := filepath.EvalSymlinks(*pwdAfter)
if err != nil {
log.Println("err while handling pwdAfter realpath:", err)
realPwdAfter = ""
}
gitDirAfter, gitRealDirAfter := collect.GetGitDirs(*gitCdupAfter, *gitCdupExitCodeAfter, *pwdAfter)
if *gitRemoteExitCodeAfter != 0 {
*gitRemoteAfter = ""
}
rec := records.Record{
// core
BaseRecord: records.BaseRecord{
CmdLine: *cmdLine,
ExitCode: *exitCode,
SessionID: *sessionID,
Shlvl: *shlvl,
PwdAfter: *pwdAfter,
// non-posix
RealPwdAfter: realPwdAfter,
// before after
TimezoneAfter: *timezoneAfter,
RealtimeBefore: realtimeBefore,
RealtimeAfter: realtimeAfter,
RealtimeAfterLocal: realtimeAfterLocal,
RealtimeDuration: realtimeDuration,
GitDirAfter: gitDirAfter,
GitRealDirAfter: gitRealDirAfter,
GitOriginRemoteAfter: *gitRemoteAfter,
MachineID: collect.ReadFileContent(machineIDPath),
PartOne: false,
ReshUUID: collect.ReadFileContent(reshUUIDPath),
ReshVersion: Version,
ReshRevision: Revision,
},
}
collect.SendRecord(rec, strconv.Itoa(config.Port), "/record")
}

@ -89,7 +89,7 @@ func main() {
log.Println("Line:", line)
log.Fatal("Decoding error:", err)
}
record = records.ConvertRecord(&fallbackRecord)
record = records.Convert(&fallbackRecord)
}
err = sanitizer.sanitizeRecord(&record)
if err != nil {

@ -0,0 +1,186 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"github.com/BurntSushi/toml"
"github.com/curusarn/resh/pkg/cfg"
"github.com/curusarn/resh/pkg/collect"
"github.com/curusarn/resh/pkg/records"
"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")
reshUUIDPath := filepath.Join(dir, "/.resh/resh-uuid")
machineIDPath := "/etc/machine-id"
var config cfg.Config
if _, err := toml.DecodeFile(configPath, &config); err != nil {
log.Fatal("Error reading config:", err)
}
showVersion := flag.Bool("version", false, "Show version and exit")
showRevision := flag.Bool("revision", false, "Show git revision and exit")
requireVersion := flag.String("requireVersion", "", "abort if version doesn't match")
requireRevision := flag.String("requireRevision", "", "abort if revision doesn't match")
shell := flag.String("shell", "", "actual shell")
uname := flag.String("uname", "", "uname")
sessionID := flag.String("sessionId", "", "resh generated session id")
// posix variables
cols := flag.String("cols", "-1", "$COLUMNS")
lines := flag.String("lines", "-1", "$LINES")
home := flag.String("home", "", "$HOME")
lang := flag.String("lang", "", "$LANG")
lcAll := flag.String("lcAll", "", "$LC_ALL")
login := flag.String("login", "", "$LOGIN")
shellEnv := flag.String("shellEnv", "", "$SHELL")
term := flag.String("term", "", "$TERM")
// non-posix
pid := flag.Int("pid", -1, "$$")
sessionPid := flag.Int("sessionPid", -1, "$$ at session start")
shlvl := flag.Int("shlvl", -1, "$SHLVL")
host := flag.String("host", "", "$HOSTNAME")
hosttype := flag.String("hosttype", "", "$HOSTTYPE")
ostype := flag.String("ostype", "", "$OSTYPE")
machtype := flag.String("machtype", "", "$MACHTYPE")
// before after
timezoneBefore := flag.String("timezoneBefore", "", "")
osReleaseID := flag.String("osReleaseId", "", "/etc/os-release ID")
osReleaseVersionID := flag.String("osReleaseVersionId", "",
"/etc/os-release ID")
osReleaseIDLike := flag.String("osReleaseIdLike", "", "/etc/os-release ID")
osReleaseName := flag.String("osReleaseName", "", "/etc/os-release ID")
osReleasePrettyName := flag.String("osReleasePrettyName", "",
"/etc/os-release ID")
rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME")
rtsess := flag.String("realtimeSession", "-1",
"on session start $EPOCHREALTIME")
rtsessboot := flag.String("realtimeSessSinceBoot", "-1",
"on session start $EPOCHREALTIME")
flag.Parse()
if *showVersion == true {
fmt.Println(Version)
os.Exit(0)
}
if *showRevision == true {
fmt.Println(Revision)
os.Exit(0)
}
if *requireVersion != "" && *requireVersion != Version {
fmt.Println("Please restart/reload this terminal session " +
"(resh version: " + Version +
"; resh version of this terminal session: " + *requireVersion +
")")
os.Exit(3)
}
if *requireRevision != "" && *requireRevision != Revision {
fmt.Println("Please restart/reload this terminal session " +
"(resh revision: " + Revision +
"; resh revision of this terminal session: " + *requireRevision +
")")
os.Exit(3)
}
realtimeBefore, err := strconv.ParseFloat(*rtb, 64)
if err != nil {
log.Fatal("Flag Parsing error (rtb):", err)
}
realtimeSessionStart, err := strconv.ParseFloat(*rtsess, 64)
if err != nil {
log.Fatal("Flag Parsing error (rt sess):", err)
}
realtimeSessSinceBoot, err := strconv.ParseFloat(*rtsessboot, 64)
if err != nil {
log.Fatal("Flag Parsing error (rt sess boot):", err)
}
realtimeSinceSessionStart := realtimeBefore - realtimeSessionStart
realtimeSinceBoot := realtimeSessSinceBoot + realtimeSinceSessionStart
timezoneBeforeOffset := collect.GetTimezoneOffsetInSeconds(*timezoneBefore)
realtimeBeforeLocal := realtimeBefore + timezoneBeforeOffset
if *osReleaseID == "" {
*osReleaseID = "linux"
}
if *osReleaseName == "" {
*osReleaseName = "Linux"
}
if *osReleasePrettyName == "" {
*osReleasePrettyName = "Linux"
}
rec := records.Record{
// posix
Cols: *cols,
Lines: *lines,
// core
BaseRecord: records.BaseRecord{
Shell: *shell,
Uname: *uname,
SessionID: *sessionID,
// posix
Home: *home,
Lang: *lang,
LcAll: *lcAll,
Login: *login,
// Path: *path,
ShellEnv: *shellEnv,
Term: *term,
// non-posix
Pid: *pid,
SessionPID: *sessionPid,
Host: *host,
Hosttype: *hosttype,
Ostype: *ostype,
Machtype: *machtype,
Shlvl: *shlvl,
// before after
TimezoneBefore: *timezoneBefore,
RealtimeBefore: realtimeBefore,
RealtimeBeforeLocal: realtimeBeforeLocal,
RealtimeSinceSessionStart: realtimeSinceSessionStart,
RealtimeSinceBoot: realtimeSinceBoot,
MachineID: collect.ReadFileContent(machineIDPath),
OsReleaseID: *osReleaseID,
OsReleaseVersionID: *osReleaseVersionID,
OsReleaseIDLike: *osReleaseIDLike,
OsReleaseName: *osReleaseName,
OsReleasePrettyName: *osReleasePrettyName,
ReshUUID: collect.ReadFileContent(reshUUIDPath),
ReshVersion: Version,
ReshRevision: Revision,
},
}
collect.SendRecord(rec, strconv.Itoa(config.Port), "/session_init")
}

@ -1 +1,3 @@
port = 2627
sesswatchPeriodSeconds = 120
sesshistInitHistorySize = 1000

@ -8,7 +8,9 @@ require (
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629
github.com/mattn/go-shellwords v1.0.6
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b
github.com/schollz/progressbar v1.0.0
github.com/spf13/cobra v0.0.5
github.com/wcharczuk/go-chart v2.0.1+incompatible
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39 // indirect

@ -1,19 +1,51 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629 h1:1dSBUfGlorLAua2CRx0zFN7kQsTpE2DQSmr7rrTNgY8=
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629/go.mod h1:mb5nS4uRANwOJSZj8rlCWAfAcGi72GGMIXx+xGOjA7M=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7 h1:VsJjhYhufMGXICLwLYr8mFVMp8/A+YqmagMHnG/BA/4=
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7/go.mod h1:zQmHoMvvVJb7cxyt1wGT77lqUaeOFXlogOppOr4uHVo=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b h1:9+ke9YJ9KGWw5ANXK6ozjoK47uI3uNbXv4YVINBnGm8=
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/schollz/progressbar v1.0.0 h1:gbyFReLHDkZo8mxy/dLWMr+Mpb1MokGJ1FqCiqacjZM=
github.com/schollz/progressbar v1.0.0/go.mod h1:/l9I7PC3L3erOuz54ghIRKUEFcosiWfLvJv+Eq26UMs=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A=
github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa h1:rW+Lu6281ed/4XGuVIa4/YebTRNvoUJlfJ44ktEVwZk=
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa/go.mod h1:2rx5KE5FLD0HRfkkpyn8JwbVLBdhgeiOb2D2D9LLKM4=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39 h1:4dQcAORh9oYBwVSBVIkP489LUPC+f1HBkTYXgmqfR+o=
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -3,4 +3,6 @@ package cfg
// Config struct
type Config struct {
Port int
SesswatchPeriodSeconds uint
SesshistInitHistorySize int
}

@ -0,0 +1,118 @@
package collect
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/curusarn/resh/pkg/records"
)
// SingleResponse json struct
type SingleResponse struct {
CmdLine string `json:"cmdline"`
}
// SendRecallRequest to daemon
func SendRecallRequest(r records.Record, port string) string {
recJSON, err := json.Marshal(r)
if err != nil {
log.Fatal("send err 1", err)
}
req, err := http.NewRequest("POST", "http://localhost:"+port+"/recall",
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 := SingleResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
log.Fatal("unmarshal resp error: ", err)
}
log.Println(response)
return response.CmdLine
}
// SendRecord to daemon
func SendRecord(r records.Record, port, path string) {
recJSON, err := json.Marshal(r)
if err != nil {
log.Fatal("send err 1", err)
}
req, err := http.NewRequest("POST", "http://localhost:"+port+path,
bytes.NewBuffer(recJSON))
if err != nil {
log.Fatal("send err 2", err)
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
_, err = client.Do(req)
if err != nil {
log.Fatal("resh-daemon is not running :(")
}
}
// ReadFileContent and return it as a string
func ReadFileContent(path string) string {
dat, err := ioutil.ReadFile(path)
if err != nil {
return ""
//log.Fatal("failed to open " + path)
}
return strings.TrimSuffix(string(dat), "\n")
}
// GetGitDirs based on result of git "cdup" command
func GetGitDirs(cdup string, exitCode int, pwd string) (string, string) {
if exitCode != 0 {
return "", ""
}
abspath := filepath.Clean(filepath.Join(pwd, cdup))
realpath, err := filepath.EvalSymlinks(abspath)
if err != nil {
log.Println("err while handling git dir paths:", err)
return "", ""
}
return abspath, realpath
}
// GetTimezoneOffsetInSeconds based on zone returned by date command
func GetTimezoneOffsetInSeconds(zone string) float64 {
// date +%z -> "+0200"
hoursStr := zone[:3]
minsStr := zone[3:]
hours, err := strconv.Atoi(hoursStr)
if err != nil {
log.Println("err while parsing hours in timezone offset:", err)
return -1
}
mins, err := strconv.Atoi(minsStr)
if err != nil {
log.Println("err while parsing mins in timezone offset:", err)
return -1
}
secs := ((hours * 60) + mins) * 60
return float64(secs)
}

@ -162,7 +162,7 @@ func (e *HistLoad) loadHistoryRecords(fname string) []records.EnrichedRecord {
log.Println("Line:", line)
log.Fatal("Decoding error:", err)
}
record = records.ConvertRecord(&fallbackRecord)
record = records.Convert(&fallbackRecord)
}
if e.sanitizedInput == false {
if record.CmdLength != 0 {

@ -0,0 +1,137 @@
package histfile
import (
"encoding/json"
"log"
"os"
"strconv"
"sync"
"github.com/curusarn/resh/pkg/records"
)
// Histfile writes records to histfile
type Histfile struct {
sessionsMutex sync.Mutex
sessions map[string]records.Record
historyPath string
recentMutex sync.Mutex
recentRecords []records.Record
recentCmdLines []string // deduplicated
cmdLinesLastIndex map[string]int
}
// New creates new histfile and runs two gorutines on it
func New(input chan records.Record, historyPath string, initHistSize int, sessionsToDrop chan string) *Histfile {
hf := Histfile{
sessions: map[string]records.Record{},
historyPath: historyPath,
cmdLinesLastIndex: map[string]int{},
}
go hf.loadHistory(initHistSize)
go hf.writer(input)
go hf.sessionGC(sessionsToDrop)
return &hf
}
func (h *Histfile) loadHistory(initHistSize int) {
h.recentMutex.Lock()
defer h.recentMutex.Unlock()
h.recentCmdLines = records.LoadCmdLinesFromFile(h.historyPath, initHistSize)
}
// sessionGC reads sessionIDs from channel and deletes them from histfile struct
func (h *Histfile) sessionGC(sessionsToDrop chan string) {
for {
func() {
session := <-sessionsToDrop
log.Println("histfile: got session to drop", session)
h.sessionsMutex.Lock()
defer h.sessionsMutex.Unlock()
if part1, found := h.sessions[session]; found == true {
log.Println("histfile: Dropping session:", session)
delete(h.sessions, session)
go writeRecord(part1, h.historyPath)
} else {
log.Println("histfile: No hanging parts for session:", session)
}
}()
}
}
// writer reads records from channel, merges them and writes them to file
func (h *Histfile) writer(input chan records.Record) {
for {
func() {
record := <-input
h.sessionsMutex.Lock()
defer h.sessionsMutex.Unlock()
// allows nested sessions to merge records properly
mergeID := record.SessionID + "_" + strconv.Itoa(record.Shlvl)
if record.PartOne {
if _, found := h.sessions[mergeID]; found {
log.Println("histfile WARN: Got another first part of the records before merging the previous one - overwriting! " +
"(this happens in bash because bash-preexec runs when it's not supposed to)")
}
h.sessions[mergeID] = record
} else {
if part1, found := h.sessions[mergeID]; found == false {
log.Println("histfile ERROR: Got second part of records and nothing to merge it with - ignoring! (mergeID:", mergeID, ")")
} else {
delete(h.sessions, mergeID)
go h.mergeAndWriteRecord(part1, record)
}
}
}()
}
}
func (h *Histfile) mergeAndWriteRecord(part1, part2 records.Record) {
err := part1.Merge(part2)
if err != nil {
log.Println("Error while merging", err)
return
}
func() {
h.recentMutex.Lock()
defer h.recentMutex.Unlock()
h.recentRecords = append(h.recentRecords, part1)
cmdLine := part1.CmdLine
idx, found := h.cmdLinesLastIndex[cmdLine]
if found {
h.recentCmdLines = append(h.recentCmdLines[:idx], h.recentCmdLines[idx+1:]...)
}
h.cmdLinesLastIndex[cmdLine] = len(h.recentCmdLines)
h.recentCmdLines = append(h.recentCmdLines, cmdLine)
}()
writeRecord(part1, h.historyPath)
}
func writeRecord(rec records.Record, outputPath string) {
recJSON, err := json.Marshal(rec)
if err != nil {
log.Println("Marshalling error", err)
return
}
f, err := os.OpenFile(outputPath,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println("Could not open file", err)
return
}
defer f.Close()
_, err = f.Write(append(recJSON, []byte("\n")...))
if err != nil {
log.Printf("Error while writing: %v, %s\n", rec, err)
return
}
}
// GetRecentCmdLines returns recent cmdLines
func (h *Histfile) GetRecentCmdLines(limit int) []string {
return h.recentCmdLines
}

@ -1,10 +1,12 @@
package records
import (
"bufio"
"encoding/json"
"errors"
"log"
"math"
"os"
"strconv"
"strings"
@ -35,7 +37,7 @@ type BaseRecord struct {
RealPwd string `json:"realPwd"`
RealPwdAfter string `json:"realPwdAfter"`
Pid int `json:"pid"`
SessionPid int `json:"sessionPid"`
SessionPID int `json:"sessionPid"`
Host string `json:"host"`
Hosttype string `json:"hosttype"`
Ostype string `json:"ostype"`
@ -59,6 +61,9 @@ type BaseRecord struct {
GitDir string `json:"gitDir"`
GitRealDir string `json:"gitRealDir"`
GitOriginRemote string `json:"gitOriginRemote"`
GitDirAfter string `json:"gitDirAfter"`
GitRealDirAfter string `json:"gitRealDirAfter"`
GitOriginRemoteAfter string `json:"gitOriginRemoteAfter"`
MachineID string `json:"machineId"`
OsReleaseID string `json:"osReleaseId"`
@ -71,6 +76,22 @@ type BaseRecord struct {
ReshVersion string `json:"reshVersion"`
ReshRevision string `json:"reshRevision"`
// records come in two parts (collect and postcollect)
PartOne bool `json:"partOne,omitempty"` // false => part two
PartsMerged bool `json:"partsMerged"`
// special flag -> not an actual record but an session end
SessionExit bool `json:"sessionExit,omitempty"`
// recall metadata
Recalled bool `json:"recalled"`
RecallHistno int `json:"recallHistno,omitempty"`
RecallStrategy string `json:"recallStrategy,omitempty"`
RecallActionsRaw string `json:"recallActionsRaw,omitempty"`
RecallActions []string `json:"recallActions,omitempty"`
// recall command
RecallPrefix string `json:"recallPrefix,omitempty"`
// added by sanitizatizer
Sanitized bool `json:"sanitized,omitempty"`
CmdLength int `json:"cmdLength,omitempty"`
@ -108,8 +129,8 @@ type FallbackRecord struct {
Lines int `json:"lines"` // notice the int type
}
// ConvertRecord from FallbackRecord to Record
func ConvertRecord(r *FallbackRecord) Record {
// Convert from FallbackRecord to Record
func Convert(r *FallbackRecord) Record {
return Record{
BaseRecord: r.BaseRecord,
// these two lines are the only reason we are doing this
@ -151,6 +172,34 @@ func Enriched(r Record) EnrichedRecord {
// TODO: Detect and mark simple commands r.Simple
}
// Merge two records (part1 - collect + part2 - postcollect)
func (r *Record) Merge(r2 Record) error {
if r.PartOne == false || r2.PartOne {
return errors.New("Expected part1 and part2 of the same record - usage: part1.Merge(part2)")
}
if r.SessionID != r2.SessionID {
return errors.New("Records to merge are not from the same sesion - r1:" + r.SessionID + " r2:" + r2.SessionID)
}
if r.CmdLine != r2.CmdLine {
return errors.New("Records to merge are not parts of the same records - r1:" + r.CmdLine + " r2:" + r2.CmdLine)
}
// r.RealtimeBefore != r2.RealtimeBefore - can't be used because of bash-preexec runs when it's not supposed to
r.ExitCode = r2.ExitCode
r.PwdAfter = r2.PwdAfter
r.RealPwdAfter = r2.RealPwdAfter
r.GitDirAfter = r2.GitDirAfter
r.GitRealDirAfter = r2.GitRealDirAfter
r.RealtimeAfter = r2.RealtimeAfter
r.GitOriginRemoteAfter = r2.GitOriginRemoteAfter
r.TimezoneAfter = r2.TimezoneAfter
r.RealtimeAfterLocal = r2.RealtimeAfterLocal
r.RealtimeDuration = r2.RealtimeDuration
r.PartsMerged = true
r.PartOne = false
return nil
}
// Validate - returns error if the record is invalid
func (r *Record) Validate() error {
if r.CmdLine == "" {
@ -389,3 +438,50 @@ func (r *EnrichedRecord) DistanceTo(r2 EnrichedRecord, p DistParams) float64 {
return dist
}
// LoadCmdLinesFromFile loads limit cmdlines from file
func LoadCmdLinesFromFile(fname string, limit int) []string {
recs := LoadFromFile(fname, limit*3) // assume that at least 1/3 of commands is unique
var cmdLines []string
cmdLinesSet := map[string]bool{}
for i := len(recs) - 1; i >= 0; i-- {
cmdLine := recs[i].CmdLine
if cmdLinesSet[cmdLine] {
continue
}
cmdLinesSet[cmdLine] = true
cmdLines = append([]string{cmdLine}, cmdLines...)
if len(cmdLines) > limit {
break
}
}
return cmdLines
}
// LoadFromFile loads at most 'limit' records from 'fname' file
func LoadFromFile(fname string, limit int) []Record {
file, err := os.Open(fname)
if err != nil {
log.Fatal("Open() resh history file error:", err)
}
defer file.Close()
var recs []Record
scanner := bufio.NewScanner(file)
for scanner.Scan() {
record := Record{}
fallbackRecord := FallbackRecord{}
line := scanner.Text()
err = json.Unmarshal([]byte(line), &record)
if err != nil {
err = json.Unmarshal([]byte(line), &fallbackRecord)
if err != nil {
log.Println("Line:", line)
log.Fatal("Decoding error:", err)
}
record = Convert(&fallbackRecord)
}
recs = append(recs, record)
}
return recs
}

@ -0,0 +1,7 @@
package sess
// Session represents a session, used for sennding through channels when more than just ID is needed
type Session struct {
ID string
PID int
}

@ -0,0 +1,201 @@
package sesshist
import (
"errors"
"log"
"strconv"
"strings"
"sync"
"github.com/curusarn/resh/pkg/histfile"
"github.com/curusarn/resh/pkg/records"
)
// Dispatch Recall() calls to an apropriate session history (sesshist)
type Dispatch struct {
sessions map[string]*sesshist
mutex sync.RWMutex
history *histfile.Histfile
historyInitSize int
}
// NewDispatch creates a new sesshist.Dispatch and starts necessary gorutines
func NewDispatch(sessionsToInit chan records.Record, sessionsToDrop chan string,
recordsToAdd chan records.Record, history *histfile.Histfile, historyInitSize int) *Dispatch {
s := Dispatch{
sessions: map[string]*sesshist{},
history: history,
historyInitSize: historyInitSize,
}
go s.sessionInitializer(sessionsToInit)
go s.sessionDropper(sessionsToDrop)
go s.recordAdder(recordsToAdd)
return &s
}
func (s *Dispatch) sessionInitializer(sessionsToInit chan records.Record) {
for {
record := <-sessionsToInit
log.Println("sesshist: got session to init - " + record.SessionID)
s.initSession(record.SessionID)
}
}
func (s *Dispatch) sessionDropper(sessionsToDrop chan string) {
for {
sessionID := <-sessionsToDrop
log.Println("sesshist: got session to drop - " + sessionID)
s.dropSession(sessionID)
}
}
func (s *Dispatch) recordAdder(recordsToAdd chan records.Record) {
for {
record := <-recordsToAdd
if record.PartOne {
log.Println("sesshist: got record to add - " + record.CmdLine)
s.addRecentRecord(record.SessionID, record)
}
// TODO: we will need to handle part2 as well eventually
}
}
// InitSession struct
func (s *Dispatch) initSession(sessionID string) error {
log.Println("sesshist: initializing session - " + sessionID)
s.mutex.RLock()
_, found := s.sessions[sessionID]
s.mutex.RUnlock()
if found == true {
return errors.New("sesshist ERROR: Can't INIT already existing session " + sessionID)
}
log.Println("sesshist: loading history to populate session - " + sessionID)
historyCmdLines := s.history.GetRecentCmdLines(s.historyInitSize)
s.mutex.Lock()
defer s.mutex.Unlock()
// init sesshist and populate it with history loaded from file
s.sessions[sessionID] = &sesshist{
recentCmdLines: historyCmdLines,
cmdLinesLastIndex: map[string]int{},
}
log.Println("sesshist: session init done - " + sessionID)
return nil
}
// DropSession struct
func (s *Dispatch) dropSession(sessionID string) error {
s.mutex.RLock()
_, found := s.sessions[sessionID]
s.mutex.RUnlock()
if found == false {
return errors.New("sesshist ERROR: Can't DROP not existing session " + sessionID)
}
s.mutex.Lock()
defer s.mutex.Unlock()
delete(s.sessions, sessionID)
return nil
}
// AddRecent record to session
func (s *Dispatch) addRecentRecord(sessionID string, record records.Record) error {
s.mutex.RLock()
session, found := s.sessions[sessionID]
s.mutex.RUnlock()
if found == false {
log.Println("sesshist ERROR: addRecontRecord(): No session history for SessionID " + sessionID + " - creating session history.")
s.initSession(sessionID)
return s.addRecentRecord(sessionID, record)
}
session.mutex.Lock()
defer session.mutex.Unlock()
session.recentRecords = append(session.recentRecords, record)
// remove previous occurance of record
cmdLine := record.CmdLine
idx, found := session.cmdLinesLastIndex[cmdLine]
if found {
session.recentCmdLines = append(session.recentCmdLines[:idx], session.recentCmdLines[idx+1:]...)
}
session.cmdLinesLastIndex[cmdLine] = len(session.recentCmdLines)
// append new record
session.recentCmdLines = append(session.recentCmdLines, cmdLine)
log.Println("sesshist: record:", record.CmdLine, "; added to session:", sessionID,
"; session len:", len(session.recentCmdLines), "; session len w/ dups:", len(session.recentRecords))
return nil
}
// Recall command from recent session history
func (s *Dispatch) Recall(sessionID string, histno int, prefix string) (string, error) {
s.mutex.RLock()
session, found := s.sessions[sessionID]
s.mutex.RUnlock()
if found == false {
// go s.initSession(sessionID)
return "", errors.New("sesshist ERROR: No session history for SessionID " + sessionID + " - should we create one?")
}
if prefix == "" {
session.mutex.Lock()
defer session.mutex.Unlock()
return session.getRecordByHistno(histno)
}
session.mutex.Lock()
defer session.mutex.Unlock()
return session.searchRecordByPrefix(prefix, histno)
}
type sesshist struct {
recentRecords []records.Record
recentCmdLines []string // deduplicated
cmdLinesLastIndex map[string]int
mutex sync.Mutex
}
func (s *sesshist) getRecordByHistno(histno int) (string, error) {
// addRecords() appends records to the end of the slice
// -> this func handles the indexing
if histno == 0 {
return "", errors.New("sesshist ERROR: 'histno == 0' is not a record from history")
}
if histno < 0 {
return "", errors.New("sesshist ERROR: 'histno < 0' is a command from future (not supperted yet)")
}
index := len(s.recentCmdLines) - histno
if index < 0 {
return "", errors.New("sesshist ERROR: 'histno > number of commands in the session' (" + strconv.Itoa(len(s.recentCmdLines)) + ")")
}
return s.recentCmdLines[index], nil
}
func (s *sesshist) searchRecordByPrefix(prefix string, histno int) (string, error) {
if histno == 0 {
return "", errors.New("sesshist ERROR: 'histno == 0' is not a record from history")
}
if histno < 0 {
return "", errors.New("sesshist ERROR: 'histno < 0' is a command from future (not supperted yet)")
}
index := len(s.recentCmdLines) - histno
if index < 0 {
return "", errors.New("sesshist ERROR: 'histno > number of commands in the session' (" + strconv.Itoa(len(s.recentCmdLines)) + ")")
}
cmdLines := []string{}
for i := len(s.recentCmdLines) - 1; i >= 0; i-- {
if strings.HasPrefix(s.recentCmdLines[i], prefix) {
cmdLines = append(cmdLines, s.recentCmdLines[i])
if len(cmdLines) >= histno {
break
}
}
}
if len(cmdLines) < histno {
return "", errors.New("sesshist ERROR: 'histno > number of commands matching with given prefix' (" + strconv.Itoa(len(cmdLines)) + ")")
}
return cmdLines[histno-1], nil
}

@ -0,0 +1,64 @@
package sesswatch
import (
"log"
"sync"
"time"
"github.com/curusarn/resh/pkg/records"
"github.com/mitchellh/go-ps"
)
type sesswatch struct {
sessionsToDrop []chan string
sleepSeconds uint
watchedSessions map[string]bool
mutex sync.Mutex
}
// Go runs the session watcher - watches sessions and sends
func Go(sessionsToWatch chan records.Record, sessionsToDrop []chan string, sleepSeconds uint) {
sw := sesswatch{sessionsToDrop: sessionsToDrop, sleepSeconds: sleepSeconds, watchedSessions: map[string]bool{}}
go sw.waiter(sessionsToWatch)
}
func (s *sesswatch) waiter(sessionsToWatch chan records.Record) {
for {
func() {
record := <-sessionsToWatch
id := record.SessionID
pid := record.SessionPID
s.mutex.Lock()
defer s.mutex.Unlock()
if s.watchedSessions[id] == false {
log.Println("sesswatch: start watching NEW session ~ pid:", id, "~", pid)
s.watchedSessions[id] = true
go s.watcher(id, pid)
}
}()
}
}
func (s *sesswatch) watcher(sessionID string, sessionPID int) {
for {
time.Sleep(time.Duration(s.sleepSeconds) * time.Second)
proc, err := ps.FindProcess(sessionPID)
if err != nil {
log.Println("sesswatch ERROR: error while finding process:", sessionPID)
} else if proc == nil {
log.Println("sesswatch: Dropping session ~ pid:", sessionID, "~", sessionPID)
func() {
s.mutex.Lock()
defer s.mutex.Unlock()
s.watchedSessions[sessionID] = false
}()
for _, ch := range s.sessionsToDrop {
log.Println("sesswatch: sending 'drop session' message ...")
ch <- sessionID
log.Println("sesswatch: sending 'drop session' message DONE")
}
break
}
}
}

@ -0,0 +1,161 @@
__resh_reset_variables() {
__RESH_HISTNO=0
__RESH_HISTNO_ZERO_LINE=""
__RESH_HIST_PREV_LINE=""
__RESH_HIST_RECALL_ACTIONS=""
__RESH_HIST_NO_PREFIX_MODE=0
__RESH_HIST_RECALL_STRATEGY=""
}
__resh_preexec() {
# core
__RESH_COLLECT=1
__RESH_CMDLINE="$1" # not local to preserve it for postcollect (useful as sanity check)
__resh_collect --cmdLine "$__RESH_CMDLINE" \
--recall-actions "$__RESH_HIST_RECALL_ACTIONS" \
--recall-strategy "$__RESH_HIST_RECALL_STRATEGY" \
&>~/.resh/collect_last_run_out.txt || echo "resh-collect ERROR: $(head -n 1 ~/.resh/collect_last_run_out.txt)"
}
# used for collect and collect --recall
__resh_collect() {
# posix
local __RESH_COLS="$COLUMNS"
local __RESH_LANG="$LANG"
local __RESH_LC_ALL="$LC_ALL"
# other LC ?
local __RESH_LINES="$LINES"
# __RESH_PATH="$PATH"
local __RESH_PWD="$PWD"
# non-posix
local __RESH_SHLVL="$SHLVL"
local __RESH_GIT_CDUP; __RESH_GIT_CDUP="$(git rev-parse --show-cdup 2>/dev/null)"
local __RESH_GIT_CDUP_EXIT_CODE=$?
local __RESH_GIT_REMOTE; __RESH_GIT_REMOTE="$(git remote get-url origin 2>/dev/null)"
local __RESH_GIT_REMOTE_EXIT_CODE=$?
#__RESH_GIT_TOPLEVEL="$(git rev-parse --show-toplevel)"
#__RESH_GIT_TOPLEVEL_EXIT_CODE=$?
if [ -n "$ZSH_VERSION" ]; then
# assume Zsh
local __RESH_PID="$$" # current pid
elif [ -n "$BASH_VERSION" ]; then
# assume Bash
local __RESH_PID="$BASHPID" # current pid
fi
# time
local __RESH_TZ_BEFORE; __RESH_TZ_BEFORE=$(date +%z)
# __RESH_RT_BEFORE="$EPOCHREALTIME"
__RESH_RT_BEFORE=$(__resh_get_epochrealtime)
if [ "$__RESH_VERSION" != "$(resh-collect -version)" ]; then
# shellcheck source=shellrc.sh
source ~/.resh/shellrc
if [ "$__RESH_VERSION" != "$(resh-collect -version)" ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-collect -version); resh version of this terminal session: ${__RESH_VERSION})"
else
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session."
fi
elif [ "$__RESH_REVISION" != "$(resh-collect -revision)" ]; then
# shellcheck source=shellrc.sh
source ~/.resh/shellrc
if [ "$__RESH_REVISION" != "$(resh-collect -revision)" ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-collect -revision); resh revision of this terminal session: ${__RESH_REVISION})"
fi
fi
if [ "$__RESH_VERSION" = "$(resh-collect -version)" ] && [ "$__RESH_REVISION" = "$(resh-collect -revision)" ]; then
resh-collect -requireVersion "$__RESH_VERSION" \
-requireRevision "$__RESH_REVISION" \
-shell "$__RESH_SHELL" \
-uname "$__RESH_UNAME" \
-sessionId "$__RESH_SESSION_ID" \
-cols "$__RESH_COLS" \
-home "$__RESH_HOME" \
-lang "$__RESH_LANG" \
-lcAll "$__RESH_LC_ALL" \
-lines "$__RESH_LINES" \
-login "$__RESH_LOGIN" \
-pwd "$__RESH_PWD" \
-shellEnv "$__RESH_SHELL_ENV" \
-term "$__RESH_TERM" \
-pid "$__RESH_PID" \
-sessionPid "$__RESH_SESSION_PID" \
-host "$__RESH_HOST" \
-hosttype "$__RESH_HOSTTYPE" \
-ostype "$__RESH_OSTYPE" \
-machtype "$__RESH_MACHTYPE" \
-shlvl "$__RESH_SHLVL" \
-gitCdup "$__RESH_GIT_CDUP" \
-gitCdupExitCode "$__RESH_GIT_CDUP_EXIT_CODE" \
-gitRemote "$__RESH_GIT_REMOTE" \
-gitRemoteExitCode "$__RESH_GIT_REMOTE_EXIT_CODE" \
-realtimeBefore "$__RESH_RT_BEFORE" \
-realtimeSession "$__RESH_RT_SESSION" \
-realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \
-timezoneBefore "$__RESH_TZ_BEFORE" \
-osReleaseId "$__RESH_OS_RELEASE_ID" \
-osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \
-osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \
-osReleaseName "$__RESH_OS_RELEASE_NAME" \
-osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \
-histno "$__RESH_HISTNO" \
"$@"
fi
}
__resh_precmd() {
local __RESH_EXIT_CODE=$?
local __RESH_RT_AFTER
local __RESH_TZ_AFTER
local __RESH_PWD_AFTER
local __RESH_GIT_CDUP_AFTER
local __RESH_GIT_CDUP_EXIT_CODE_AFTER
local __RESH_GIT_REMOTE_AFTER
local __RESH_GIT_REMOTE_EXIT_CODE_AFTER
local __RESH_SHLVL="$SHLVL"
__RESH_RT_AFTER=$(__resh_get_epochrealtime)
__RESH_TZ_AFTER=$(date +%z)
__RESH_PWD_AFTER="$PWD"
__RESH_GIT_CDUP_AFTER="$(git rev-parse --show-cdup 2>/dev/null)"
__RESH_GIT_CDUP_EXIT_CODE_AFTER=$?
__RESH_GIT_REMOTE_AFTER="$(git remote get-url origin 2>/dev/null)"
__RESH_GIT_REMOTE_EXIT_CODE_AFTER=$?
if [ -n "${__RESH_COLLECT}" ]; then
if [ "$__RESH_VERSION" != "$(resh-postcollect -version)" ]; then
# shellcheck source=shellrc.sh
source ~/.resh/shellrc
if [ "$__RESH_VERSION" != "$(resh-postcollect -version)" ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-collect -version); resh version of this terminal session: ${__RESH_VERSION})"
else
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session."
fi
elif [ "$__RESH_REVISION" != "$(resh-postcollect -revision)" ]; then
# shellcheck source=shellrc.sh
source ~/.resh/shellrc
if [ "$__RESH_REVISION" != "$(resh-postcollect -revision)" ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-collect -revision); resh revision of this terminal session: ${__RESH_REVISION})"
fi
fi
if [ "$__RESH_VERSION" = "$(resh-postcollect -version)" ] && [ "$__RESH_REVISION" = "$(resh-postcollect -revision)" ]; then
resh-postcollect -requireVersion "$__RESH_VERSION" \
-requireRevision "$__RESH_REVISION" \
-cmdLine "$__RESH_CMDLINE" \
-realtimeBefore "$__RESH_RT_BEFORE" \
-exitCode "$__RESH_EXIT_CODE" \
-sessionId "$__RESH_SESSION_ID" \
-shlvl "$__RESH_SHLVL" \
-pwdAfter "$__RESH_PWD_AFTER" \
-gitCdupAfter "$__RESH_GIT_CDUP_AFTER" \
-gitCdupExitCodeAfter "$__RESH_GIT_CDUP_EXIT_CODE_AFTER" \
-gitRemoteAfter "$__RESH_GIT_REMOTE_AFTER" \
-gitRemoteExitCodeAfter "$__RESH_GIT_REMOTE_EXIT_CODE_AFTER" \
-realtimeAfter "$__RESH_RT_AFTER" \
-timezoneAfter "$__RESH_TZ_AFTER" \
&>~/.resh/postcollect_last_run_out.txt || echo "resh-postcollect ERROR: $(head -n 1 ~/.resh/postcollect_last_run_out.txt)"
fi
__resh_reset_variables
fi
unset __RESH_COLLECT
}

@ -0,0 +1,70 @@
# shellcheck source=../submodules/bash-zsh-compat-widgets/bindfunc.sh
. ~/.resh/bindfunc.sh
# shellcheck source=widgets.sh
. ~/.resh/widgets.sh
__resh_bind_arrows() {
bindfunc '\e[A' __resh_widget_arrow_up_compat
bindfunc '\e[B' __resh_widget_arrow_down_compat
return 0
}
__resh_bind_control_R() {
echo "bindfunc __resh_widget_control_R_compat"
return 0
}
__resh_unbind_arrows() {
echo "\ bindfunc __resh_widget_arrow_up_compat"
echo "\ bindfunc __resh_widget_arrow_down_compat"
return 0
}
__resh_unbind_control_R() {
echo "\ bindfunc __resh_widget_control_R_compat"
return 0
}
__resh_bind_all() {
__resh_bind_arrows
__resh_bind_control_R
}
__resh_unbind_all() {
__resh_unbind_arrows
__resh_unbind_control_R
}
reshctl() {
# run resh-control aka the real reshctl
resh-control "$@"
# modify current shell session based on exit status
local _status=$?
case "$_status" in
0|1)
# success | fail
return "$_status"
;;
# enable
100)
# enable all
__resh_bind_all
return 0
;;
# disable
110)
# disable all
__resh_unbind_all
return 0
;;
200)
# reload rc files
. ~/.resh/shellrc
return 0
;;
*)
echo "reshctl() FATAL ERROR: unknown status" >&2
return "$_status"
;;
esac
}

@ -4,41 +4,12 @@ PATH=$PATH:~/.resh/bin
# zmodload zsh/datetime
# fi
__resh_get_uuid() {
cat /proc/sys/kernel/random/uuid 2>/dev/null || resh-uuid
}
__resh_get_epochrealtime() {
if date +%s.%N | grep -vq 'N'; then
# GNU date
date +%s.%N
elif gdate --version >/dev/null && gdate +%s.%N | grep -vq 'N'; then
# GNU date take 2
gdate +%s.%N
elif [ -n "$ZSH_VERSION" ]; then
# zsh fallback using $EPOCHREALTIME
if [ -z "${__RESH_ZSH_LOADED_DATETIME+x}" ]; then
zmodload zsh/datetime
__RESH_ZSH_LOADED_DATETIME=1
fi
echo "$EPOCHREALTIME"
else
# dumb date
# XXX: we lost precison beyond seconds
date +%s
if [ -z "${__RESH_DATE_WARN+x}" ]; then
echo "resh WARN: can't get precise time - consider installing GNU date!"
__RESH_DATE_WARN=1
fi
fi
}
__resh_run_daemon() {
if [ -n "$ZSH_VERSION" ]; then
setopt LOCAL_OPTIONS NO_NOTIFY NO_MONITOR
fi
nohup resh-daemon &>/dev/null & disown
}
# shellcheck source=hooks.sh
. ~/.resh/hooks.sh
# shellcheck source=util.sh
. ~/.resh/util.sh
# shellcheck source=reshctl.sh
. ~/.resh/reshctl.sh
__RESH_MACOS=0
__RESH_LINUX=0
@ -53,23 +24,20 @@ else
fi
if [ -n "$ZSH_VERSION" ]; then
# shellcheck disable=SC1009
__RESH_SHELL="zsh"
__RESH_HOST="$HOST"
__RESH_HOSTTYPE="$CPUTYPE"
__resh_zsh_completion_init
elif [ -n "$BASH_VERSION" ]; then
__RESH_SHELL="bash"
__RESH_HOST="$HOSTNAME"
__RESH_HOSTTYPE="$HOSTTYPE"
__resh_bash_completion_init
else
echo "resh PANIC unrecognized shell"
fi
if [ -z "${__RESH_SESSION_ID+x}" ]; then
export __RESH_SESSION_ID=$(__resh_get_uuid)
export __RESH_SESSION_PID="$$"
# TODO add sesson time
fi
# posix
__RESH_HOME="$HOME"
__RESH_LOGIN="$LOGNAME"
@ -101,116 +69,22 @@ __RESH_REVISION=$(resh-collect -revision)
__resh_run_daemon
__resh_preexec() {
# core
__RESH_COLLECT=1
__RESH_CMDLINE="$1"
# posix
__RESH_COLS="$COLUMNS"
__RESH_LANG="$LANG"
__RESH_LC_ALL="$LC_ALL"
# other LC ?
__RESH_LINES="$LINES"
# __RESH_PATH="$PATH"
__RESH_PWD="$PWD"
# non-posix
__RESH_SHLVL="$SHLVL"
__RESH_GIT_CDUP="$(git rev-parse --show-cdup 2>/dev/null)"
__RESH_GIT_CDUP_EXIT_CODE=$?
__RESH_GIT_REMOTE="$(git remote get-url origin 2>/dev/null)"
__RESH_GIT_REMOTE_EXIT_CODE=$?
#__RESH_GIT_TOPLEVEL="$(git rev-parse --show-toplevel)"
#__RESH_GIT_TOPLEVEL_EXIT_CODE=$?
if [ -n "$ZSH_VERSION" ]; then
# assume Zsh
__RESH_PID="$$" # current pid
elif [ -n "$BASH_VERSION" ]; then
# assume Bash
__RESH_PID="$BASHPID" # current pid
# block for anything we only want to do once per session
# NOTE: nested shells are still the same session
if [ -z "${__RESH_SESSION_ID+x}" ]; then
export __RESH_SESSION_ID; __RESH_SESSION_ID=$(__resh_get_uuid)
export __RESH_SESSION_PID="$$"
# TODO add sesson time
__resh_reset_variables
__resh_session_init
fi
# time
__RESH_TZ_BEFORE=$(date +%z)
# __RESH_RT_BEFORE="$EPOCHREALTIME"
__RESH_RT_BEFORE=$(__resh_get_epochrealtime)
# TODO: we should evaluate symlinks in preexec
# -> maybe create resh-precollect that could handle most of preexec
# maybe even move resh-collect here and send data to daemon and
# send rest of the data ($?, timeAfter) to daemon in precmd
# daemon will combine the data and save the record
# and save the unfinnished record even if it never finishes
# detect if the command died with the parent ps and save it then
# block for anything we only want to do once per shell
if [ -z "${__RESH_INIT_DONE+x}" ]; then
preexec_functions+=(__resh_preexec)
precmd_functions+=(__resh_precmd)
}
__resh_reset_variables
__resh_precmd() {
__RESH_EXIT_CODE=$?
__RESH_RT_AFTER=$(__resh_get_epochrealtime)
__RESH_TZ_AFTER=$(date +%z)
__RESH_PWD_AFTER="$PWD"
if [ -n "${__RESH_COLLECT}" ]; then
if [ "$__RESH_VERSION" != $(resh-collect -version) ]; then
source ~/.resh/shellrc
if [ "$__RESH_VERSION" != $(resh-collect -version) ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-collect -version); resh version of this terminal session: ${__RESH_VERSION})"
else
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session."
fi
elif [ "$__RESH_REVISION" != $(resh-collect -revision) ]; then
source ~/.resh/shellrc
if [ "$__RESH_REVISION" != $(resh-collect -revision) ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-collect -revision); resh revision of this terminal session: ${__RESH_REVISION})"
fi
fi
if [ "$__RESH_VERSION" = $(resh-collect -version) ] && [ "$__RESH_REVISION" = $(resh-collect -revision) ]; then
resh-collect -requireVersion "$__RESH_VERSION" \
-requireRevision "$__RESH_REVISION" \
-cmdLine "$__RESH_CMDLINE" \
-exitCode "$__RESH_EXIT_CODE" \
-shell "$__RESH_SHELL" \
-uname "$__RESH_UNAME" \
-sessionId "$__RESH_SESSION_ID" \
-cols "$__RESH_COLS" \
-home "$__RESH_HOME" \
-lang "$__RESH_LANG" \
-lcAll "$__RESH_LC_ALL" \
-lines "$__RESH_LINES" \
-login "$__RESH_LOGIN" \
-pwd "$__RESH_PWD" \
-pwdAfter "$__RESH_PWD_AFTER" \
-shellEnv "$__RESH_SHELL_ENV" \
-term "$__RESH_TERM" \
-pid "$__RESH_PID" \
-sessionPid "$__RESH_SESSION_PID" \
-host "$__RESH_HOST" \
-hosttype "$__RESH_HOSTTYPE" \
-ostype "$__RESH_OSTYPE" \
-machtype "$__RESH_MACHTYPE" \
-shlvl "$__RESH_SHLVL" \
-gitCdup "$__RESH_GIT_CDUP" \
-gitCdupExitCode "$__RESH_GIT_CDUP_EXIT_CODE" \
-gitRemote "$__RESH_GIT_REMOTE" \
-gitRemoteExitCode "$__RESH_GIT_REMOTE_EXIT_CODE" \
-realtimeBefore "$__RESH_RT_BEFORE" \
-realtimeAfter "$__RESH_RT_AFTER" \
-realtimeSession "$__RESH_RT_SESSION" \
-realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \
-timezoneBefore "$__RESH_TZ_BEFORE" \
-timezoneAfter "$__RESH_TZ_AFTER" \
-osReleaseId "$__RESH_OS_RELEASE_ID" \
-osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \
-osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \
-osReleaseName "$__RESH_OS_RELEASE_NAME" \
-osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \
&>~/.resh/client_last_run_out.txt || echo "resh ERROR: $(head -n 1 ~/.resh/client_last_run_out.txt)"
# -path "$__RESH_PATH" \
__RESH_INIT_DONE=1
fi
fi
unset __RESH_COLLECT
}
preexec_functions+=(__resh_preexec)
precmd_functions+=(__resh_precmd)

@ -7,8 +7,10 @@ for f in scripts/*.sh; do
shellcheck $f --shell=bash --severity=error || exit 1
done
echo "Checking Zsh syntax of scripts/shellrc.sh ..."
for f in scripts/{shellrc,util,reshctl,hooks}.sh; do
echo "Checking Zsh syntax of $f ..."
! zsh -n scripts/shellrc.sh && echo "Zsh syntax check failed!" && exit 1
done
for sh in bash zsh; do
echo "Running functions in scripts/shellrc.sh using $sh ..."

@ -0,0 +1,136 @@
# util.sh - resh utility functions
__resh_get_uuid() {
cat /proc/sys/kernel/random/uuid 2>/dev/null || resh-uuid
}
__resh_get_pid() {
if [ -n "$ZSH_VERSION" ]; then
# assume Zsh
local __RESH_PID="$$" # current pid
elif [ -n "$BASH_VERSION" ]; then
# assume Bash
local __RESH_PID="$BASHPID" # current pid
fi
echo "$__RESH_PID"
}
__resh_get_epochrealtime() {
if date +%s.%N | grep -vq 'N'; then
# GNU date
date +%s.%N
elif gdate --version >/dev/null && gdate +%s.%N | grep -vq 'N'; then
# GNU date take 2
gdate +%s.%N
elif [ -n "$ZSH_VERSION" ]; then
# zsh fallback using $EPOCHREALTIME
if [ -z "${__RESH_ZSH_LOADED_DATETIME+x}" ]; then
zmodload zsh/datetime
__RESH_ZSH_LOADED_DATETIME=1
fi
echo "$EPOCHREALTIME"
else
# dumb date
# XXX: we lost precison beyond seconds
date +%s
if [ -z "${__RESH_DATE_WARN+x}" ]; then
echo "resh WARN: can't get precise time - consider installing GNU date!"
__RESH_DATE_WARN=1
fi
fi
}
__resh_run_daemon() {
if [ -n "$ZSH_VERSION" ]; then
setopt LOCAL_OPTIONS NO_NOTIFY NO_MONITOR
fi
nohup resh-daemon &>~/.resh/daemon_last_run_out.txt & disown
}
__resh_bash_completion_init() {
local bash_completion_dir=~/.resh/bash_completion.d
# source user completion directory definitions
# taken from /usr/share/bash-completion/bash_completion
if [[ -d $bash_completion_dir && -r $bash_completion_dir && \
-x $bash_completion_dir ]]; then
for i in $(LC_ALL=C command ls "$bash_completion_dir"); do
i=$bash_completion_dir/$i
# shellcheck disable=SC2154
# shellcheck source=/dev/null
[[ ${i##*/} != @($_backup_glob|Makefile*|$_blacklist_glob) \
&& -f $i && -r $i ]] && . "$i"
done
fi
}
__resh_zsh_completion_init() {
# shellcheck disable=SC2206
fpath=(~/.resh/zsh_completion.d $fpath)
}
__resh_session_init() {
# posix
local __RESH_COLS="$COLUMNS"
local __RESH_LANG="$LANG"
local __RESH_LC_ALL="$LC_ALL"
# other LC ?
local __RESH_LINES="$LINES"
local __RESH_PWD="$PWD"
# non-posix
local __RESH_SHLVL="$SHLVL"
# pid
local __RESH_PID; __RESH_PID=$(__resh_get_pid)
# time
local __RESH_TZ_BEFORE; __RESH_TZ_BEFORE=$(date +%z)
local __RESH_RT_BEFORE; __RESH_RT_BEFORE=$(__resh_get_epochrealtime)
if [ "$__RESH_VERSION" != "$(resh-session-init -version)" ]; then
# shellcheck source=shellrc.sh
source ~/.resh/shellrc
if [ "$__RESH_VERSION" != "$(resh-session-init -version)" ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-session-init -version); resh version of this terminal session: ${__RESH_VERSION})"
else
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session."
fi
elif [ "$__RESH_REVISION" != "$(resh-session-init -revision)" ]; then
# shellcheck source=shellrc.sh
source ~/.resh/shellrc
if [ "$__RESH_REVISION" != "$(resh-session-init -revision)" ]; then
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-session-init -revision); resh revision of this terminal session: ${__RESH_REVISION})"
fi
fi
if [ "$__RESH_VERSION" = "$(resh-session-init -version)" ] && [ "$__RESH_REVISION" = "$(resh-session-init -revision)" ]; then
resh-session-init -requireVersion "$__RESH_VERSION" \
-requireRevision "$__RESH_REVISION" \
-shell "$__RESH_SHELL" \
-uname "$__RESH_UNAME" \
-sessionId "$__RESH_SESSION_ID" \
-cols "$__RESH_COLS" \
-home "$__RESH_HOME" \
-lang "$__RESH_LANG" \
-lcAll "$__RESH_LC_ALL" \
-lines "$__RESH_LINES" \
-login "$__RESH_LOGIN" \
-shellEnv "$__RESH_SHELL_ENV" \
-term "$__RESH_TERM" \
-pid "$__RESH_PID" \
-sessionPid "$__RESH_SESSION_PID" \
-host "$__RESH_HOST" \
-hosttype "$__RESH_HOSTTYPE" \
-ostype "$__RESH_OSTYPE" \
-machtype "$__RESH_MACHTYPE" \
-shlvl "$__RESH_SHLVL" \
-realtimeBefore "$__RESH_RT_BEFORE" \
-realtimeSession "$__RESH_RT_SESSION" \
-realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \
-timezoneBefore "$__RESH_TZ_BEFORE" \
-osReleaseId "$__RESH_OS_RELEASE_ID" \
-osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \
-osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \
-osReleaseName "$__RESH_OS_RELEASE_NAME" \
-osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \
&>~/.resh/session_init_last_run_out.txt || echo "resh-session-init ERROR: $(head -n 1 ~/.resh/session_init_last_run_out.txt)"
fi
}

@ -0,0 +1,93 @@
# shellcheck source=hooks.sh
. ~/.resh/hooks.sh
__resh_helper_arrow_pre() {
# this is a very bad workaround
# force bash-preexec to run repeatedly because otherwise premature run of bash-preexec overshadows the next poper run
# I honestly think that it's impossible to make widgets work in bash without hacks like this
# shellcheck disable=2034
__bp_preexec_interactive_mode="on"
# set recall strategy
__RESH_HIST_RECALL_STRATEGY="bash_recent - history-search-{backward,forward}"
# set prefix
__RESH_PREFIX=${BUFFER:0:$CURSOR}
# cursor not at the end of the line => end "NO_PREFIX_MODE"
[ "$CURSOR" -ne "${#BUFFER}" ] && __RESH_HIST_NO_PREFIX_MODE=0
# if user made any edits from last recall action => restart histno AND deactivate "NO_PREFIX_MODE"
[ "$BUFFER" != "$__RESH_HIST_PREV_LINE" ] && __RESH_HISTNO=0 && __RESH_HIST_NO_PREFIX_MODE=0
# "NO_PREFIX_MODE" => set prefix to empty string
[ "$__RESH_HIST_NO_PREFIX_MODE" -eq 1 ] && __RESH_PREFIX=""
# histno == 0 => save current line
[ "$__RESH_HISTNO" -eq 0 ] && __RESH_HISTNO_ZERO_LINE=$BUFFER
}
__resh_helper_arrow_post() {
# cursor at the beginning of the line => activate "NO_PREFIX_MODE"
[ "$CURSOR" -eq 0 ] && __RESH_HIST_NO_PREFIX_MODE=1
# "NO_PREFIX_MODE" => move cursor to the end of the line
[ "$__RESH_HIST_NO_PREFIX_MODE" -eq 1 ] && CURSOR=${#BUFFER}
# save current line so we can spot user edits next time
__RESH_HIST_PREV_LINE=$BUFFER
}
__resh_widget_arrow_up() {
# run helper function
__resh_helper_arrow_pre
# append curent recall action
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_up:$__RESH_PREFIX"
# increment histno
__RESH_HISTNO=$((__RESH_HISTNO+1))
# back at histno == 0 => restore original line
if [ "$__RESH_HISTNO" -eq 0 ]; then
BUFFER=$__RESH_HISTNO_ZERO_LINE
else
# run recall
local NEW_BUFFER
NEW_BUFFER="$(__resh_collect --recall --prefix-search "$__RESH_PREFIX" 2> ~/.resh/arrow_up_last_run_out.txt)"
# IF new buffer in non-empty THEN use the new buffer ELSE revert histno change
# shellcheck disable=SC2015
[ "${#NEW_BUFFER}" -gt 0 ] && BUFFER=$NEW_BUFFER || __RESH_HISTNO=$((__RESH_HISTNO-1))
fi
# run post helper
__resh_helper_arrow_post
}
__resh_widget_arrow_down() {
# run helper function
__resh_helper_arrow_pre
# append curent recall action
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_down:$__RESH_PREFIX"
# increment histno
__RESH_HISTNO=$((__RESH_HISTNO-1))
# prevent HISTNO from getting negative (for now)
[ "$__RESH_HISTNO" -lt 0 ] && __RESH_HISTNO=0
# back at histno == 0 => restore original line
if [ "$__RESH_HISTNO" -eq 0 ]; then
BUFFER=$__RESH_HISTNO_ZERO_LINE
else
# run recall
local NEW_BUFFER
NEW_BUFFER="$(__resh_collect --recall --prefix-search "$__RESH_PREFIX" 2> ~/.resh/arrow_down_last_run_out.txt)"
# IF new buffer in non-empty THEN use the new buffer ELSE revert histno change
# shellcheck disable=SC2015
[ "${#NEW_BUFFER}" -gt 0 ] && BUFFER=$NEW_BUFFER || (( __RESH_HISTNO++ ))
fi
__resh_helper_arrow_post
}
__resh_widget_control_R() {
local __RESH_PREFIX=${BUFFER:0:CURSOR}
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;control_R:$__RESH_PREFIX"
# resh-collect --hstr
hstr
}
__resh_widget_arrow_up_compat() {
__bindfunc_compat_wrapper __resh_widget_arrow_up
}
__resh_widget_arrow_down_compat() {
__bindfunc_compat_wrapper __resh_widget_arrow_down
}
__resh_widget_control_R_compat() {
__bindfunc_compat_wrapper __resh_widget_control_R
}

@ -0,0 +1 @@
Subproject commit 7dde81eaa09cbed11ebc70ea892bcae24ea1606c
Loading…
Cancel
Save