diff --git a/config.go b/config.go deleted file mode 100644 index c314e8e..0000000 --- a/config.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -type paneConfig struct { - Dir string - Zoom bool - Split string - Scripts []string -} - -type windowConfig struct { - Name string - Dir string - Layout string - Sync bool - Scripts []string - Panes []paneConfig - PaneScripts []string `toml:"pane-scripts"` -} - -type sessionConfig struct { - Name string - Dir string - ClearPanes bool `toml:"clear-panes"` - Windows []windowConfig - SelectWindow string `toml:"select-window"` - SelectPane int `toml:"select-pane"` - WindowScripts []string `toml:"window-scripts"` -} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..5d55e07 --- /dev/null +++ b/config/config.go @@ -0,0 +1,114 @@ +package config + +import ( + "errors" + "fmt" + "io" + + "github.com/BurntSushi/toml" +) + +// Pane contains a pane configuration +type Pane struct { + Dir string + Zoom bool + Split string + Scripts []string +} + +// Window contains a window configuration +type Window struct { + Name string + Dir string + Layout string + Sync bool + Scripts []string + Panes []Pane + PaneScripts []string `toml:"pane-scripts"` +} + +// Session contains a tmux session configuration +type Session struct { + Name string + Dir string + ClearPanes bool `toml:"clear-panes"` + Windows []Window + SelectWindow string `toml:"select-window"` + SelectPane int `toml:"select-pane"` + WindowScripts []string `toml:"window-scripts"` +} + +var ( + validLayouts = []string{ + "even-horizontal", + "even-vertical", + "main-horizontal", + "main-vertical", + "tiled", + } +) + +func checkValid(conf Session) error { + // check at least one window + if len(conf.Windows) == 0 { + return errors.New("you must declare at least on window (0 provided)") + } + + // check select-window and select-pane exist + if conf.SelectWindow != "" { + var win Window + found := false + for _, w := range conf.Windows { + if w.Name == conf.SelectWindow { + win = w + found = true + break + } + } + + if !found { + return fmt.Errorf("selected window %s doesn't exist", conf.SelectWindow) + } + + if conf.SelectPane != 0 { + if len(win.Panes) < conf.SelectPane { + return fmt.Errorf("selected pane %d doesn't exist", conf.SelectPane) + } + } + } + + for _, w := range conf.Windows { + if w.Layout != "" { + found := false + for _, l := range validLayouts { + if l == w.Layout { + found = true + } + } + + if !found { + return fmt.Errorf("invalid layout '%s' in window '%s'", w.Layout, w.Name) + } + } + } + + // check only on zoom in a window + + return nil +} + +// Parse return a sessionConfig from a io.Reader +func Parse(reader io.ReadCloser) (Session, error) { + defer reader.Close() + + var conf Session + if _, err := toml.DecodeReader(reader, &conf); err != nil { + return conf, fmt.Errorf("parsing configuration: %v", err) + } + + if err := checkValid(conf); err != nil { + return conf, err + } + + return conf, nil +} diff --git a/main.go b/main.go index f504f22..0e47a9b 100644 --- a/main.go +++ b/main.go @@ -5,16 +5,16 @@ import ( "log" "os" - "github.com/BurntSushi/toml" "github.com/alecthomas/kingpin" "github.com/alexandrebodin/go-findup" + "github.com/alexandrebodin/tmuxctl/config" "github.com/alexandrebodin/tmuxctl/tmux" ) var ( - version = "development" - start = kingpin.Command("start", "start a tmux instance").Default() - config = start.Arg("config", "Tmux config file").Default(".tmuxctlrc").String() + version = "development" + start = kingpin.Command("start", "start a tmux instance").Default() + configPath = start.Arg("config", "Tmux config file").Default(".tmuxctlrc").String() ) func main() { @@ -23,16 +23,21 @@ func main() { kingpin.CommandLine.VersionFlag.Short('v') kingpin.Parse() - fmt.Printf("Start tmux with config file: %v\n", *config) + fmt.Printf("Start tmux with config file: %v\n", *configPath) - filePath, err := findup.Find(*config) + filePath, err := findup.Find(*configPath) if err != nil { log.Fatalf("Error locating config file %v\n", err) } - var conf sessionConfig - if _, err := toml.DecodeFile(filePath, &conf); err != nil { - log.Fatalf("Error loading configuration %v\n", err) + file, err := os.Open(filePath) + if err != nil { + log.Fatalf("Error openning config file %v\n", err) + } + + conf, err := config.Parse(file) + if err != nil { + log.Fatalf("Error parsing %s: %v\n", filePath, err) } runningSessions, err := tmux.ListSessions() diff --git a/pane.go b/pane.go index 27c6c5b..96812a1 100644 --- a/pane.go +++ b/pane.go @@ -3,6 +3,7 @@ package main import ( "strconv" + "github.com/alexandrebodin/tmuxctl/config" "github.com/alexandrebodin/tmuxctl/tmux" ) @@ -15,7 +16,7 @@ type pane struct { Target string } -func newPane(win *window, config paneConfig, index int) *pane { +func newPane(win *window, config config.Pane, index int) *pane { normalizedIndex := strconv.Itoa(index + win.Sess.TmuxOptions.PaneBaseIndex) pane := &pane{ Window: win, diff --git a/session.go b/session.go index 1b5dac2..d1a6ef0 100644 --- a/session.go +++ b/session.go @@ -7,6 +7,7 @@ import ( "os/exec" "syscall" + "github.com/alexandrebodin/tmuxctl/config" "github.com/alexandrebodin/tmuxctl/tmux" ) @@ -19,7 +20,7 @@ type session struct { WindowScripts []string } -func newSession(config sessionConfig, options *tmux.Options) *session { +func newSession(config config.Session, options *tmux.Options) *session { sess := &session{ Name: config.Name, Dir: lookupDir(config.Dir), diff --git a/tmux/commands.go b/tmux/commands.go index 9eb4cc0..412dece 100644 --- a/tmux/commands.go +++ b/tmux/commands.go @@ -1,116 +1,31 @@ package tmux -import ( - "bytes" - "fmt" - "os/exec" - "strconv" - "strings" +var ( + // DefaultRunner is the default tmux command runner + DefaultRunner = &Runner{} ) -// Result is a commadn result -type Result struct { - Stdout string - Stderr string -} - // Exec runs a tmux command func Exec(args ...string) (Result, error) { - var stdin bytes.Buffer - var stderr bytes.Buffer - var stdout bytes.Buffer - - cmd := exec.Command("tmux", args...) - cmd.Stdin = &stdin - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - err := cmd.Run() - if err != nil { - return Result{}, fmt.Errorf("Error running command \"tmux %v\", %s", args, stderr.String()) - } - - return Result{stdout.String(), stderr.String()}, nil + return DefaultRunner.Exec(args...) } // SendKeys sends keys to tmux (e.g to run a command) func SendKeys(target, keys string) error { - _, err := Exec("send-keys", "-R", "-t", target, keys, "C-m") - return err + return DefaultRunner.SendKeys(target, keys) } // SendRawKeys sends keys to tmux (e.g to run a command) func SendRawKeys(target, keys string) error { - _, err := Exec("send-keys", "-R", "-t", target, keys) - return err + return DefaultRunner.SendRawKeys(target, keys) } -// SessionInfo infos about a running tmux session -type SessionInfo struct{} - // ListSessions returns the list of sessions currently running func ListSessions() (map[string]SessionInfo, error) { - sessionMap := make(map[string]SessionInfo) - - res, err := Exec("ls") - if err != nil { - if strings.Contains(err.Error(), "no server running on") { - return sessionMap, nil - } - return sessionMap, fmt.Errorf("error listing sessions %v", err) - } - - splits := strings.Split(res.Stdout, "\n") - for _, sess := range splits { - sessSplits := strings.Split(sess, ":") - if len(sessSplits) > 1 { - sessionMap[sessSplits[0]] = SessionInfo{} - } - } - - return sessionMap, nil -} - -// Options tmux options -type Options struct { - BaseIndex int - PaneBaseIndex int + return DefaultRunner.ListSessions() } // GetOptions get tmux options func GetOptions() (*Options, error) { - options := &Options{ - BaseIndex: 0, - PaneBaseIndex: 0, - } - - var stderr bytes.Buffer - var stdout bytes.Buffer - cmd := exec.Command("sh", "-c", "tmux start-server\\; show-options -g\\; show-window-options -g") - cmd.Stdout = &stdout - - err := cmd.Run() - if err != nil { - return options, fmt.Errorf("Error getting tmux options %v, %s", err, stderr.String()) - } - - optionsString := strings.Split(stdout.String(), "\n") - for _, option := range optionsString { - optionSplits := strings.Split(option, " ") - if len(optionSplits) == 2 { - name := optionSplits[0] - if name == "base-index" { - if v, err := strconv.Atoi(optionSplits[1]); err == nil { - options.BaseIndex = v - } - } else if name == "pane-base-index" { - if v, err := strconv.Atoi(optionSplits[1]); err == nil { - options.PaneBaseIndex = v - } - - } - } - } - - return options, nil + return DefaultRunner.GetOptions() } diff --git a/tmux/runner.go b/tmux/runner.go new file mode 100644 index 0000000..0a38043 --- /dev/null +++ b/tmux/runner.go @@ -0,0 +1,119 @@ +package tmux + +import ( + "bytes" + "fmt" + "os/exec" + "strconv" + "strings" +) + +// Runner is the underlying struct to run tmux commands +type Runner struct{} + +// Options tmux options +type Options struct { + BaseIndex int + PaneBaseIndex int +} + +// Result is a commadn result +type Result struct { + Stdout string + Stderr string +} + +// Exec runs a command +func (r *Runner) Exec(args ...string) (Result, error) { + var stdin bytes.Buffer + var stderr bytes.Buffer + var stdout bytes.Buffer + + cmd := exec.Command("tmux", args...) + cmd.Stdin = &stdin + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + return Result{}, fmt.Errorf("Error running command \"tmux %v\", %s", args, stderr.String()) + } + + return Result{stdout.String(), stderr.String()}, nil +} + +// SendKeys sends keys to tmux (e.g to run a command) +func (r *Runner) SendKeys(target, keys string) error { + _, err := r.Exec("send-keys", "-R", "-t", target, keys, "C-m") + return err +} + +// SendRawKeys sends keys to tmux (e.g to run a command) +func (r *Runner) SendRawKeys(target, keys string) error { + _, err := Exec("send-keys", "-R", "-t", target, keys) + return err +} + +// SessionInfo infos about a running tmux session +type SessionInfo struct{} + +// ListSessions returns the list of sessions currently running +func (r *Runner) ListSessions() (map[string]SessionInfo, error) { + sessionMap := make(map[string]SessionInfo) + + res, err := Exec("ls") + if err != nil { + if strings.Contains(err.Error(), "no server running on") { + return sessionMap, nil + } + return sessionMap, fmt.Errorf("error listing sessions %v", err) + } + + splits := strings.Split(res.Stdout, "\n") + for _, sess := range splits { + sessSplits := strings.Split(sess, ":") + if len(sessSplits) > 1 { + sessionMap[sessSplits[0]] = SessionInfo{} + } + } + + return sessionMap, nil +} + +// GetOptions get tmux options +func (r *Runner) GetOptions() (*Options, error) { + options := &Options{ + BaseIndex: 0, + PaneBaseIndex: 0, + } + + var stderr bytes.Buffer + var stdout bytes.Buffer + cmd := exec.Command("sh", "-c", "tmux start-server\\; show-options -g\\; show-window-options -g") + cmd.Stdout = &stdout + + err := cmd.Run() + if err != nil { + return options, fmt.Errorf("Error getting tmux options %v, %s", err, stderr.String()) + } + + optionsString := strings.Split(stdout.String(), "\n") + for _, option := range optionsString { + optionSplits := strings.Split(option, " ") + if len(optionSplits) == 2 { + name := optionSplits[0] + if name == "base-index" { + if v, err := strconv.Atoi(optionSplits[1]); err == nil { + options.BaseIndex = v + } + } else if name == "pane-base-index" { + if v, err := strconv.Atoi(optionSplits[1]); err == nil { + options.PaneBaseIndex = v + } + + } + } + } + + return options, nil +} diff --git a/window.go b/window.go index 3a799be..35f033d 100644 --- a/window.go +++ b/window.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/alexandrebodin/tmuxctl/config" "github.com/alexandrebodin/tmuxctl/tmux" ) @@ -19,7 +20,7 @@ type window struct { Target string } -func newWindow(sess *session, config windowConfig) *window { +func newWindow(sess *session, config config.Window) *window { win := &window{ Sess: sess, Name: config.Name,