@@ -7,30 +7,51 @@ import (
77 "log"
88 "os"
99 "os/exec"
10- "strings "
10+ "time "
1111
1212 "gopkg.in/yaml.v2"
1313)
1414
15+ type Pane struct {
16+ Object `yaml:",inline"`
17+ Dir string
18+ Focus bool
19+ Cmd string
20+ target string
21+ }
22+
23+ type Window struct {
24+ Object `yaml:",inline"`
25+ Dir string
26+ Focus bool
27+ Layout string
28+ Panes []* Pane
29+ }
30+
1531type Project struct {
1632 Name string
1733 Dir string
18- Windows []struct {
19- Name string
20- Dir string
21- Layout string
22- Focus bool
23- Panes []string
24- }
34+ PreCmd string `yaml:"pre_cmd"`
35+ PostCmd string `yaml:"post_cmd"`
36+ Windows []* Window
2537}
2638
27- func run (name string , args ... string ) {
28- cmd := exec .Command (name , args ... )
39+ func run (format string , args ... interface {}) error {
40+ cmdStr := fmt .Sprintf (format , args ... )
41+ cmd := exec .Command ("/bin/sh" , "-c" , cmdStr )
42+
43+ fmt .Println (cmdStr )
44+ _ , err := cmd .CombinedOutput ()
45+ if err != nil {
46+ return err
47+ }
48+
49+ return nil
50+ }
2951
30- fmt . Println ( name , strings . Join ( args [:], " " ))
31- out , err := cmd . CombinedOutput ( )
52+ func shell ( format string , args ... interface {}) {
53+ err := run ( format , args ... )
3254 if err != nil {
33- fmt .Printf ("Output: %s" , out )
3455 log .Fatal (err )
3556 }
3657}
@@ -39,19 +60,19 @@ var sessionStarted bool
3960
4061func NewWindow (session , dir string ) {
4162 if sessionStarted {
42- run ("tmux" , " new-window" , "-d" , " -t " + session , " -c " + dir )
63+ shell ("tmux new-window -d -t %s -c %s" , session , dir )
4364 } else {
44- run ("tmux" , " new-session" , "-d" , " -s " + session , " -c " + dir )
65+ shell ("tmux new-session -d -s %s -c %s" , session , dir )
4566 sessionStarted = true
4667 }
4768}
4869
4970func NewPane (target , dir string ) {
50- run ("tmux" , " split-window" , " -t " + target , " -c " + dir )
71+ shell ("tmux split-window -t %s -c %s" , target , dir )
5172}
5273
5374func SelectWindow (target string ) {
54- run ("tmux" , " select-window" , " -t " + target )
75+ shell ("tmux select-window -t %s" , target )
5576}
5677
5778func SelectLayout (target , layout string ) {
@@ -67,19 +88,23 @@ func SelectLayout(target, layout string) {
6788 default :
6889 log .Fatal ("Bad layout: " + layout )
6990 }
70- run ("tmux" , " select-layout" , " -t " + target , layout )
91+ shell ("tmux select-layout -t %s %s" , target , layout )
7192}
7293
7394func SendLine (target , text string ) {
7495 if text == "" {
7596 return
7697 }
77- run ("tmux" , " send-keys" , "-l" , "-t " + target , text )
78- run ("tmux" , " send-keys" , "-R" , " -t " + target , " Enter" )
98+ shell ("tmux send-keys -t %s '%s'" , target , text )
99+ shell ("tmux send-keys -R -t %s ' Enter'" , target )
79100}
80101
81102func KillSession (session string ) {
82- run ("tmux" , "kill-session" , "-t " + session )
103+ shell ("tmux kill-session -t %s" , session )
104+ }
105+
106+ func SetEnvironment (session , key , value string ) {
107+ shell ("tmux set-environment -t %s %s %s" , session , key , value )
83108}
84109
85110func coalesce (args ... string ) string {
@@ -91,42 +116,139 @@ func coalesce(args ...string) string {
91116 return ""
92117}
93118
119+ func (project * Project ) getDir (w * Window , paneIndex int ) string {
120+ if paneIndex > len (w .Panes ) {
121+ log .Fatal ("Pane index out of bounds?!" )
122+ }
123+
124+ if paneIndex == 0 && len (w .Panes ) == 0 {
125+ // The window has no explicit panes
126+ return coalesce (w .Dir , project .Dir , "." )
127+ }
128+
129+ return coalesce (w .Panes [paneIndex ].Dir , w .Dir , project .Dir , "." )
130+ }
131+
132+ func (p * Pane ) Run () {
133+ SendLine (p .target , p .Cmd )
134+ }
135+
136+ func (w * Window ) Run () {
137+ }
138+
139+ func (w * Window ) DoReadyCheck () {
140+ for {
141+ ready := true
142+
143+ for _ , p := range w .Panes {
144+ if p == nil {
145+ continue
146+ }
147+ if ! p .IsReady () {
148+ ready = false
149+ time .Sleep (100 * time .Millisecond )
150+ break
151+ }
152+ }
153+
154+ if ready {
155+ return
156+ }
157+ }
158+ }
159+
160+ func (p * Pane ) DoReadyCheck () {
161+ if p .ReadyCheck .Test == "" {
162+ return
163+ }
164+
165+ for {
166+ if err := run (p .ReadyCheck .Test ); err == nil {
167+ break
168+ }
169+
170+ if p .ReadyCheck .Retries <= 0 {
171+ log .Fatal ("Object test failed?!" )
172+ } else {
173+ p .ReadyCheck .Retries --
174+ time .Sleep (p .ReadyCheck .Interval )
175+ }
176+ }
177+ }
178+
179+ func shellInDir (dir , cmd string ) {
180+ shell ("cd %s;%s" , coalesce (dir , "." ), cmd )
181+ }
182+
94183func up (session string , project * Project ) {
184+ if project .PreCmd != "" {
185+ shellInDir (project .Dir , project .PreCmd )
186+ }
187+
188+ // Spawn all the windows/panes
95189 for wi , w := range project .Windows {
190+ if w == nil {
191+ continue
192+ }
96193 target := fmt .Sprintf ("%s:%d" , session , wi )
97- dir := coalesce ( w . Dir , project . Dir , "." )
194+ dir := project . getDir ( w , 0 )
98195
99196 NewWindow (session , dir )
100197
101198 for pi , p := range w .Panes {
199+ if p == nil {
200+ continue
201+ }
202+ p .target = fmt .Sprintf ("%s:%d.%d" , session , wi , pi )
203+ dir := project .getDir (w , pi )
102204 if pi > 0 {
103205 NewPane (target , dir )
104206 }
105- SendLine (target , p )
106207 }
107208
108209 SelectLayout (target , w .Layout )
109210 }
110211
212+ // Run the commands concurrently
213+ for _ , w := range project .Windows {
214+ if w == nil {
215+ continue
216+ }
217+ addRunner (w )
218+ for _ , p := range w .Panes {
219+ if p == nil {
220+ continue
221+ }
222+ addRunner (p )
223+ }
224+ }
225+ runAll ()
226+
227+ // Set which window has focus
111228 for wi , w := range project .Windows {
229+ if w == nil {
230+ continue
231+ }
112232 if w .Focus {
113233 target := fmt .Sprintf ("%s:%d" , session , wi )
114234 SelectWindow (target )
115235 }
116236 }
237+
238+ if project .PostCmd != "" {
239+ shellInDir (project .Dir , project .PostCmd )
240+ }
117241}
118242
119243func down (session string , project * Project ) {
120244 KillSession (session )
121245}
122246
123- var defaultFile = "tmux-compose.yml"
124-
125247func main () {
126248 var overrideName string
127249 var composeFile string
128250 flag .StringVar (& overrideName , "s" , "" , "Override the config files session name." )
129- flag .StringVar (& composeFile , "f" , defaultFile , "Specify an alternate compose file" )
251+ flag .StringVar (& composeFile , "f" , "tmux-compose.yml" , "Specify an alternate compose file" )
130252 flag .Parse ()
131253
132254 if flag .Arg (0 ) == "" {
0 commit comments