Skip to content

Commit ba828a3

Browse files
Update fork for latest release of sesh and merge main to strict-toml-marshalling
2 parents 7778afe + ffed054 commit ba828a3

File tree

11 files changed

+157
-24
lines changed

11 files changed

+157
-24
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,17 @@ startup_command = "nvim tmux.conf"
295295
preview_command = "bat --color=always ~/c/dotfiles/.config/tmux/tmux.conf"
296296
```
297297

298+
### Path substitution
299+
If you want to use the path of the selected session in your startup or preview command, you can use the `{}` placeholder.
300+
This will be replaced with the session's path when the command is run.
301+
302+
An example of this in use is the following, where the `tmuxinator` default_project uses the path as key/value pair using [ERB syntax](https://github.com/tmuxinator/tmuxinator?tab=readme-ov-file#erb):
303+
```toml
304+
[default_session]
305+
startup_command = "tmuxinator start default_project path={}"
306+
preview_command = "eza --all --git --icons --color=always {}"
307+
```
308+
298309
### Multiple windows
299310

300311
If you want your session to have multiple windows you can define windows in your configuration. You can then use these window layouts in your sessions. These windows can be reused as many times as you want and you can add as many windows to each session as you want.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.21
44

55
require (
66
github.com/pelletier/go-toml/v2 v2.2.1
7+
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745
78
github.com/stretchr/testify v1.9.0
89
github.com/urfave/cli/v2 v2.27.1
910
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
77
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
8+
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745 h1:Vpr4VgAizEgEZsaMohpw6JYDP+i9Of9dmdY4ufNP6HI=
9+
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
810
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
911
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1012
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=

main.go

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,31 @@ func init() {
3232
var err error
3333
fileOnly := false
3434

35-
if f, err = createLoggerFile(); err != nil {
36-
slog.Error("Unable to create logger file", "error", err)
37-
os.Exit(1)
35+
// TempDir returns the default directory to use for temporary files.
36+
//
37+
// On Unix systems, it returns $TMPDIR if non-empty, else /tmp.
38+
// On Windows, it uses GetTempPath, returning the first non-empty
39+
// value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory.
40+
// On Plan 9, it returns /tmp.
41+
// It does not guarantee the user can write to the directory;
42+
userTempDir := os.TempDir()
43+
if f, err = createLoggerFile(userTempDir); err != nil {
44+
if !strings.Contains(err.Error(), "permission denied") {
45+
slog.Error("Unable to create logger file", "error", err)
46+
os.Exit(1)
47+
}
48+
49+
// If we can't write to the temp dir, try the user home dir
50+
userTempDir, err = os.UserHomeDir()
51+
if err != nil {
52+
slog.Error("Unable to get user home directory", "error", err)
53+
os.Exit(1)
54+
}
55+
56+
if f, err = createLoggerFile(userTempDir); err != nil {
57+
slog.Error("Unable to create logger file in user home directory", "error", err)
58+
os.Exit(1)
59+
}
3860
}
3961

4062
env := os.Getenv("ENV")
@@ -62,24 +84,15 @@ func init() {
6284
slog.SetDefault(slog.New(loggerHandler))
6385
}
6486

65-
func createLoggerFile() (*os.File, error) {
87+
func createLoggerFile(userTempDir string) (*os.File, error) {
6688
now := time.Now()
6789
date := fmt.Sprintf("%s.log", now.Format("2006-01-02"))
6890

69-
// TempDir returns the default directory to use for temporary files.
70-
//
71-
// On Unix systems, it returns $TMPDIR if non-empty, else /tmp.
72-
// On Windows, it uses GetTempPath, returning the first non-empty
73-
// value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory.
74-
// On Plan 9, it returns /tmp.
75-
userTempDir := os.TempDir()
76-
slog.Debug("createLoggerFile:", "userTempDir", userTempDir)
77-
78-
if err := os.MkdirAll(path.Join(userTempDir, "sesh"), 0755); err != nil {
91+
if err := os.MkdirAll(path.Join(userTempDir, ".seshtmp"), 0755); err != nil {
7992
return nil, err
8093
}
8194

82-
fileFullPath := path.Join(userTempDir, "sesh", date)
95+
fileFullPath := path.Join(userTempDir, ".seshtmp", date)
8396
file, err := os.OpenFile(fileFullPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
8497
if err != nil {
8598
return nil, err

replacer/replacer.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package replacer
2+
3+
import (
4+
ahocorasick "github.com/petar-dambovaliev/aho-corasick"
5+
)
6+
7+
type Replacer interface {
8+
Replace(command string, replacements map[string]string) string
9+
}
10+
11+
type RealReplacer struct{}
12+
13+
func NewReplacer() Replacer {
14+
return &RealReplacer{}
15+
}
16+
17+
func (r *RealReplacer) Replace(command string, replacements map[string]string) string {
18+
dict := make([]string, 0, len(replacements))
19+
replacementArray := make([]string, 0, len(replacements))
20+
for k, v := range replacements {
21+
dict = append(dict, k)
22+
replacementArray = append(replacementArray, v)
23+
}
24+
ac := getAhoCorasick(dict)
25+
replacer := ahocorasick.NewReplacer(ac)
26+
return replacer.ReplaceAll(command, replacementArray)
27+
}
28+
29+
func getAhoCorasick(dictionary []string) ahocorasick.AhoCorasick {
30+
builder := ahocorasick.NewAhoCorasickBuilder(ahocorasick.Opts{
31+
AsciiCaseInsensitive: true,
32+
MatchOnlyWholeWords: true,
33+
MatchKind: ahocorasick.LeftMostFirstMatch,
34+
DFA: true,
35+
})
36+
37+
return builder.Build(dictionary)
38+
}

replacer/replacer_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package replacer
2+
3+
import (
4+
"testing"
5+
)
6+
7+
type testCase struct {
8+
input string
9+
expected string
10+
}
11+
12+
func TestReplace(t *testing.T) {
13+
defaultReplacements := map[string]string{
14+
"{}": "hello",
15+
"~": "/home/test",
16+
}
17+
18+
testCases := map[string]testCase{
19+
"multiple replacements": {
20+
"~/.local/bin/rat {}{}",
21+
"/home/test/.local/bin/rat hellohello",
22+
},
23+
"single replacement": {
24+
"/bin/rat {}",
25+
"/bin/rat hello",
26+
},
27+
"no replacement": {
28+
"/bin/rat",
29+
"/bin/rat",
30+
},
31+
}
32+
33+
for name, test := range testCases {
34+
t.Run(name, func(t *testing.T) {
35+
replacer := NewReplacer()
36+
result := replacer.Replace(test.input, defaultReplacements)
37+
if result != test.expected {
38+
t.Errorf("expected %s, got %s", test.expected, result)
39+
}
40+
})
41+
}
42+
t.Run("", func(t *testing.T) {
43+
})
44+
}

seshcli/seshcli.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/joshmedeski/sesh/v2/oswrap"
2121
"github.com/joshmedeski/sesh/v2/pathwrap"
2222
"github.com/joshmedeski/sesh/v2/previewer"
23+
"github.com/joshmedeski/sesh/v2/replacer"
2324
"github.com/joshmedeski/sesh/v2/runtimewrap"
2425
"github.com/joshmedeski/sesh/v2/shell"
2526
"github.com/joshmedeski/sesh/v2/startup"
@@ -39,6 +40,7 @@ func App(version string) cli.App {
3940
home := home.NewHome(os)
4041
shell := shell.NewShell(exec, home)
4142
json := json.NewJson()
43+
replacer := replacer.NewReplacer()
4244

4345
// resource dependencies
4446
git := git.NewGit(shell)
@@ -63,7 +65,7 @@ func App(version string) cli.App {
6365
// core dependencies
6466
ls := ls.NewLs(config, shell)
6567
lister := lister.NewLister(config, home, tmux, zoxide, tmuxinator)
66-
startup := startup.NewStartup(config, lister, tmux, home)
68+
startup := startup.NewStartup(config, lister, tmux, home, replacer)
6769
namer := namer.NewNamer(path, git, home)
6870
connector := connector.NewConnector(config, dir, home, lister, namer, startup, tmux, zoxide, tmuxinator)
6971
icon := icon.NewIcon(config)

shell/shell_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,14 @@ func TestShellPrepareCmd(t *testing.T) {
5555
assert.Nil(t, err)
5656
assert.Equal(t, []string{"/home/test/.local/bin/rat", "hello"}, cmdParts)
5757
})
58+
59+
// This test case asserts the existing behaviour when the desired replacement is not separated by spaces
60+
t.Run("should not use a partial match", func(t *testing.T) {
61+
mockHome := new(home.MockHome)
62+
shell := &RealShell{home: mockHome}
63+
mockHome.On("ExpandHome", "~/.local/bin/rat").Return("/home/test/.local/bin/rat", nil)
64+
cmdParts, err := shell.PrepareCmd("~/.local/bin/rat localVar={}", map[string]string{"{}": "hello"})
65+
assert.Nil(t, err)
66+
assert.Equal(t, []string{"/home/test/.local/bin/rat", "localVar={}"}, cmdParts)
67+
})
5868
}

startup/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ func configStrategy(s *RealStartup, session model.SeshSession) (string, error) {
1010
}
1111

1212
if exists && config.StartupCommand != "" {
13-
return config.StartupCommand, nil
13+
replacements := map[string]string{
14+
"{}": session.Path,
15+
}
16+
17+
return s.replacer.Replace(config.StartupCommand, replacements), nil
1418
}
1519
return "", nil
1620
}

startup/defaultconfig.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ func defaultConfigStrategy(s *RealStartup, session model.SeshSession) (string, e
99

1010
defaultConfig := s.config.DefaultSessionConfig
1111
if defaultConfig.StartupCommand != "" {
12-
return defaultConfig.StartupCommand, nil
12+
replacements := map[string]string{
13+
"{}": session.Path,
14+
}
15+
16+
return s.replacer.Replace(defaultConfig.StartupCommand, replacements), nil
1317
}
1418

1519
return "", nil

0 commit comments

Comments
 (0)