teamocil 0.4.5 → 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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