Skip to content

Commit

Permalink
feat: implement config and startup scripts (#22)
Browse files Browse the repository at this point in the history
Implements parsing of a sesh/sesh.toml config file and creates a pattern for future use.
Add the ability to configure a startup script for a project
  • Loading branch information
joebonneau authored Jan 26, 2024
1 parent db29565 commit b6f0de4
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 30 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ You can customize this however you want, see `man fzf` for more info on the diff
sesh connect (sesh list | zf --height 24)
```

## Configuration

To configure `sesh`, you will need to create `sesh/sesh.toml` with `$XDG_CONFIG_HOME` or `$HOME/.config` as the root directory, depending on whether the former has been set. On MacOS, this will likely be `~/.config/` by default and therefore the configuration filepath would be `~/.config/sesh/sesh.toml`. See the [Go docs](https://cs.opensource.google/go/go/+/go1.21.6:src/os/file.go;l=460) for more information.

An example of the `sesh.toml` file is shown below with all configurable options and their default values:

```toml
default_startup_script = "~/git_repos/dotfiles/bin/sesh/default.sh"

[[startup_scripts]]
session_path = "~/git_repos/sesh"
script_path = "~/git_repos/dotfiles/bin/sesh/sesh.sh"
```

### Startup Scripts

Startup scripts will be ignored if the `--command/-c` flag is passed! Startup scripts will only be run upon session creation!

If a `[[startup_scripts]]` entry is present for a given session path, then the startup script path specified by `script_path` will be run when the session is created. Otherwise, if `default_startup_script` is specified, then the script at that path will be executed. Make sure that the scripts specified are executable, e.g. `chmod +x ~/git_repos/dotfiles/bin/sesh/default.sh`.

## Background (the "t" script)

Sesh is the successor to my popular [t-smart-tmux-session-manager](https://github.com/joshmedeski/t-smart-tmux-session-manager) tmux plugin. After a year of development and over 250 stars, it's clear that people enjoy the idea of a smart session manager. However, I've always felt that the tmux plugin was a bit of a hack. It's a bash script that runs in the background and parses the output of tmux commands. It works, but it's not ideal and isn't flexible enough to support other terminal multiplexers.
Expand Down
4 changes: 3 additions & 1 deletion cmds/choose.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

cli "github.com/urfave/cli/v2"

"github.com/joshmedeski/sesh/config"
"github.com/joshmedeski/sesh/connect"
"github.com/joshmedeski/sesh/session"
)
Expand Down Expand Up @@ -65,7 +66,8 @@ func Choose() *cli.Command {
}
choice := strings.TrimSpace(cmdOutput.String())
// TODO: get choice from Session structs array
return connect.Connect(choice, false, "")
config := config.ParseConfigFile()
return connect.Connect(choice, false, "", &config)
},
}
}
5 changes: 3 additions & 2 deletions cmds/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmds
import (
cli "github.com/urfave/cli/v2"

"github.com/joshmedeski/sesh/config"
"github.com/joshmedeski/sesh/connect"
"github.com/joshmedeski/sesh/git"
)
Expand Down Expand Up @@ -32,8 +33,8 @@ func Clone() *cli.Command {
if err != nil {
return cli.Exit(err, 1)
}

return connect.Connect(c.Path, false, "")
config := config.ParseConfigFile()
return connect.Connect(c.Path, false, "", &config)
},
}
}
4 changes: 3 additions & 1 deletion cmds/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmds
import (
cli "github.com/urfave/cli/v2"

"github.com/joshmedeski/sesh/config"
"github.com/joshmedeski/sesh/connect"
)

Expand Down Expand Up @@ -31,7 +32,8 @@ func Connect() *cli.Command {
if session == "" {
return cli.Exit("No session provided", 0)
}
return connect.Connect(session, alwaysSwitch, command)
config := config.ParseConfigFile()
return connect.Connect(session, alwaysSwitch, command, &config)
},
}
}
62 changes: 62 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package config

import (
"fmt"
"os"
"path"
"path/filepath"
"runtime"

"github.com/pelletier/go-toml/v2"
)

type (
Script struct {
SessionPath string `toml:"session_path"`
ScriptPath string `toml:"script_path"`
}
Config struct {
StartupScripts []Script `toml:"startup_scripts"`
DefaultStartupScript string `toml:"default_startup_script"`
}
)

func getUserConfigDir() (string, error) {
switch runtime.GOOS {
case "darwin":
// typically ~/Library/Application Support, but we want to use ~/.config
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return path.Join(homeDir, ".config"), nil
default:
return os.UserConfigDir()
}
}

func ParseConfigFile() Config {
config := Config{}
configDir, err := getUserConfigDir()
if err != nil {
fmt.Printf(
"Error determining the user config directory: %s\nUsing default config instead",
err,
)
return config
}
configPath := filepath.Join(configDir, "sesh", "sesh.toml")
data, err := os.ReadFile(configPath)
if err != nil {
return config
}
err = toml.Unmarshal(data, &config)
if err != nil {
fmt.Printf(
"Error parsing config file: %s\nUsing default config instead",
err,
)
return config
}
return config
}
18 changes: 8 additions & 10 deletions connect/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@ package connect
import (
"fmt"

"github.com/joshmedeski/sesh/config"
"github.com/joshmedeski/sesh/session"
"github.com/joshmedeski/sesh/tmux"
"github.com/joshmedeski/sesh/zoxide"
)

func Connect(choice string, alwaysSwitch bool, command string) error {
session, err := session.Determine(choice)
func Connect(choice string, alwaysSwitch bool, command string, config *config.Config) error {
session, err := session.Determine(choice, config)
if err != nil {
return fmt.Errorf("unable to connect to %q: %w", choice, err)
}

if err := zoxide.Add(session.Path); err != nil {
if err = zoxide.Add(session.Path); err != nil {
return fmt.Errorf("unable to connect to %q: %w", choice, err)
}

return tmux.Connect(
tmux.TmuxSession{Name: session.Name, Path: session.Path},
alwaysSwitch,
command,
)
return tmux.Connect(tmux.TmuxSession{
Name: session.Name,
Path: session.Path,
}, alwaysSwitch, command, session.Path, config)
}
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ module github.com/joshmedeski/sesh

go 1.21

require github.com/urfave/cli/v2 v2.27.1
require (
github.com/pelletier/go-toml/v2 v2.1.1
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.27.1
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 3 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.27.0 h1:uNs1K8JwTFL84X68j5Fjny6hfANh9nTlJ6dRtZAFAHY=
github.com/urfave/cli/v2 v2.27.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
6 changes: 4 additions & 2 deletions session/determine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package session
import (
"fmt"
"log"

"github.com/joshmedeski/sesh/config"
)

func Determine(choice string) (s Session, err error) {
func Determine(choice string, config *config.Config) (s Session, err error) {
path, err := DeterminePath(choice)
if err != nil {
return s, fmt.Errorf(
Expand All @@ -16,7 +18,7 @@ func Determine(choice string) (s Session, err error) {
}
s.Path = path

name := DetermineName(path)
name := DetermineName(path, config)
if name == "" {
log.Fatal("Couldn't determine the session name", err)
return s, fmt.Errorf(
Expand Down
5 changes: 3 additions & 2 deletions session/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"
"strings"

"github.com/joshmedeski/sesh/config"
"github.com/joshmedeski/sesh/git"
)

Expand Down Expand Up @@ -47,9 +48,9 @@ func nameFromGit(result string) string {
return nameFromGit
}

// TODO: parent directory feature flag detection
func DetermineName(result string) string {
func DetermineName(result string, config *config.Config) string {
name := result
// TODO: parent directory config option detection
pathName := nameFromPath(result)
if pathName != "" {
name = pathName
Expand Down
48 changes: 46 additions & 2 deletions tmux/tmux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package tmux
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"strings"

"github.com/joshmedeski/sesh/config"
"github.com/joshmedeski/sesh/dir"
)

func tmuxCmd(args []string) (string, error) {
Expand Down Expand Up @@ -82,7 +86,38 @@ func NewSession(s TmuxSession) (string, error) {
return out, nil
}

func Connect(s TmuxSession, alwaysSwitch bool, command string) error {
func execStartupScript(name string, scriptPath string) error {
bash, err := exec.LookPath("bash")
if err != nil {
return err
}
cmd := strings.Join(
[]string{bash, "-c", fmt.Sprintf("\"source %s\"", scriptPath)},
" ",
)
err = runPersistentCommand(name, cmd)
if err != nil {
return err
}
return nil
}

func getStartupScript(sessionPath string, config *config.Config) string {
for _, script := range config.StartupScripts {
if dir.FullPath(script.SessionPath) == sessionPath {
return dir.FullPath(script.ScriptPath)
}
}
return ""
}

func Connect(
s TmuxSession,
alwaysSwitch bool,
command string,
sessionPath string,
config *config.Config,
) error {
isSession, _ := IsSession(s.Name)
if !isSession {
_, err := NewSession(s)
Expand All @@ -91,6 +126,16 @@ func Connect(s TmuxSession, alwaysSwitch bool, command string) error {
}
if command != "" {
runPersistentCommand(s.Name, command)
} else if scriptPath := getStartupScript(sessionPath, config); scriptPath != "" {
err := execStartupScript(s.Name, scriptPath)
if err != nil {
log.Fatal(err)
}
} else if config.DefaultStartupScript != "" {
err := execStartupScript(s.Name, config.DefaultStartupScript)
if err != nil {
log.Fatal(err)
}
}
}
isAttached := isAttached()
Expand All @@ -99,6 +144,5 @@ func Connect(s TmuxSession, alwaysSwitch bool, command string) error {
} else {
attachSession(s.Name)
}

return nil
}

0 comments on commit b6f0de4

Please sign in to comment.