teamocil 0.4.5 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -4
  3. data/.rubocop.yml +48 -0
  4. data/.travis.yml +2 -2
  5. data/Gemfile +1 -1
  6. data/{LICENSE → LICENSE.md} +0 -0
  7. data/README.md +124 -166
  8. data/Rakefile +12 -27
  9. data/bin/teamocil +3 -4
  10. data/lib/teamocil.rb +51 -4
  11. data/lib/teamocil/cli.rb +23 -90
  12. data/lib/teamocil/command/new_window.rb +16 -0
  13. data/lib/teamocil/command/rename_session.rb +9 -0
  14. data/lib/teamocil/command/rename_window.rb +9 -0
  15. data/lib/teamocil/command/select_layout.rb +9 -0
  16. data/lib/teamocil/command/select_pane.rb +9 -0
  17. data/lib/teamocil/command/select_window.rb +9 -0
  18. data/lib/teamocil/command/send_keys.rb +9 -0
  19. data/lib/teamocil/command/send_keys_to_pane.rb +9 -0
  20. data/lib/teamocil/command/split_window.rb +15 -0
  21. data/lib/teamocil/layout.rb +58 -36
  22. data/lib/teamocil/tmux/pane.rb +15 -0
  23. data/lib/teamocil/tmux/session.rb +28 -0
  24. data/lib/teamocil/tmux/window.rb +47 -0
  25. data/lib/teamocil/utils/closed_struct.rb +16 -0
  26. data/lib/teamocil/utils/option_parser.rb +56 -0
  27. data/lib/teamocil/version.rb +1 -1
  28. data/teamocil.gemspec +15 -16
  29. metadata +27 -54
  30. data/examples/four-splits.yml +0 -8
  31. data/examples/one-and-three-splits.yml +0 -8
  32. data/examples/six-splits.yml +0 -10
  33. data/examples/two-horizontal-splits.yml +0 -6
  34. data/examples/two-vertical-splits.yml +0 -6
  35. data/lib/teamocil/error.rb +0 -6
  36. data/lib/teamocil/layout/pane.rb +0 -66
  37. data/lib/teamocil/layout/session.rb +0 -30
  38. data/lib/teamocil/layout/window.rb +0 -77
  39. data/spec/cli_spec.rb +0 -79
  40. data/spec/fixtures/.my-fancy-layouts-directory/sample-3.yml +0 -10
  41. data/spec/fixtures/.teamocil/sample-2.yml +0 -10
  42. data/spec/fixtures/.teamocil/sample.yml +0 -10
  43. data/spec/fixtures/layouts.yml +0 -76
  44. data/spec/layout_spec.rb +0 -229
  45. data/spec/mock/cli.rb +0 -35
  46. data/spec/mock/layout.rb +0 -16
  47. data/spec/spec_helper.rb +0 -17
@@ -1,30 +0,0 @@
1
- module Teamocil
2
- class Layout
3
- # This class represents a session within tmux
4
- class Session
5
- attr_reader :options, :windows, :name
6
-
7
- # Initialize a new tmux session
8
- #
9
- # @param options [Hash] the options, mostly passed by the CLI
10
- # @param attrs [Hash] the session data from the layout file
11
- def initialize(options, attrs={})
12
- raise Teamocil::Error::LayoutError.new("You must specify a `windows` or `session` key for your layout.") unless attrs["windows"]
13
- @name = attrs["name"] || "teamocil-session-#{rand(10000) + 1}"
14
- @windows = attrs["windows"].each_with_index.map { |window, window_index| Window.new(self, window_index, window) }
15
- @options = options
16
- end
17
-
18
- # Generate commands to send to tmux
19
- #
20
- # @return [Array]
21
- def generate_commands
22
- commands = []
23
- commands << "tmux rename-session \"#{@name}\"" unless @name.nil?
24
- commands << @windows.map(&:generate_commands)
25
- @windows.each { |w| commands << "tmux select-window -t:#{w.name}" && break if w.focus }
26
- commands
27
- end
28
- end
29
- end
30
- end
@@ -1,77 +0,0 @@
1
- module Teamocil
2
- class Layout
3
- # This class represents a window within tmux
4
- class Window
5
- attr_reader :filters, :focus, :root, :panes, :options, :index, :name, :clear, :layout, :with_env_var, :cmd_separator
6
-
7
- # Initialize a new tmux window
8
- #
9
- # @param session [Session] the session where the window is initialized
10
- # @param index [Fixnnum] the window index
11
- # @param attrs [Hash] the window data from the layout file
12
- def initialize(session, index, attrs={})
13
- @name = attrs["name"] || "teamocil-window-#{index+1}"
14
- @root = attrs["root"] || "."
15
- @with_env_var = attrs["with_env_var"] || (true if attrs["with_env_var"].nil?)
16
- @cmd_separator = attrs["cmd_separator"] || "; "
17
- @clear = attrs["clear"] == true ? "clear" : nil
18
- @options = attrs["options"] || {}
19
- @layout = attrs["layout"]
20
- @focus = attrs["focus"]
21
-
22
- @panes = attrs["panes"] || attrs["splits"] || []
23
- raise Teamocil::Error::LayoutError.new("You must specify a `panes` (or legacy `splits`) key for every window.") if @panes.empty?
24
- @panes = @panes.each_with_index.map { |pane, pane_index| Pane.new(self, pane_index + pane_base_index, pane) }
25
-
26
- @filters = attrs["filters"] || {}
27
- @filters["before"] ||= []
28
- @filters["after"] ||= []
29
-
30
- @index = index
31
- @session = session
32
- end
33
-
34
- # Generate commands to send to tmux
35
- #
36
- # @return [Array]
37
- def generate_commands
38
- commands = []
39
-
40
- if @session.options.include?(:here) and @index == 0
41
- commands << "tmux rename-window \"#{@name}\""
42
- else
43
- commands << "tmux new-window -n \"#{@name}\""
44
- end
45
-
46
- pane_commands = @panes.map do |pane|
47
- c = pane.generate_commands
48
- c << "tmux select-layout \"#{@layout}\"" if @layout
49
- c
50
- end
51
- commands << pane_commands
52
-
53
- @options.each_pair do |option, value|
54
- value = "on" if value === true
55
- value = "off" if value === false
56
- commands << "tmux set-window-option #{option} #{value}"
57
- end
58
-
59
- commands << "tmux select-layout \"#{@layout}\"" if @layout
60
- commands << "tmux select-pane -t #{@panes.map(&:focus).index(true) || 0}"
61
-
62
- commands
63
- end
64
-
65
- # Return the `pane-base-index` option for the current window
66
- #
67
- # @return [Fixnum]
68
- def pane_base_index
69
- @pane_base_index ||= begin
70
- global_setting = `tmux show-window-options -g | grep pane-base-index`.split(/\s/).last
71
- local_setting = `tmux show-window-options | grep pane-base-index`.split(/\s/).last
72
- (local_setting || global_setting || "0").to_i
73
- end
74
- end
75
- end
76
- end
77
- end
data/spec/cli_spec.rb DELETED
@@ -1,79 +0,0 @@
1
- # encoding: utf-8
2
- require File.join(File.dirname(__FILE__), "spec_helper.rb")
3
-
4
- describe Teamocil::CLI do
5
- let(:window_pane_base_index) { 0 }
6
- before { Teamocil::Layout::Window.any_instance.stub(:pane_base_index).and_return(window_pane_base_index) }
7
-
8
- context "executing" do
9
- before do
10
- @fake_env = { "TMUX" => 1, "HOME" => File.join(File.dirname(__FILE__), "fixtures") }
11
- end
12
-
13
- context "not in tmux" do
14
- it "should allow editing" do
15
- FileUtils.stub(:touch)
16
- Kernel.should_receive(:system).with("${EDITOR:-vim} #{File.join(@fake_env["HOME"], ".teamocil", "my-layout.yml").inspect}")
17
- Teamocil::CLI.new(["--edit", "my-layout"], @fake_env)
18
- end
19
- end
20
-
21
- context "in tmux" do
22
- before :each do
23
- Teamocil::CLI.messages = []
24
- end
25
-
26
- it "creates a layout from a name" do
27
- @cli = Teamocil::CLI.new(["sample"], @fake_env)
28
- @cli.layout.session.name.should == "sample"
29
- @cli.layout.session.windows.length.should == 2
30
- @cli.layout.session.windows.first.name.should == "foo"
31
- @cli.layout.session.windows.last.name.should == "bar"
32
- end
33
-
34
- it "fails to launch if no layout is provided" do
35
- expect { @cli = Teamocil::CLI.new([], @fake_env) }.to raise_error SystemExit
36
- Teamocil::CLI.messages.should include("You must supply a layout for teamocil to use. See `teamocil --help` for more options.")
37
- end
38
-
39
- it "fails to create a layout from a layout that doesn’t exist" do
40
- expect { @cli = Teamocil::CLI.new(["i-do-not-exist"], @fake_env) }.to raise_error SystemExit
41
- Teamocil::CLI.messages.should include("There is no file \"#{File.join(File.dirname(__FILE__), "fixtures", ".teamocil", "i-do-not-exist.yml")}\".")
42
- end
43
-
44
- it "creates a layout from a specific file" do
45
- @cli = Teamocil::CLI.new(["--layout", "./spec/fixtures/.teamocil/sample.yml"], @fake_env)
46
- @cli.layout.session.name.should == "sample"
47
- @cli.layout.session.windows.length.should == 2
48
- @cli.layout.session.windows.first.name.should == "foo"
49
- @cli.layout.session.windows.last.name.should == "bar"
50
- end
51
-
52
- it "fails to create a layout from a file that doesn’t exist" do
53
- expect { @cli = Teamocil::CLI.new(["--layout", "./spec/fixtures/.teamocil/i-do-not-exist.yml"], @fake_env) }.to raise_error SystemExit
54
- Teamocil::CLI.messages.should include("There is no file \"./spec/fixtures/.teamocil/i-do-not-exist.yml\".")
55
- end
56
-
57
- it "lists available layouts" do
58
- @cli = Teamocil::CLI.new(["--list"], @fake_env)
59
- @cli.layouts.should == ["sample", "sample-2"]
60
- end
61
-
62
- it "should show the content" do
63
- FileUtils.stub(:touch)
64
- Kernel.should_receive(:system).with("cat #{File.join(@fake_env["HOME"], ".teamocil", "sample.yml").inspect}")
65
- Teamocil::CLI.new(["--show", "sample"], @fake_env)
66
- end
67
-
68
- it "looks only in the $TEAMOCIL_PATH environment variable for layouts" do
69
- @fake_env = { "TMUX" => 1, "HOME" => File.join(File.dirname(__FILE__), "fixtures"), "TEAMOCIL_PATH" => File.join(File.dirname(__FILE__), "fixtures/.my-fancy-layouts-directory") }
70
-
71
- @cli = Teamocil::CLI.new(["sample-3"], @fake_env)
72
- @cli.layout.session.name.should == "sample-3"
73
-
74
- expect { @cli = Teamocil::CLI.new(["sample"], @fake_env) }.to raise_error SystemExit
75
- Teamocil::CLI.messages.should include("There is no file \"#{@fake_env["TEAMOCIL_PATH"]}/sample.yml\".")
76
- end
77
- end
78
- end
79
- end
@@ -1,10 +0,0 @@
1
- session:
2
- name: sample-3
3
- root: ~
4
- windows:
5
- - name: "foo"
6
- panes:
7
- - cmd: "pwd"
8
- - name: "bar"
9
- panes:
10
- - cmd: "pwd"
@@ -1,10 +0,0 @@
1
- session:
2
- name: sample-2
3
- root: ~
4
- windows:
5
- - name: "foo"
6
- panes:
7
- - cmd: "pwd"
8
- - name: "bar"
9
- panes:
10
- - cmd: "pwd"
@@ -1,10 +0,0 @@
1
- session:
2
- name: sample
3
- root: ~
4
- windows:
5
- - name: <%= "foo" %>
6
- panes:
7
- - cmd: "pwd"
8
- - name: "bar"
9
- panes:
10
- - cmd: "pwd"
@@ -1,76 +0,0 @@
1
- # Simple two windows layout
2
- two-windows:
3
- windows:
4
- - name: "foo"
5
- clear: true
6
- root: "/foo"
7
- focus: true
8
- layout: "tiled"
9
- panes:
10
- - cmd: "echo 'foo'"
11
- - cmd: "echo 'foo again'"
12
- - name: "bar"
13
- root: "/bar"
14
- splits:
15
- - cmd:
16
- - "echo 'bar'"
17
- - "echo 'bar in an array'"
18
- target: bottom-right
19
- - cmd: "echo 'bar again'"
20
- focus: true
21
- width: 50
22
-
23
- # Simple two windows layout with filters
24
- two-windows-with-filters:
25
- windows:
26
- - name: "foo"
27
- root: "/foo"
28
- filters:
29
- before:
30
- - "echo first before filter"
31
- - "echo second before filter"
32
- after:
33
- - "echo first after filter"
34
- - "echo second after filter"
35
- panes:
36
- - cmd: "echo 'foo'"
37
- - cmd: "echo 'foo again'"
38
- width: 50
39
-
40
- two-windows-with-custom-command-options:
41
- windows:
42
- - name: "foo"
43
- cmd_separator: "\n"
44
- with_env_var: false
45
- clear: true
46
- root: "/foo"
47
- layout: "tiled"
48
- panes:
49
- - cmd: "echo 'foo'"
50
- - cmd: "echo 'foo again'"
51
- - name: "bar"
52
- cmd_separator: " && "
53
- with_env_var: true
54
- root: "/bar"
55
- splits:
56
- - cmd:
57
- - "echo 'bar'"
58
- - "echo 'bar in an array'"
59
- target: bottom-right
60
- - cmd: "echo 'bar again'"
61
- focus: true
62
- width: 50
63
-
64
- three-windows-within-a-session:
65
- session:
66
- name: "my awesome session"
67
- windows:
68
- - name: "first window"
69
- panes:
70
- - cmd: "echo 'foo'"
71
- - name: "second window"
72
- panes:
73
- - cmd: "echo 'foo'"
74
- - name: "third window"
75
- panes:
76
- - cmd: "echo 'foo'"
data/spec/layout_spec.rb DELETED
@@ -1,229 +0,0 @@
1
- # encoding: utf-8
2
- require File.join(File.dirname(__FILE__), "spec_helper.rb")
3
-
4
- describe Teamocil::Layout do
5
- let(:window_pane_base_index) { 0 }
6
- before do
7
- Teamocil::Layout::Window.any_instance.stub(:pane_base_index).and_return(window_pane_base_index)
8
- ENV.stub(:[]).with("SHELL").and_return("/usr/bin/bash")
9
- end
10
-
11
- context "compiling" do
12
- before do
13
- @layout = Teamocil::Layout.new(layouts["two-windows"], {})
14
- end
15
-
16
- describe "handles bad layouts" do
17
- it "does not compile without windows" do
18
- @layout = Teamocil::Layout.new({ "name" => "foo" }, {})
19
- expect { @layout.compile! }.to raise_error Teamocil::Error::LayoutError
20
- end
21
-
22
- it "does not compile without panes" do
23
- @layout = Teamocil::Layout.new({ "windows" => [{ "name" => "foo" }] }, {})
24
- expect { @layout.compile! }.to raise_error Teamocil::Error::LayoutError
25
- end
26
-
27
- it "does not compile with empty panes" do
28
- @layout = Teamocil::Layout.new({ "windows" => [{ "name" => "foo", "panes" => [nil, nil] }] }, {})
29
- expect { @layout.compile! }.to raise_error Teamocil::Error::LayoutError
30
- end
31
- end
32
-
33
- describe "windows" do
34
- it "creates windows" do
35
- session = @layout.compile!
36
- session.windows.each do |window|
37
- window.should be_an_instance_of Teamocil::Layout::Window
38
- end
39
- end
40
-
41
- it "creates windows with names" do
42
- session = @layout.compile!
43
- session.windows[0].name.should == "foo"
44
- session.windows[1].name.should == "bar"
45
- end
46
-
47
- it "creates windows with root paths" do
48
- session = @layout.compile!
49
- session.windows[0].root.should == "/foo"
50
- session.windows[1].root.should == "/bar"
51
- end
52
-
53
- it "creates windows with clear option" do
54
- session = @layout.compile!
55
- session.windows[0].clear.should == "clear"
56
- session.windows[1].clear.should be_nil
57
- end
58
-
59
- it "creates windows with layout option" do
60
- session = @layout.compile!
61
- session.windows[0].layout.should == "tiled"
62
- session.windows[1].layout.should be_nil
63
- end
64
-
65
- it "handles focused windows" do
66
- session = @layout.compile!
67
- session.windows.first.focus.should be_true
68
- end
69
- end
70
-
71
- describe "panes" do
72
- it "creates panes" do
73
- session = @layout.compile!
74
- session.windows.first.panes.each do |pane|
75
- pane.should be_an_instance_of Teamocil::Layout::Pane
76
- end
77
- end
78
-
79
- it "creates panes with legacy `splits` key" do
80
- session = @layout.compile!
81
- session.windows.last.panes.each do |pane|
82
- pane.should be_an_instance_of Teamocil::Layout::Pane
83
- end
84
- end
85
-
86
- it "creates panes with dimensions" do
87
- session = @layout.compile!
88
- session.windows.last.panes[0].width.should == nil
89
- session.windows.last.panes[1].width.should == 50
90
- end
91
-
92
- it "creates panes with commands specified in strings" do
93
- session = @layout.compile!
94
- session.windows.first.panes[0].cmd.should == "echo 'foo'"
95
- end
96
-
97
- it "creates panes with commands specified in an array" do
98
- session = @layout.compile!
99
- session.windows.last.panes[0].cmd.length.should == 2
100
- session.windows.last.panes[0].cmd.first.should == "echo 'bar'"
101
- session.windows.last.panes[0].cmd.last.should == "echo 'bar in an array'"
102
- end
103
-
104
- it "handles focused panes" do
105
- session = @layout.compile!
106
- session.windows.last.panes[1].focus.should be_true
107
- session.windows.last.panes[0].focus.should be_false
108
- end
109
- end
110
-
111
- describe "filters" do
112
- it "creates windows with before filters" do
113
- layout = Teamocil::Layout.new(layouts["two-windows-with-filters"], {})
114
- session = layout.compile!
115
- session.windows.first.filters["before"].length.should == 2
116
- session.windows.first.filters["before"].first.should == "echo first before filter"
117
- session.windows.first.filters["before"].last.should == "echo second before filter"
118
- end
119
-
120
- it "creates windows with after filters" do
121
- layout = Teamocil::Layout.new(layouts["two-windows-with-filters"], {})
122
- session = layout.compile!
123
- session.windows.first.filters["after"].length.should == 2
124
- session.windows.first.filters["after"].first.should == "echo first after filter"
125
- session.windows.first.filters["after"].last.should == "echo second after filter"
126
- end
127
-
128
- it "should handle blank filters" do
129
- session = @layout.compile!
130
- session.windows.first.filters.should have_key "after"
131
- session.windows.first.filters.should have_key "before"
132
- session.windows.first.filters["after"].should be_empty
133
- session.windows.first.filters["before"].should be_empty
134
- end
135
- end
136
-
137
- describe "targets" do
138
- it "should handle panes without a target" do
139
- session = @layout.compile!
140
- session.windows.last.panes.last.target.should == nil
141
- end
142
-
143
- it "should handle panes with a target" do
144
- session = @layout.compile!
145
- session.windows.last.panes.first.target.should == "bottom-right"
146
- end
147
- end
148
-
149
- describe "sessions" do
150
- it "should handle windows within a session" do
151
- layout = Teamocil::Layout.new(layouts["three-windows-within-a-session"], {})
152
- session = layout.compile!
153
- session.windows.length.should == 3
154
- session.name.should == "my awesome session"
155
- end
156
-
157
- it "should assign a random name if none is provided" do
158
- layout = Teamocil::Layout.new(layouts["two-windows"], {})
159
- session = layout.compile!
160
- session.name.should match /teamocil-session-\d+/
161
- end
162
- end
163
- end
164
-
165
- context "generating commands" do
166
- before { @layout = Teamocil::Layout.new(layouts["two-windows"], {}) }
167
-
168
- it "should generate pane commands" do
169
- session = @layout.compile!
170
- commands = session.windows.last.panes[0].generate_commands
171
- commands.length.should == 2
172
- commands.first.should == "tmux send-keys -t 0 \"export TEAMOCIL=1; cd \"/bar\"; echo 'bar'; echo 'bar in an array'\""
173
- commands.last.should == "tmux send-keys -t 0 Enter"
174
-
175
- session = @layout.compile!
176
- commands = session.windows.first.panes[0].generate_commands
177
- commands.length.should == 2
178
- commands.first.should == "tmux send-keys -t 0 \"export TEAMOCIL=1; cd \"/foo\"; clear; echo 'foo'\""
179
- commands.last.should == "tmux send-keys -t 0 Enter"
180
- end
181
-
182
- it "should generate window commands" do
183
- session = @layout.compile!
184
- commands = session.windows.last.generate_commands
185
- commands.first.should == "tmux new-window -n \"bar\""
186
- commands.last.should == "tmux select-pane -t 1"
187
- end
188
-
189
- it "should apply the layout after each pane is created" do
190
- session = @layout.compile!
191
- commands = session.windows.first.generate_commands
192
- commands[1][0].should == ["tmux send-keys -t 0 \"export TEAMOCIL=1; cd \"/foo\"; clear; echo 'foo'\"", "tmux send-keys -t 0 Enter", "tmux select-layout \"tiled\""]
193
- commands[1][1].should == ["tmux split-window", "tmux send-keys -t 1 \"export TEAMOCIL=1; cd \"/foo\"; clear; echo 'foo again'\"", "tmux send-keys -t 1 Enter", "tmux select-layout \"tiled\""]
194
- end
195
-
196
- it "should use fish syntax when fish shell is default shell" do
197
- ENV.stub(:[]).with("SHELL").and_return("/usr/local/bin/fish")
198
- session = @layout.compile!
199
- commands = session.windows.last.panes[0].generate_commands
200
- commands.length.should == 2
201
- commands.first.should == "tmux send-keys -t 0 \"set -gx TEAMOCIL 1; cd \"/bar\"; echo 'bar'; echo 'bar in an array'\""
202
- commands.last.should == "tmux send-keys -t 0 Enter"
203
- end
204
-
205
- context "with custom pane-base-index option" do
206
- let(:window_pane_base_index) { 2 }
207
-
208
- it "should generate pane commands" do
209
- session = @layout.compile!
210
- commands = session.windows.last.panes[0].generate_commands
211
- commands.length.should == 2
212
- commands.first.should == "tmux send-keys -t 2 \"export TEAMOCIL=1; cd \"/bar\"; echo 'bar'; echo 'bar in an array'\""
213
- commands.last.should == "tmux send-keys -t 2 Enter"
214
- end
215
- end
216
-
217
- it "should not export TEAMOCIL = 1 env variable if disabled" do
218
- @layout = Teamocil::Layout.new(layouts["two-windows-with-custom-command-options"], {})
219
-
220
- session = @layout.compile!
221
- commands = session.windows.first.generate_commands
222
- commands[1][0].should == ["tmux send-keys -t 0 \"cd \"/foo\"\nclear\necho 'foo'\"", "tmux send-keys -t 0 Enter", "tmux select-layout \"tiled\""]
223
- commands[1][1].should == ["tmux split-window", "tmux send-keys -t 1 \"cd \"/foo\"\nclear\necho 'foo again'\"", "tmux send-keys -t 1 Enter", "tmux select-layout \"tiled\""]
224
-
225
- commands = session.windows[1].generate_commands
226
- commands[1][0].should == ["tmux send-keys -t 0 \"export TEAMOCIL=1 && cd \"/bar\" && echo 'bar' && echo 'bar in an array'\"", "tmux send-keys -t 0 Enter"]
227
- end
228
- end
229
- end