checkpoint before continuing work after a while

pull/179/head
Simon Let 3 years ago
parent bafb244588
commit 9dfcb25d95
No known key found for this signature in database
GPG Key ID: D650C65DD46D431D
  1. 7
      Makefile
  2. 46
      cmd/config-setup/main.go
  3. 5
      cmd/install-utils/backup.go
  4. 49
      cmd/install-utils/main.go
  5. 36
      cmd/install-utils/migrate.go
  6. 1
      go.mod
  7. 2
      go.sum
  8. 2
      internal/cfg/cfg.go
  9. 71
      internal/datadir/datadir.go
  10. 18
      internal/deviceid/deviceid.go
  11. 56
      internal/histio/file.go
  12. 44
      internal/histio/histio.go
  13. 9
      internal/logger/logger.go
  14. 19
      internal/msg/msg.go
  15. 9
      internal/recconv/recconv.go
  16. 158
      internal/recio/read.go
  17. 13
      internal/recio/recio.go
  18. 46
      internal/recio/write.go
  19. 1
      internal/recload/recload.go
  20. 88
      internal/record/legacy.go
  21. 2
      internal/record/record.go
  22. 58
      internal/record/v1.go
  23. 21
      internal/recordint/collect.go
  24. 51
      internal/recordint/enriched.go
  25. 9
      internal/recordint/flag.go
  26. 9
      internal/recordint/indexed.go
  27. 2
      internal/recordint/recordint.go
  28. 40
      internal/recordint/searchapp.go
  29. 360
      internal/records/records.go
  30. 66
      internal/records/records_test.go
  31. 94
      internal/recutil/recutil.go
  32. 14
      scripts/hooks.sh
  33. 21
      scripts/install.sh
  34. 1218
      scripts/resh-evaluate-plot.py
  35. 17
      scripts/shellrc.sh
  36. 35
      scripts/util.sh

@ -5,8 +5,9 @@ VERSION="${LATEST_TAG}-DEV"
GOFLAGS=-ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.development=true" GOFLAGS=-ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.development=true"
build: submodules bin/resh-session-init bin/resh-collect bin/resh-postcollect bin/resh-daemon\ build: submodules bin/resh-session-init bin/resh-collect bin/resh-postcollect\
bin/resh-control bin/resh-config bin/resh-cli bin/resh-config-setup bin/resh-daemon bin/resh-control bin/resh-config bin/resh-cli\
bin/installutil
install: build install: build
scripts/install.sh scripts/install.sh
@ -21,7 +22,7 @@ rebuild:
make build make build
clean: clean:
rm -f bin/resh-* rm -f bin/*
uninstall: uninstall:
# Uninstalling ... # Uninstalling ...

@ -1,46 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/curusarn/resh/internal/cfg"
"github.com/curusarn/resh/internal/logger"
"go.uber.org/zap"
)
// info passed during build
var version string
var commit string
var developement bool
func main() {
errDo := doConfigSetup()
config, errCfg := cfg.New()
logger, _ := logger.New("config-setup", config.LogLevel, developement)
defer logger.Sync() // flushes buffer, if any
if errDo != nil {
logger.Error("Config setup failed", zap.Error(errDo))
// TODO: better error message for people
fmt.Fprintf(os.Stderr, "ERROR: %v\n", errDo)
}
if errCfg != nil {
logger.Error("Error while getting configuration", zap.Error(errCfg))
}
}
func doConfigSetup() error {
err := cfg.Touch()
if err != nil {
return fmt.Errorf("could not touch config file: %w", err)
}
changes, err := cfg.Migrate()
if err != nil {
return fmt.Errorf("could not migrate config file version: %v", err)
}
if changes {
fmt.Printf("Config file format has changed - your config was updated to reflect the changes.\n")
}
return nil
}

@ -0,0 +1,5 @@
package main
func backup() {
}

@ -0,0 +1,49 @@
package main
import (
"flag"
"fmt"
"os"
)
// info passed during build
var version string
var commit string
var developement bool
func main() {
var command string
flag.StringVar(&command, "command", "", "Utility to run")
flag.Parse()
switch command {
case "backup":
backup()
case "rollback":
rollback()
case "migrate-config":
migrateConfig()
case "migrate-history":
migrateHistory()
case "help":
printUsage(os.Stdout)
default:
fmt.Fprintf(os.Stderr, "ERROR: Unknown command")
printUsage(os.Stderr)
}
}
func printUsage(f *os.File) {
usage := `
Utils used during resh instalation
USAGE: ./install-utils COMMAND
COMMANDS:
backup backup resh installation and data
rollback restore resh installation and data from backup
migrate-config update config to reflect updates
migrate-history update history to reflect updates
help show this help
`
fmt.Fprintf(f, usage)
}

@ -0,0 +1,36 @@
package main
import (
"fmt"
"os"
"github.com/curusarn/resh/internal/cfg"
)
func migrateConfig() {
err := cfg.Touch()
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: Failed to touch config file: %v\n", err)
os.Exit(1)
}
changes, err := cfg.Migrate()
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: Failed to update config file: %v\n", err)
os.Exit(1)
}
if changes {
fmt.Printf("Config file format has changed since last update - your config was updated to reflect the changes.\n")
}
}
func migrateHistory() {
// homeDir, err := os.UserHomeDir()
// if err != nil {
// }
// TODO: Find history in:
// - xdg_data/resh/history.reshjson
// - .resh_history.json
// - .resh/history.json
}

@ -8,6 +8,7 @@ require (
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/go-ps v1.0.0
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.2.1
github.com/whilp/git-urls v1.0.0
go.uber.org/zap v1.21.0 go.uber.org/zap v1.21.0
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6
) )

@ -246,6 +246,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

@ -59,7 +59,7 @@ var defaults = Config{
BindControlR: true, BindControlR: true,
Debug: false, Debug: false,
SesswatchPeriodSeconds: 120, SesswatchPeriodSeconds: 600,
SesshistInitHistorySize: 1000, SesshistInitHistorySize: 1000,
} }

@ -0,0 +1,71 @@
package datadir
import (
"fmt"
"os"
"path"
)
// You should not need this caching
// It messes with proper dependency injection
// Find another way
// type dirCache struct {
// dir string
// err error
//
// cached bool
// }
//
// var cache dirCache
//
// func getPathNoCache() (string, error) {
// reshDir := "resh"
// xdgDir, found := os.LookupEnv("XDG_DATA_HOME")
// if found {
// return path.Join(xdgDir, reshDir), nil
// }
// homeDir, err := os.UserHomeDir()
// if err != nil {
// return "", fmt.Errorf("error while getting home dir: %w", err)
// }
// return path.Join(homeDir, ".local/share/", reshDir), nil
// }
//
// func GetPath() (string, error) {
// if !cache.cached {
// dir, err := getPathNoCache()
// cache = dirCache{
// dir: dir,
// err: err,
// cached: true,
// }
// }
// return cache.dir, cache.err
// }
func GetPath() (string, error) {
reshDir := "resh"
xdgDir, found := os.LookupEnv("XDG_DATA_HOME")
if found {
return path.Join(xdgDir, reshDir), nil
}
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("error while getting home dir: %w", err)
}
return path.Join(homeDir, ".local/share/", reshDir), nil
}
func MakePath() (string, error) {
path, err := GetPath()
if err != nil {
return "", err
}
err = os.MkdirAll(path, 0755)
// skip "exists" error
if err != nil && !os.IsExist(err) {
return "", fmt.Errorf("error while creating directories: %w", err)
}
return path, nil
}

@ -0,0 +1,18 @@
package deviceid
import (
"fmt"
"os"
"path"
"strings"
)
func Get(dataDir string) (string, error) {
fname := "device-id"
dat, err := os.ReadFile(path.Join(dataDir, fname))
if err != nil {
return "", fmt.Errorf("could not read file with device-id: %w", err)
}
id := strings.TrimRight(string(dat), "\n")
return id, nil
}

@ -0,0 +1,56 @@
package histio
import (
"fmt"
"os"
"sync"
"github.com/curusarn/resh/internal/recio"
"github.com/curusarn/resh/internal/recordint"
"go.uber.org/zap"
)
type histfile struct {
sugar *zap.SugaredLogger
// deviceID string
path string
mu sync.RWMutex
data []recordint.Indexed
fileinfo os.FileInfo
}
func newHistfile(sugar *zap.SugaredLogger, path string) *histfile {
return &histfile{
sugar: sugar.With(
// FIXME: drop V1 once original histfile is gone
"component", "histfileV1",
"path", path,
),
// deviceID: deviceID,
path: path,
}
}
func (h *histfile) updateFromFile() error {
rio := recio.New(h.sugar)
// TODO: decide and handle errors
newData, _, err := rio.ReadFile(h.path)
if err != nil {
return fmt.Errorf("could not read history file: %w", err)
}
h.mu.Lock()
defer h.mu.Unlock()
h.data = newData
h.updateFileInfo()
return nil
}
func (h *histfile) updateFileInfo() error {
info, err := os.Stat(h.path)
if err != nil {
return fmt.Errorf("history file not found: %w", err)
}
h.fileinfo = info
return nil
}

@ -0,0 +1,44 @@
package histio
import (
"path"
"github.com/curusarn/resh/internal/record"
"github.com/curusarn/resh/internal/recordint"
"go.uber.org/zap"
)
type Histio struct {
sugar *zap.SugaredLogger
histDir string
thisDeviceID string
thisHistory *histfile
// TODO: remote histories
// moreHistories map[string]*histfile
recordsToAppend chan record.V1
recordsToFlag chan recordint.Flag
}
func New(sugar *zap.SugaredLogger, dataDir, deviceID string) *Histio {
sugarHistio := sugar.With(zap.String("component", "histio"))
histDir := path.Join(dataDir, "history")
currPath := path.Join(histDir, deviceID)
// TODO: file extenstion for the history, yes or no? (<id>.reshjson vs. <id>)
// TODO: discover other history files, exclude current
return &Histio{
sugar: sugarHistio,
histDir: histDir,
thisDeviceID: deviceID,
thisHistory: newHistfile(sugar, currPath),
// moreHistories: ...
}
}
func (h *Histio) Append(r *record.V1) {
}

@ -2,20 +2,19 @@ package logger
import ( import (
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"github.com/curusarn/resh/internal/datadir"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
func New(executable string, level zapcore.Level, developement bool) (*zap.Logger, error) { func New(executable string, level zapcore.Level, developement bool) (*zap.Logger, error) {
// TODO: consider getting log path from config ? dataDir, err := datadir.GetPath()
homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
return nil, fmt.Errorf("error while getting home dir: %w", err) return nil, fmt.Errorf("error while getting resh data dir: %w", err)
} }
logPath := filepath.Join(homeDir, ".resh/log.json") logPath := filepath.Join(dataDir, "log.json")
loggerConfig := zap.NewProductionConfig() loggerConfig := zap.NewProductionConfig()
loggerConfig.OutputPaths = []string{logPath} loggerConfig.OutputPaths = []string{logPath}
loggerConfig.Level.SetLevel(level) loggerConfig.Level.SetLevel(level)

@ -1,27 +1,14 @@
package msg package msg
import "github.com/curusarn/resh/internal/records"
// CliMsg struct // CliMsg struct
type CliMsg struct { type CliMsg struct {
SessionID string `json:"sessionID"` SessionID string
PWD string `json:"pwd"` PWD string
} }
// CliResponse struct // CliResponse struct
type CliResponse struct { type CliResponse struct {
CliRecords []records.CliRecord `json:"cliRecords"` Records []record.SearchApp
}
// InspectMsg struct
type InspectMsg struct {
SessionID string `json:"sessionId"`
Count uint `json:"count"`
}
// MultiResponse struct
type MultiResponse struct {
CmdLines []string `json:"cmdlines"`
} }
// StatusResponse struct // StatusResponse struct

@ -0,0 +1,9 @@
package recconv
import "github.com/curusarn/resh/internal/record"
func LegacyToV1(r *record.Legacy) *record.V1 {
return &record.V1{
// FIXME: fill in all the fields
}
}

@ -0,0 +1,158 @@
package recio
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"github.com/curusarn/resh/internal/recconv"
"github.com/curusarn/resh/internal/record"
"github.com/curusarn/resh/internal/recordint"
"go.uber.org/zap"
)
func (r *RecIO) ReadAndFixFile(fpath string, maxErrors int) ([]recordint.Indexed, error) {
recs, numErrs, err := r.ReadFile(fpath)
if err != nil {
return nil, err
}
if numErrs > maxErrors {
return nil, fmt.Errorf("encountered too many decoding errors")
}
if numErrs == 0 {
return recs, nil
}
// TODO: check there error messages
r.sugar.Warnw("Some history records could not be decoded - fixing resh history file by dropping them",
"corruptedRecords", numErrs,
)
fpathBak := fpath + ".bak"
r.sugar.Infow("Backing up current corrupted history file",
"backupFilename", fpathBak,
)
// TODO: maybe use upstram copy function
err = copyFile(fpath, fpathBak)
if err != nil {
r.sugar.Errorw("Failed to create a backup history file - aborting fixing history file",
"backupFilename", fpathBak,
zap.Error(err),
)
return recs, nil
}
r.sugar.Info("Writing resh history file without errors ...")
var recsV1 []record.V1
for _, rec := range recs {
recsV1 = append(recsV1, rec.Rec)
}
err = r.WriteFile(fpath, recsV1)
if err != nil {
r.sugar.Errorw("Failed write fixed history file - aborting fixing history file",
"filename", fpath,
zap.Error(err),
)
}
return recs, nil
}
func (r *RecIO) ReadFile(fpath string) ([]recordint.Indexed, int, error) {
var recs []recordint.Indexed
file, err := os.Open(fpath)
if err != nil {
return nil, 0, fmt.Errorf("failed to open history file: %w", err)
}
defer file.Close()
reader := bufio.NewReader(file)
numErrs := 0
var idx int
for {
var line string
line, err = reader.ReadString('\n')
if err != nil {
break
}
idx++
rec, err := r.decodeLine(line)
if err != nil {
numErrs++
continue
}
recidx := recordint.Indexed{
Rec: *rec,
// TODO: Is line index actually enough?
// Don't we want to count bytes because we will scan by number of bytes?
// hint: https://benjamincongdon.me/blog/2018/04/10/Counting-Scanned-Bytes-in-Go/
Idx: idx,
}
recs = append(recs, recidx)
}
if err != io.EOF {
r.sugar.Error("Error while loading file", zap.Error(err))
}
r.sugar.Infow("Loaded resh history records",
"recordCount", len(recs),
)
return recs, numErrs, nil
}
func copyFile(source, dest string) error {
from, err := os.Open(source)
if err != nil {
return err
}
defer from.Close()
// This is equivalnet to: os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0666)
to, err := os.Create(dest)
if err != nil {
return err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
return nil
}
func (r *RecIO) decodeLine(line string) (*record.V1, error) {
idx := strings.Index(line, "{")
if idx == -1 {
return nil, fmt.Errorf("no openning brace found")
}
schema := line[:idx]
jsn := line[idx:]
switch schema {
case "v1":
var rec record.V1
err := decodeAnyRecord(jsn, &rec)
if err != nil {
return nil, err
}
return &rec, nil
case "":
var rec record.Legacy
err := decodeAnyRecord(jsn, &rec)
if err != nil {
return nil, err
}
return recconv.LegacyToV1(&rec), nil
default:
return nil, fmt.Errorf("unknown record schema/type '%s'", schema)
}
}
// TODO: find out if we are loosing performance because of the use of interface{}
func decodeAnyRecord(jsn string, rec interface{}) error {
err := json.Unmarshal([]byte(jsn), &rec)
if err != nil {
return fmt.Errorf("failed to decode json: %w", err)
}
return nil
}

@ -0,0 +1,13 @@
package recio
import (
"go.uber.org/zap"
)
type RecIO struct {
sugar *zap.SugaredLogger
}
func New(sugar *zap.SugaredLogger) RecIO {
return RecIO{sugar: sugar}
}

@ -0,0 +1,46 @@
package recio
import (
"encoding/json"
"fmt"
"os"
"github.com/curusarn/resh/internal/record"
"github.com/curusarn/resh/internal/recordint"
)
// TODO: better errors
func (r *RecIO) WriteFile(fpath string, data []record.V1) error {
file, err := os.Create(fpath)
if err != nil {
return err
}
defer file.Close()
for _, rec := range data {
jsn, err := encodeV1Record(rec)
if err != nil {
return err
}
_, err = file.Write(jsn)
if err != nil {
return err
}
}
return nil
}
func (r *RecIO) EditRecordFlagsInFile(fpath string, idx int, rec recordint.Flag) error {
// FIXME: implement
// open file "not as append"
// scan to the correct line
return nil
}
func encodeV1Record(rec record.V1) ([]byte, error) {
jsn, err := json.Marshal(rec)
if err != nil {
return nil, fmt.Errorf("failed to encode json: %w", err)
}
return append(jsn, []byte("\n")...), nil
}

@ -0,0 +1 @@
package recload

@ -0,0 +1,88 @@
package record
type Legacy struct {
// core
CmdLine string `json:"cmdLine"`
ExitCode int `json:"exitCode"`
Shell string `json:"shell"`
Uname string `json:"uname"`
SessionID string `json:"sessionId"`
RecordID string `json:"recordId"`
// posix
Home string `json:"home"`
Lang string `json:"lang"`
LcAll string `json:"lcAll"`
Login string `json:"login"`
Pwd string `json:"pwd"`
PwdAfter string `json:"pwdAfter"`
ShellEnv string `json:"shellEnv"`
Term string `json:"term"`
// non-posix"`
RealPwd string `json:"realPwd"`
RealPwdAfter string `json:"realPwdAfter"`
Pid int `json:"pid"`
SessionPID int `json:"sessionPid"`
Host string `json:"host"`
Hosttype string `json:"hosttype"`
Ostype string `json:"ostype"`
Machtype string `json:"machtype"`
Shlvl int `json:"shlvl"`
// before after
TimezoneBefore string `json:"timezoneBefore"`
TimezoneAfter string `json:"timezoneAfter"`
RealtimeBefore float64 `json:"realtimeBefore"`
RealtimeAfter float64 `json:"realtimeAfter"`
RealtimeBeforeLocal float64 `json:"realtimeBeforeLocal"`
RealtimeAfterLocal float64 `json:"realtimeAfterLocal"`
RealtimeDuration float64 `json:"realtimeDuration"`
RealtimeSinceSessionStart float64 `json:"realtimeSinceSessionStart"`
RealtimeSinceBoot float64 `json:"realtimeSinceBoot"`
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"`
OsReleaseVersionID string `json:"osReleaseVersionId"`
OsReleaseIDLike string `json:"osReleaseIdLike"`
OsReleaseName string `json:"osReleaseName"`
OsReleasePrettyName string `json:"osReleasePrettyName"`
ReshUUID string `json:"reshUuid"`
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"`
RecallLastCmdLine string `json:"recallLastCmdLine"`
// recall command
RecallPrefix string `json:"recallPrefix,omitempty"`
// added by sanitizatizer
Sanitized bool `json:"sanitized,omitempty"`
CmdLength int `json:"cmdLength,omitempty"`
// fields that are string here and int in older resh verisons
Cols interface{} `json:"cols"`
Lines interface{} `json:"lines"`
}

@ -0,0 +1,2 @@
// Package record provides record types that are used in resh history files
package record

@ -0,0 +1,58 @@
package record
type V1 struct {
// flags
// deleted, favorite
// FIXME: is this the best way? .. what about string, separate fields, or something similar
Flags int `json:"flags"`
DeviceID string `json:"deviceID"`
SessionID string `json:"sessionID"`
// can we have a shorter uuid for record
RecordID string `json:"recordID"`
// cmdline, exitcode
CmdLine string `json:"cmdLine"`
ExitCode int `json:"exitCode"`
// paths
Home string `json:"home"`
Pwd string `json:"pwd"`
RealPwd string `json:"realPwd"`
// hostname + lognem (not sure if we actually need logname)
Logname string `json:"logname"`
Hostname string `json:"hostname"`
// git info
// origin is the most important
GitOriginRemote string `json:"gitOriginRemote"`
// maybe branch could be useful - e.g. in monorepo ??
GitBranch string `json:"gitBranch"`
// what is this for ??
// session watching needs this
// but I'm not sure if we need to save it
// records belong to sessions
// PID int `json:"pid"`
// needed for tracking of sessions but I think it shouldn't be part of V1
SessionPID int `json:"sessionPID"`
// needed to because records are merged with parts with same "SessionID + Shlvl"
// I don't think we need to save it
Shlvl int `json:"shlvl"`
// time (before), duration of command
Time float64 `json:"time"`
Duration float64 `json:"duration"`
// these look like internal stuff
// records come in two parts (collect and postcollect)
PartOne bool `json:"partOne,omitempty"` // false => part two
PartsNotMerged bool `json:"partsNotMerged,omitempty"`
// special flag -> not an actual record but an session end
// TODO: this shouldn't be part of serializable V1 record
SessionExit bool `json:"sessionExit,omitempty"`
}

@ -0,0 +1,21 @@
package recordint
import "github.com/curusarn/resh/internal/record"
type Collect struct {
// record merging
SessionID string
Shlvl int
// session watching
SessionPID int
Rec record.V1
}
type Postcollect struct {
// record merging
SessionID string
Shlvl int
// session watching
SessionPID int
}

@ -0,0 +1,51 @@
package recordint
import (
"github.com/curusarn/resh/internal/record"
"github.com/curusarn/resh/internal/recutil"
)
// TODO: This all seems excessive
// TODO: V1 should be converted directly to SearchApp record
// EnrichedRecord - record enriched with additional data
type Enriched struct {
// TODO: think about if it really makes sense to have this based on V1
record.V1
// TODO: drop some/all of this
// enriching fields - added "later"
Command string `json:"command"`
FirstWord string `json:"firstWord"`
Invalid bool `json:"invalid"`
SeqSessionID uint64 `json:"seqSessionId"`
LastRecordOfSession bool `json:"lastRecordOfSession"`
DebugThisRecord bool `json:"debugThisRecord"`
Errors []string `json:"errors"`
// SeqSessionID uint64 `json:"seqSessionId,omitempty"`
}
// Enriched - returns enriched record
func NewEnrichedFromV1(r *record.V1) Enriched {
rec := Enriched{Record: r}
// normlize git remote
rec.GitOriginRemote = NormalizeGitRemote(rec.GitOriginRemote)
rec.GitOriginRemoteAfter = NormalizeGitRemote(rec.GitOriginRemoteAfter)
// Get command/first word from commandline
var err error
err = recutil.Validate(r)
if err != nil {
rec.Errors = append(rec.Errors, "Validate error:"+err.Error())
// rec, _ := record.ToString()
// sugar.Println("Invalid command:", rec)
rec.Invalid = true
}
rec.Command, rec.FirstWord, err = GetCommandAndFirstWord(r.CmdLine)
if err != nil {
rec.Errors = append(rec.Errors, "GetCommandAndFirstWord error:"+err.Error())
// rec, _ := record.ToString()
// sugar.Println("Invalid command:", rec)
rec.Invalid = true // should this be really invalid ?
}
return rec
}

@ -0,0 +1,9 @@
package recordint
type Flag struct {
deviceID string
recordID string
flagDeleted bool
flagFavourite bool
}

@ -0,0 +1,9 @@
package recordint
import "github.com/curusarn/resh/internal/record"
// Indexed record allows us to find records in history file in order to edit them
type Indexed struct {
Rec record.V1
Idx int
}

@ -0,0 +1,2 @@
// Package recordint provides internal record types that are passed between resh components
package recordint

@ -0,0 +1,40 @@
package recordint
// SearchApp record used for sending records to RESH-CLI
type SearchApp struct {
IsRaw bool
SessionID string
DeviceID string
CmdLine string
Host string
Pwd string
Home string // helps us to collapse /home/user to tilde
GitOriginRemote string
ExitCode int
Time float64
}
// NewCliRecordFromCmdLine
func NewSearchAppFromCmdLine(cmdLine string) SearchApp {
return SearchApp{
IsRaw: true,
CmdLine: cmdLine,
}
}
// NewCliRecord from EnrichedRecord
func NewSearchApp(r *Enriched) SearchApp {
return SearchApp{
IsRaw: false,
SessionID: r.SessionID,
CmdLine: r.CmdLine,
Host: r.Hostname,
Pwd: r.Pwd,
Home: r.Home,
GitOriginRemote: r.GitOriginRemote,
ExitCode: r.ExitCode,
Time: r.Time,
}
}

@ -3,16 +3,13 @@ package records
import ( import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"github.com/curusarn/resh/internal/histlist" "github.com/curusarn/resh/internal/histlist"
"github.com/mattn/go-shellwords"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -134,61 +131,6 @@ type FallbackRecord struct {
Lines int `json:"lines"` // notice the int type Lines int `json:"lines"` // notice the int type
} }
// SlimRecord used for recalling because unmarshalling record w/ 50+ fields is too slow
type SlimRecord struct {
SessionID string `json:"sessionId"`
RecallHistno int `json:"recallHistno,omitempty"`
RecallPrefix string `json:"recallPrefix,omitempty"`
// extra recall - we might use these in the future
// Pwd string `json:"pwd"`
// RealPwd string `json:"realPwd"`
// GitDir string `json:"gitDir"`
// GitRealDir string `json:"gitRealDir"`
// GitOriginRemote string `json:"gitOriginRemote"`
}
// CliRecord used for sending records to RESH-CLI
type CliRecord struct {
IsRaw bool `json:"isRaw"`
SessionID string `json:"sessionId"`
CmdLine string `json:"cmdLine"`
Host string `json:"host"`
Pwd string `json:"pwd"`
Home string `json:"home"` // helps us to collapse /home/user to tilde
GitOriginRemote string `json:"gitOriginRemote"`
ExitCode int `json:"exitCode"`
RealtimeBefore float64 `json:"realtimeBefore"`
// RealtimeAfter float64 `json:"realtimeAfter"`
// RealtimeDuration float64 `json:"realtimeDuration"`
}
// NewCliRecordFromCmdLine from EnrichedRecord
func NewCliRecordFromCmdLine(cmdLine string) CliRecord {
return CliRecord{
IsRaw: true,
CmdLine: cmdLine,
}
}
// NewCliRecord from EnrichedRecord
func NewCliRecord(r EnrichedRecord) CliRecord {
return CliRecord{
IsRaw: false,
SessionID: r.SessionID,
CmdLine: r.CmdLine,
Host: r.Host,
Pwd: r.Pwd,
Home: r.Home,
GitOriginRemote: r.GitOriginRemote,
ExitCode: r.ExitCode,
RealtimeBefore: r.RealtimeBefore,
}
}
// Convert from FallbackRecord to Record // Convert from FallbackRecord to Record
func Convert(r *FallbackRecord) Record { func Convert(r *FallbackRecord) Record {
return Record{ return Record{
@ -208,308 +150,6 @@ func (r EnrichedRecord) ToString() (string, error) {
return string(jsonRec), nil return string(jsonRec), nil
} }
// Enriched - returnd enriched record
func Enriched(r Record) EnrichedRecord {
record := EnrichedRecord{Record: r}
// normlize git remote
record.GitOriginRemote = NormalizeGitRemote(record.GitOriginRemote)
record.GitOriginRemoteAfter = NormalizeGitRemote(record.GitOriginRemoteAfter)
// Get command/first word from commandline
var err error
err = r.Validate()
if err != nil {
record.Errors = append(record.Errors, "Validate error:"+err.Error())
// rec, _ := record.ToString()
// sugar.Println("Invalid command:", rec)
record.Invalid = true
}
record.Command, record.FirstWord, err = GetCommandAndFirstWord(r.CmdLine)
if err != nil {
record.Errors = append(record.Errors, "GetCommandAndFirstWord error:"+err.Error())
// rec, _ := record.ToString()
// sugar.Println("Invalid command:", rec)
record.Invalid = true // should this be really invalid ?
}
return record
}
// 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)
}
if r.RecordID != r2.RecordID {
return errors.New("Records to merge do not have the same ID - r1:" + r.RecordID + " r2:" + r2.RecordID)
}
// 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 == "" {
return errors.New("There is no CmdLine")
}
if r.RealtimeBefore == 0 || r.RealtimeAfter == 0 {
return errors.New("There is no Time")
}
if r.RealtimeBeforeLocal == 0 || r.RealtimeAfterLocal == 0 {
return errors.New("There is no Local Time")
}
if r.RealPwd == "" || r.RealPwdAfter == "" {
return errors.New("There is no Real Pwd")
}
if r.Pwd == "" || r.PwdAfter == "" {
return errors.New("There is no Pwd")
}
// TimezoneBefore
// TimezoneAfter
// RealtimeDuration
// RealtimeSinceSessionStart - TODO: add later
// RealtimeSinceBoot - TODO: add later
// device extras
// Host
// Hosttype
// Ostype
// Machtype
// OsReleaseID
// OsReleaseVersionID
// OsReleaseIDLike
// OsReleaseName
// OsReleasePrettyName
// session extras
// Term
// Shlvl
// static info
// Lang
// LcAll
// meta
// ReshUUID
// ReshVersion
// ReshRevision
// added by sanitizatizer
// Sanitized
// CmdLength
return nil
}
// SetCmdLine sets cmdLine and related members
func (r *EnrichedRecord) SetCmdLine(cmdLine string) {
r.CmdLine = cmdLine
r.CmdLength = len(cmdLine)
r.ExitCode = 0
var err error
r.Command, r.FirstWord, err = GetCommandAndFirstWord(cmdLine)
if err != nil {
r.Errors = append(r.Errors, "GetCommandAndFirstWord error:"+err.Error())
// sugar.Println("Invalid command:", r.CmdLine)
r.Invalid = true
}
}
// Stripped returns record stripped of all info that is not available during prediction
func Stripped(r EnrichedRecord) EnrichedRecord {
// clear the cmd itself
r.SetCmdLine("")
// replace after info with before info
r.PwdAfter = r.Pwd
r.RealPwdAfter = r.RealPwd
r.TimezoneAfter = r.TimezoneBefore
r.RealtimeAfter = r.RealtimeBefore
r.RealtimeAfterLocal = r.RealtimeBeforeLocal
// clear some more stuff
r.RealtimeDuration = 0
r.LastRecordOfSession = false
return r
}
// GetCommandAndFirstWord func
func GetCommandAndFirstWord(cmdLine string) (string, string, error) {
args, err := shellwords.Parse(cmdLine)
if err != nil {
// Println("shellwords Error:", err, " (cmdLine: <", cmdLine, "> )")
return "", "", err
}
if len(args) == 0 {
return "", "", nil
}
i := 0
for true {
// commands in shell sometimes look like this `variable=something command argument otherArgument --option`
// to get the command we skip over tokens that contain '='
if strings.ContainsRune(args[i], '=') && len(args) > i+1 {
i++
continue
}
return args[i], args[0], nil
}
return "ERROR", "ERROR", errors.New("failed to retrieve first word of command")
}
// NormalizeGitRemote func
func NormalizeGitRemote(gitRemote string) string {
if strings.HasSuffix(gitRemote, ".git") {
return gitRemote[:len(gitRemote)-4]
}
return gitRemote
}
// DistParams is used to supply params to Enrichedrecords.DistanceTo()
type DistParams struct {
ExitCode float64
MachineID float64
SessionID float64
Login float64
Shell float64
Pwd float64
RealPwd float64
Git float64
Time float64
}
// DistanceTo another record
func (r *EnrichedRecord) DistanceTo(r2 EnrichedRecord, p DistParams) float64 {
var dist float64
dist = 0
// lev distance or something? TODO later
// CmdLine
// exit code
if r.ExitCode != r2.ExitCode {
if r.ExitCode == 0 || r2.ExitCode == 0 {
// one success + one error -> 1
dist += 1 * p.ExitCode
} else {
// two different errors
dist += 0.5 * p.ExitCode
}
}
// machine/device
if r.MachineID != r2.MachineID {
dist += 1 * p.MachineID
}
// Uname
// session
if r.SessionID != r2.SessionID {
dist += 1 * p.SessionID
}
// Pid - add because of nested shells?
// SessionPid
// user
if r.Login != r2.Login {
dist += 1 * p.Login
}
// Home
// shell
if r.Shell != r2.Shell {
dist += 1 * p.Shell
}
// ShellEnv
// pwd
if r.Pwd != r2.Pwd {
// TODO: compare using hierarchy
// TODO: make more important
dist += 1 * p.Pwd
}
if r.RealPwd != r2.RealPwd {
// TODO: -||-
dist += 1 * p.RealPwd
}
// PwdAfter
// RealPwdAfter
// git
if r.GitDir != r2.GitDir {
dist += 1 * p.Git
}
if r.GitRealDir != r2.GitRealDir {
dist += 1 * p.Git
}
if r.GitOriginRemote != r2.GitOriginRemote {
dist += 1 * p.Git
}
// time
// this can actually get negative for differences of less than one second which is fine
// distance grows by 1 with every order
distTime := math.Log10(math.Abs(r.RealtimeBefore-r2.RealtimeBefore)) * p.Time
if math.IsNaN(distTime) == false && math.IsInf(distTime, 0) == false {
dist += distTime
}
// RealtimeBeforeLocal
// RealtimeAfter
// RealtimeAfterLocal
// TimezoneBefore
// TimezoneAfter
// RealtimeDuration
// RealtimeSinceSessionStart - TODO: add later
// RealtimeSinceBoot - TODO: add later
// device extras
// Host
// Hosttype
// Ostype
// Machtype
// OsReleaseID
// OsReleaseVersionID
// OsReleaseIDLike
// OsReleaseName
// OsReleasePrettyName
// session extras
// Term
// Shlvl
// static info
// Lang
// LcAll
// meta
// ReshUUID
// ReshVersion
// ReshRevision
// added by sanitizatizer
// Sanitized
// CmdLength
return dist
}
// LoadFromFile loads records from 'fname' file // LoadFromFile loads records from 'fname' file
func LoadFromFile(sugar *zap.SugaredLogger, fname string) []Record { func LoadFromFile(sugar *zap.SugaredLogger, fname string) []Record {
const allowedErrors = 3 const allowedErrors = 3

@ -77,75 +77,9 @@ func TestValidate(t *testing.T) {
} }
} }
func TestSetCmdLine(t *testing.T) {
record := EnrichedRecord{}
cmdline := "cmd arg1 arg2"
record.SetCmdLine(cmdline)
if record.CmdLine != cmdline || record.Command != "cmd" || record.FirstWord != "cmd" {
t.Error()
}
}
func TestStripped(t *testing.T) {
for _, rec := range GetTestEnrichedRecords() {
stripped := Stripped(rec)
// there should be no cmdline
if stripped.CmdLine != "" ||
stripped.FirstWord != "" ||
stripped.Command != "" {
t.Error("Stripped() returned record w/ info about CmdLine, Command OR FirstWord")
}
// *after* fields should be overwritten by *before* fields
if stripped.PwdAfter != stripped.Pwd ||
stripped.RealPwdAfter != stripped.RealPwd ||
stripped.TimezoneAfter != stripped.TimezoneBefore ||
stripped.RealtimeAfter != stripped.RealtimeBefore ||
stripped.RealtimeAfterLocal != stripped.RealtimeBeforeLocal {
t.Error("Stripped() returned record w/ different *after* and *before* values - *after* fields should be overwritten by *before* fields")
}
// there should be no information about duration and session end
if stripped.RealtimeDuration != 0 ||
stripped.LastRecordOfSession != false {
t.Error("Stripped() returned record with too much information")
}
}
}
func TestGetCommandAndFirstWord(t *testing.T) { func TestGetCommandAndFirstWord(t *testing.T) {
cmd, stWord, err := GetCommandAndFirstWord("cmd arg1 arg2") cmd, stWord, err := GetCommandAndFirstWord("cmd arg1 arg2")
if err != nil || cmd != "cmd" || stWord != "cmd" { if err != nil || cmd != "cmd" || stWord != "cmd" {
t.Error("GetCommandAndFirstWord() returned wrong Command OR FirstWord") t.Error("GetCommandAndFirstWord() returned wrong Command OR FirstWord")
} }
} }
func TestDistanceTo(t *testing.T) {
paramsFull := DistParams{
ExitCode: 1,
MachineID: 1,
SessionID: 1,
Login: 1,
Shell: 1,
Pwd: 1,
RealPwd: 1,
Git: 1,
Time: 1,
}
paramsZero := DistParams{}
var prevRec EnrichedRecord
for _, rec := range GetTestEnrichedRecords() {
dist := rec.DistanceTo(rec, paramsFull)
if dist != 0 {
t.Error("DistanceTo() itself should be always 0")
}
dist = rec.DistanceTo(prevRec, paramsFull)
if dist == 0 {
t.Error("DistanceTo() between two test records shouldn't be 0")
}
dist = rec.DistanceTo(prevRec, paramsZero)
if dist != 0 {
t.Error("DistanceTo() should be 0 when DistParams is all zeros")
}
prevRec = rec
}
}

@ -0,0 +1,94 @@
package recutil
import (
"errors"
"net/url"
"strings"
"github.com/curusarn/resh/internal/record"
"github.com/mattn/go-shellwords"
giturls "github.com/whilp/git-urls"
)
// NormalizeGitRemote helper
func NormalizeGitRemote(gitRemote string) string {
if strings.HasSuffix(gitRemote, ".git") {
gitRemote = gitRemote[:len(gitRemote)-4]
}
parsedURL, err := giturls.Parse(gitRemote)
if err != nil {
// TODO: log this error
return gitRemote
}
if parsedURL.User == nil || parsedURL.User.Username() == "" {
parsedURL.User = url.User("git")
}
// TODO: figure out what scheme we want
parsedURL.Scheme = "git+ssh"
return parsedURL.String()
}
// Validate returns error if the record is invalid
func Validate(r *record.V1) error {
if r.CmdLine == "" {
return errors.New("There is no CmdLine")
}
if r.RealtimeBefore == 0 || r.RealtimeAfter == 0 {
return errors.New("There is no Time")
}
if r.RealtimeBeforeLocal == 0 || r.RealtimeAfterLocal == 0 {
return errors.New("There is no Local Time")
}
if r.RealPwd == "" || r.RealPwdAfter == "" {
return errors.New("There is no Real Pwd")
}
if r.Pwd == "" || r.PwdAfter == "" {
return errors.New("There is no Pwd")
}
return nil
}
// Merge two records (part1 - collect + part2 - postcollect)
func Merge(r1 *record.V1, r2 *record.V1) error {
if r1.PartOne == false || r2.PartOne {
return errors.New("Expected part1 and part2 of the same record - usage: Merge(part1, part2)")
}
if r1.SessionID != r2.SessionID {
return errors.New("Records to merge are not from the same sesion - r1:" + r1.SessionID + " r2:" + r2.SessionID)
}
if r1.CmdLine != r2.CmdLine {
return errors.New("Records to merge are not parts of the same records - r1:" + r1.CmdLine + " r2:" + r2.CmdLine)
}
if r1.RecordID != r2.RecordID {
return errors.New("Records to merge do not have the same ID - r1:" + r1.RecordID + " r2:" + r2.RecordID)
}
r1.ExitCode = r2.ExitCode
r1.Duration = r2.Duration
r1.PartsMerged = true
r1.PartOne = false
return nil
}
// GetCommandAndFirstWord func
func GetCommandAndFirstWord(cmdLine string) (string, string, error) {
args, err := shellwords.Parse(cmdLine)
if err != nil {
// Println("shellwords Error:", err, " (cmdLine: <", cmdLine, "> )")
return "", "", err
}
if len(args) == 0 {
return "", "", nil
}
i := 0
for true {
// commands in shell sometimes look like this `variable=something command argument otherArgument --option`
// to get the command we skip over tokens that contain '='
if strings.ContainsRune(args[i], '=') && len(args) > i+1 {
i++
continue
}
return args[i], args[0], nil
}
return "ERROR", "ERROR", errors.New("failed to retrieve first word of command")
}

@ -29,19 +29,7 @@ __resh_collect() {
local __RESH_GIT_REMOTE; __RESH_GIT_REMOTE="$(git remote get-url origin 2>/dev/null)" local __RESH_GIT_REMOTE; __RESH_GIT_REMOTE="$(git remote get-url origin 2>/dev/null)"
local __RESH_GIT_REMOTE_EXIT_CODE=$? local __RESH_GIT_REMOTE_EXIT_CODE=$?
if [ -n "${ZSH_VERSION-}" ]; then local __RESH_PID="$$" # current pid
# assume Zsh
local __RESH_PID="$$" # current pid
elif [ -n "${BASH_VERSION-}" ]; then
# assume Bash
if [ "${BASH_VERSINFO[0]}" -ge "4" ]; then
# $BASHPID is only available in bash4+
# $$ is fairly similar so it should not be an issue
local __RESH_PID="$BASHPID" # current pid
else
local __RESH_PID="$$" # current pid
fi
fi
# time # time
local __RESH_TZ_BEFORE; __RESH_TZ_BEFORE=$(date +%z) local __RESH_TZ_BEFORE; __RESH_TZ_BEFORE=$(date +%z)
# __RESH_RT_BEFORE="$EPOCHREALTIME" # __RESH_RT_BEFORE="$EPOCHREALTIME"

@ -94,6 +94,19 @@ fi
# read -r x # read -r x
echo echo
echo "Backing up previous installation"
# TODO: ~/.resh -> XDG_DATA/resh/rollback/
# TODO: ~/XDG_DATA/resh/history.reshjson -> XDG_DATA/resh/rollback/
# TODO: what about legacy history locations
# TODO: ~/XDG_DATA/resh/log.json -> XDG_DATA/resh/rollback/
echo "Cleaning up installation directory ..."
rm ~/.resh/bin/* 2>/dev/null ||:
rm ~/.resh/* 2>/dev/null 2>/dev/null ||:
# TODO: put this behind version condition
# backward compatibility: We have a new location for resh history file
[ ! -f ~/.resh/history.json ] || mv ~/.resh/history.json ~/.resh_history.json
echo "Creating directories ..." echo "Creating directories ..."
mkdir_if_not_exists() { mkdir_if_not_exists() {
@ -122,15 +135,9 @@ bin/resh-control completion zsh > ~/.resh/zsh_completion.d/_reshctl
echo "Copying more files ..." echo "Copying more files ..."
cp -f scripts/uuid.sh ~/.resh/bin/resh-uuid cp -f scripts/uuid.sh ~/.resh/bin/resh-uuid
rm ~/.resh/bin/resh-* ||:
cp -f bin/resh-{daemon,control,collect,postcollect,session-init,config} ~/.resh/bin/ cp -f bin/resh-{daemon,control,collect,postcollect,session-init,config} ~/.resh/bin/
cp -f scripts/resh-evaluate-plot.py ~/.resh/bin/
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
echo "Checking config file ..." echo "Creating/updating config file ..."
./bin/resh-config-setup ./bin/resh-config-setup
echo "Finishing up ..." echo "Finishing up ..."

File diff suppressed because it is too large Load Diff

@ -47,24 +47,9 @@ __RESH_TERM="$TERM"
# non-posix # non-posix
__RESH_RT_SESSION=$(__resh_get_epochrealtime) __RESH_RT_SESSION=$(__resh_get_epochrealtime)
__RESH_OSTYPE="$OSTYPE" __RESH_OSTYPE="$OSTYPE"
__RESH_MACHTYPE="$MACHTYPE" __RESH_MACHTYPE="$MACHTYPE"
if [ $__RESH_LINUX -eq 1 ]; then
__RESH_OS_RELEASE_ID=$(. /etc/os-release; echo "$ID")
__RESH_OS_RELEASE_VERSION_ID=$(. /etc/os-release; echo "$VERSION_ID")
__RESH_OS_RELEASE_ID_LIKE=$(. /etc/os-release; echo "$ID_LIKE")
__RESH_OS_RELEASE_NAME=$(. /etc/os-release; echo "$NAME")
__RESH_OS_RELEASE_PRETTY_NAME=$(. /etc/os-release; echo "$PRETTY_NAME")
__RESH_RT_SESS_SINCE_BOOT=$(cut -d' ' -f1 /proc/uptime)
elif [ $__RESH_MACOS -eq 1 ]; then
__RESH_OS_RELEASE_ID="macos"
__RESH_OS_RELEASE_VERSION_ID=$(sw_vers -productVersion 2>/dev/null)
__RESH_OS_RELEASE_NAME="macOS"
__RESH_OS_RELEASE_PRETTY_NAME="Mac OS X"
__RESH_RT_SESS_SINCE_BOOT=$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//g')
fi
# shellcheck disable=2155 # shellcheck disable=2155
export __RESH_VERSION=$(resh-collect -version) export __RESH_VERSION=$(resh-collect -version)
# shellcheck disable=2155 # shellcheck disable=2155

@ -68,18 +68,10 @@ __resh_bash_completion_init() {
# skip completion init if they are not # skip completion init if they are not
_get_comp_words_by_ref >/dev/null 2>/dev/null _get_comp_words_by_ref >/dev/null 2>/dev/null
[[ $? == 127 ]] && return [[ $? == 127 ]] && return
local bash_completion_dir=~/.resh/bash_completion.d . ~/.resh/bash_completion.d/_reshctl
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
[[ -f "$i" && -r "$i" ]] && . "$i"
done
fi
} }
// TODO: redo this
__resh_zsh_completion_init() { __resh_zsh_completion_init() {
# NOTE: this is hacky - each completion needs to be added individually # NOTE: this is hacky - each completion needs to be added individually
# TODO: fix later # TODO: fix later
@ -137,7 +129,6 @@ __resh_session_init() {
fi fi
fi fi
if [ "$__RESH_VERSION" = "$(resh-session-init -version)" ] && [ "$__RESH_REVISION" = "$(resh-session-init -revision)" ]; then if [ "$__RESH_VERSION" = "$(resh-session-init -version)" ] && [ "$__RESH_REVISION" = "$(resh-session-init -revision)" ]; then
local fpath_last_run="$__RESH_XDG_CACHE_HOME/session_init_last_run_out.txt"
resh-session-init -requireVersion "$__RESH_VERSION" \ resh-session-init -requireVersion "$__RESH_VERSION" \
-requireRevision "$__RESH_REVISION" \ -requireRevision "$__RESH_REVISION" \
-shell "$__RESH_SHELL" \ -shell "$__RESH_SHELL" \
@ -166,26 +157,6 @@ __resh_session_init() {
-osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \ -osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \
-osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \ -osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \
-osReleaseName "$__RESH_OS_RELEASE_NAME" \ -osReleaseName "$__RESH_OS_RELEASE_NAME" \
-osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \ -osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME"
>| "$fpath_last_run" 2>&1 || echo "resh-session-init ERROR: $(head -n 1 $fpath_last_run)"
fi fi
} }
__resh_set_xdg_home_paths() {
if [ -z "${XDG_CACHE_HOME-}" ]; then
__RESH_XDG_CACHE_HOME="$HOME/.cache/resh"
else
__RESH_XDG_CACHE_HOME="$XDG_CACHE_HOME/resh"
fi
mkdir -p "$__RESH_XDG_CACHE_HOME" >/dev/null 2>/dev/null
export __RESH_XDG_CACHE_HOME
if [ -z "${XDG_DATA_HOME-}" ]; then
__RESH_XDG_DATA_HOME="$HOME/.local/share/resh"
else
__RESH_XDG_DATA_HOME="$XDG_DATA_HOME/resh"
fi
mkdir -p "$__RESH_XDG_DATA_HOME" >/dev/null 2>/dev/null
export __RESH_XDG_DATA_HOME
}

Loading…
Cancel
Save