Skip to content

Commit 843ff04

Browse files
committed
feat: allow replacements in startup command
1 parent 6e43f37 commit 843ff04

File tree

9 files changed

+118
-9
lines changed

9 files changed

+118
-9
lines changed

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=

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)
@@ -60,7 +62,7 @@ func App(version string) cli.App {
6062
// core dependencies
6163
ls := ls.NewLs(config, shell)
6264
lister := lister.NewLister(config, home, tmux, zoxide, tmuxinator)
63-
startup := startup.NewStartup(config, lister, tmux, home)
65+
startup := startup.NewStartup(config, lister, tmux, home, replacer)
6466
namer := namer.NewNamer(path, git, home)
6567
connector := connector.NewConnector(config, dir, home, lister, namer, startup, tmux, zoxide, tmuxinator)
6668
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

startup/startup.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/joshmedeski/sesh/v2/home"
77
"github.com/joshmedeski/sesh/v2/lister"
88
"github.com/joshmedeski/sesh/v2/model"
9+
"github.com/joshmedeski/sesh/v2/replacer"
910
"github.com/joshmedeski/sesh/v2/tmux"
1011
)
1112

@@ -14,14 +15,17 @@ type Startup interface {
1415
}
1516

1617
type RealStartup struct {
17-
lister lister.Lister
18-
tmux tmux.Tmux
19-
config model.Config
20-
home home.Home
18+
lister lister.Lister
19+
tmux tmux.Tmux
20+
config model.Config
21+
home home.Home
22+
replacer replacer.Replacer
2123
}
2224

23-
func NewStartup(config model.Config, lister lister.Lister, tmux tmux.Tmux, home home.Home) Startup {
24-
return &RealStartup{lister, tmux, config, home}
25+
func NewStartup(
26+
config model.Config, lister lister.Lister, tmux tmux.Tmux, home home.Home, replacer replacer.Replacer,
27+
) Startup {
28+
return &RealStartup{lister, tmux, config, home, replacer}
2529
}
2630

2731
func (s *RealStartup) Exec(session model.SeshSession) (string, error) {

0 commit comments

Comments
 (0)