tmuxinator 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tmuxinator +1 -1
  3. data/lib/tmuxinator.rb +6 -7
  4. data/lib/tmuxinator/assets/sample.yml +13 -1
  5. data/lib/tmuxinator/assets/template-stop.erb +11 -0
  6. data/lib/tmuxinator/assets/template.erb +15 -1
  7. data/lib/tmuxinator/cli.rb +85 -15
  8. data/lib/tmuxinator/config.rb +74 -30
  9. data/lib/tmuxinator/deprecations.rb +8 -0
  10. data/lib/tmuxinator/doctor.rb +17 -0
  11. data/lib/tmuxinator/hooks.rb +14 -0
  12. data/lib/tmuxinator/hooks/project.rb +42 -0
  13. data/lib/tmuxinator/pane.rb +26 -18
  14. data/lib/tmuxinator/project.rb +113 -48
  15. data/lib/tmuxinator/version.rb +1 -1
  16. data/lib/tmuxinator/wemux_support.rb +18 -9
  17. data/lib/tmuxinator/window.rb +42 -26
  18. data/spec/fixtures/TMUXINATOR_CONFIG/TMUXINATOR_CONFIG.yml +0 -0
  19. data/spec/fixtures/dot-tmuxinator/both.yml +0 -0
  20. data/spec/fixtures/dot-tmuxinator/dup/local-dup.yml +0 -0
  21. data/spec/fixtures/dot-tmuxinator/home.yml +0 -0
  22. data/spec/fixtures/dot-tmuxinator/local-dup.yml +0 -0
  23. data/spec/fixtures/sample.yml +0 -1
  24. data/spec/fixtures/xdg-tmuxinator/both.yml +0 -0
  25. data/spec/fixtures/xdg-tmuxinator/xdg.yml +0 -0
  26. data/spec/lib/tmuxinator/cli_spec.rb +87 -13
  27. data/spec/lib/tmuxinator/config_spec.rb +163 -91
  28. data/spec/lib/tmuxinator/doctor_spec.rb +69 -0
  29. data/spec/lib/tmuxinator/hooks/project_spec.rb +63 -0
  30. data/spec/lib/tmuxinator/hooks_spec.rb +31 -0
  31. data/spec/lib/tmuxinator/pane_spec.rb +24 -1
  32. data/spec/lib/tmuxinator/project_spec.rb +58 -23
  33. data/spec/lib/tmuxinator/window_spec.rb +8 -6
  34. data/spec/spec_helper.rb +2 -1
  35. metadata +177 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d4701d5ffcbc531c162e257f79c3f3399e106ec4
4
- data.tar.gz: 94774dea4bbad7cfbc17f16d9428cd85061e2b5d
3
+ metadata.gz: b82c1fce5acc4afbea3917be671204b8ec18ce24
4
+ data.tar.gz: '06860d74aa9ccabd291d0f4e996fdfdf803600d9'
5
5
  SHA512:
6
- metadata.gz: fac94b03868d51bfac62e4205b91b4695a97d686d46759a09df7e33144e415b100b9b72984f7c0fd7d0146a27ac21c630d97729769e137047d0253ecf16afaa7
7
- data.tar.gz: d9833164fd80716d76f19269f0eb07d424b57e788a2fe70f6c31c2a7f74130285a1a332e2b1401c2026a9a696afaa76cb76db7fa05e4934a7391912170e00e42
6
+ metadata.gz: e240783bffcbabaa05f371944a7e6654117b9ca1840e7cb2cdf736171943e740973ce8d38506d67c8cd5a8b2ebab360727f3d67b84587c2241c4850e568118ef
7
+ data.tar.gz: ca0fc2c809ead67680c77cbb2fff2a43d917ffaa6a89c40ed9ef2c46eef2f517611814c2b385979c18401709158e4695d602bec448d599d70148a0789d916012
@@ -6,7 +6,7 @@ require "tmuxinator"
6
6
 
7
7
  name = ARGV[0] || nil
8
8
 
9
- if ARGV.length == 0 && Tmuxinator::Config.local?
9
+ if ARGV.empty? && Tmuxinator::Config.local?
10
10
  Tmuxinator::Cli.new.local
11
11
  elsif name && !Tmuxinator::Cli::COMMANDS.keys.include?(name.to_sym) &&
12
12
  Tmuxinator::Config.exists?(name)
@@ -1,14 +1,19 @@
1
- require "yaml"
2
1
  require "erubis"
2
+ require "fileutils"
3
3
  require "shellwords"
4
4
  require "thor"
5
5
  require "thor/version"
6
+ require "xdg"
7
+ require "yaml"
6
8
 
7
9
  require "tmuxinator/util"
8
10
  require "tmuxinator/deprecations"
9
11
  require "tmuxinator/wemux_support"
10
12
  require "tmuxinator/cli"
11
13
  require "tmuxinator/config"
14
+ require "tmuxinator/doctor"
15
+ require "tmuxinator/hooks"
16
+ require "tmuxinator/hooks/project"
12
17
  require "tmuxinator/pane"
13
18
  require "tmuxinator/project"
14
19
  require "tmuxinator/window"
@@ -16,9 +21,3 @@ require "tmuxinator/version"
16
21
 
17
22
  module Tmuxinator
18
23
  end
19
-
20
- class Object
21
- def blank?
22
- respond_to?(:empty?) ? !!empty? : !self
23
- end
24
- end
@@ -9,6 +9,18 @@ root: ~/
9
9
  # Runs before everything. Use it to start daemons etc.
10
10
  # pre: sudo /etc/rc.d/mysqld start
11
11
 
12
+ # Project hooks
13
+ # Runs on project start, always
14
+ # on_project_start: command
15
+ # Run on project start, the first time
16
+ # on_project_first_start: command
17
+ # Run on project start, after the first time
18
+ # on_project_restart: command
19
+ # Run on project exit ( detaching from tmux session )
20
+ # on_project_exit: command
21
+ # Run on project stop
22
+ # on_project_stop: command
23
+
12
24
  # Runs in each window and pane before window/pane specific commands. Useful for setting up interpreter versions.
13
25
  # pre_window: rbenv shell 2.0.0-p247
14
26
 
@@ -21,7 +33,7 @@ root: ~/
21
33
  # Specifies (by name or index) which window will be selected on project startup. If not set, the first window is used.
22
34
  # startup_window: editor
23
35
 
24
- # Specitifes (by index) which pane of the specified window will be selected on project startup. If not set, the first pane is used.
36
+ # Specifies (by index) which pane of the specified window will be selected on project startup. If not set, the first pane is used.
25
37
  # startup_pane: 1
26
38
 
27
39
  # Controls whether the tmux session should be attached to automatically. Defaults to true.
@@ -0,0 +1,11 @@
1
+ #!<%= ENV["SHELL"] || "/bin/bash" %>
2
+
3
+ <%- if tmux_has_session? name -%>
4
+ cd <%= root || "." %>
5
+
6
+ <%= tmux_kill_session_command %>
7
+
8
+ # Run on_project_stop command
9
+ <%= hook_on_project_stop %>
10
+
11
+ <%- end -%>
@@ -6,11 +6,18 @@ unset RBENV_DIR
6
6
 
7
7
  <%= tmux %> start-server;
8
8
 
9
+ cd <%= root || "." %>
10
+
11
+ # Run on_project_start command.
12
+ <%= hook_on_project_start %>
13
+
9
14
  <%- if !tmux_has_session? name -%>
10
- cd <%= root || "." %>
11
15
 
12
16
  # Run pre command.
13
17
  <%= pre %>
18
+
19
+ # Run on_project_first_start command.
20
+ <%= hook_on_project_first_start %>
14
21
 
15
22
  # Create the session and the first window. Manually switch to root
16
23
  # directory if required to support tmux < 1.9
@@ -73,6 +80,9 @@ unset RBENV_DIR
73
80
 
74
81
  <%= tmux %> select-window -t <%= startup_window %>
75
82
  <%= tmux %> select-pane -t <%= startup_pane %>
83
+ <%- else -%>
84
+ # Run on_project_restart command.
85
+ <%= hook_on_project_restart %>
76
86
  <%- end -%>
77
87
 
78
88
  <%- if attach? -%>
@@ -84,3 +94,7 @@ unset RBENV_DIR
84
94
  <%- end -%>
85
95
 
86
96
  <%= post %>
97
+ <%- unless attach? -%>
98
+ # Run on_project_exit command.
99
+ <%= hook_on_project_exit %>
100
+ <%- end -%>
@@ -1,3 +1,5 @@
1
+ require "open3"
2
+
1
3
  module Tmuxinator
2
4
  class Cli < Thor
3
5
  # By default, Thor returns exit(0) when an error occurs.
@@ -30,7 +32,7 @@ module Tmuxinator
30
32
  version: "Display installed tmuxinator version",
31
33
  doctor: "Look for problems in your configuration",
32
34
  list: "Lists all tmuxinator projects"
33
- }
35
+ }.freeze
34
36
 
35
37
  package_name "tmuxinator" \
36
38
  unless Gem::Version.create(Thor::VERSION) < Gem::Version.create("0.18")
@@ -58,7 +60,7 @@ module Tmuxinator
58
60
  end
59
61
  end
60
62
 
61
- desc "new [PROJECT]", COMMANDS[:new]
63
+ desc "new [PROJECT] [SESSION]", COMMANDS[:new]
62
64
  map "open" => :new
63
65
  map "edit" => :new
64
66
  map "o" => :new
@@ -68,18 +70,76 @@ module Tmuxinator
68
70
  aliases: ["-l"],
69
71
  desc: "Create local project file at ./.tmuxinator.yml"
70
72
 
71
- def new(name)
72
- project_file = find_project_file(name, options[:local])
73
- Kernel.system("$EDITOR #{project_file}") || doctor
73
+ def new(name, session = nil)
74
+ if session
75
+ new_project_with_session(name, session)
76
+ else
77
+ new_project(name)
78
+ end
74
79
  end
75
80
 
76
81
  no_commands do
82
+ def new_project(name)
83
+ project_file = find_project_file(name, options[:local])
84
+ Kernel.system("$EDITOR #{project_file}") || doctor
85
+ end
86
+
87
+ def new_project_with_session(name, session)
88
+ if Tmuxinator::Config.version < 1.6
89
+ raise "Creating projects from sessions is unsupported\
90
+ for tmux version 1.5 or lower."
91
+ end
92
+
93
+ windows, _, s0 = Open3.capture3(<<-CMD)
94
+ tmux list-windows -t #{session}\
95
+ -F "#W \#{window_layout} \#{window_active} \#{pane_current_path}"
96
+ CMD
97
+ panes, _, s1 = Open3.capture3(<<-CMD)
98
+ tmux list-panes -s -t #{session} -F "#W \#{pane_current_path}"
99
+ CMD
100
+ tmux_options, _, s2 = Open3.capture3(<<-CMD)
101
+ tmux show-options -t #{session}
102
+ CMD
103
+ project_root = tmux_options[/^default-path "(.+)"$/, 1]
104
+
105
+ unless [s0, s1, s2].all?(&:success?)
106
+ raise "Session '#{session}' doesn't exist."
107
+ end
108
+
109
+ panes = panes.each_line.map(&:split).group_by(&:first)
110
+ windows = windows.each_line.map do |line|
111
+ window_name, layout, active, path = line.split(" ")
112
+ project_root ||= path if active.to_i == 1
113
+ [
114
+ window_name,
115
+ layout,
116
+ Array(panes[window_name]).map do |_, pane_path|
117
+ "cd #{pane_path}"
118
+ end
119
+ ]
120
+ end
121
+
122
+ yaml = {
123
+ "name" => name,
124
+ "project_root" => project_root,
125
+ "windows" => windows.map do |window_name, layout, window_panes|
126
+ {
127
+ window_name => {
128
+ "layout" => layout,
129
+ "panes" => window_panes
130
+ }
131
+ }
132
+ end
133
+ }
134
+
135
+ path = config_path(name, options[:local])
136
+ File.open(path, "w") do |f|
137
+ f.write(YAML.dump(yaml))
138
+ end
139
+ end
140
+
77
141
  def find_project_file(name, local = false)
78
- path = if local
79
- Tmuxinator::Config::LOCAL_DEFAULT
80
- else
81
- Tmuxinator::Config.default_project(name)
82
- end
142
+ path = config_path(name, local)
83
143
  if File.exist?(path)
84
144
  path
85
145
  else
@@ -87,6 +147,14 @@ module Tmuxinator
87
147
  end
88
148
  end
89
149
 
150
+ def config_path(name, local = false)
151
+ if local
152
+ Tmuxinator::Config::LOCAL_DEFAULT
153
+ else
154
+ Tmuxinator::Config.default_project(name)
155
+ end
156
+ end
157
+
90
158
  def generate_project_file(name, path)
91
159
  template = Tmuxinator::Config.default? ? :default : :sample
92
160
  content = File.read(Tmuxinator::Config.send(template.to_sym))
@@ -127,7 +195,7 @@ module Tmuxinator
127
195
  end
128
196
 
129
197
  def kill_project(project)
130
- Kernel.exec(project.tmux_kill_session_command)
198
+ Kernel.exec(project.kill)
131
199
  end
132
200
  end
133
201
 
@@ -231,7 +299,9 @@ module Tmuxinator
231
299
 
232
300
  def implode
233
301
  if yes?("Are you sure you want to delete all tmuxinator configs?", :red)
234
- FileUtils.remove_dir(Tmuxinator::Config.root)
302
+ Tmuxinator::Config.directories.each do |directory|
303
+ FileUtils.remove_dir(directory)
304
+ end
235
305
  say "Deleted all tmuxinator projects."
236
306
  end
237
307
  end
@@ -257,13 +327,13 @@ module Tmuxinator
257
327
 
258
328
  def doctor
259
329
  say "Checking if tmux is installed ==> "
260
- yes_no Tmuxinator::Config.installed?
330
+ yes_no Tmuxinator::Doctor.installed?
261
331
 
262
332
  say "Checking if $EDITOR is set ==> "
263
- yes_no Tmuxinator::Config.editor?
333
+ yes_no Tmuxinator::Doctor.editor?
264
334
 
265
335
  say "Checking if $SHELL is set ==> "
266
- yes_no Tmuxinator::Config.shell?
336
+ yes_no Tmuxinator::Doctor.shell?
267
337
  end
268
338
  end
269
339
  end
@@ -1,13 +1,35 @@
1
1
  module Tmuxinator
2
2
  class Config
3
3
  LOCAL_DEFAULT = "./.tmuxinator.yml".freeze
4
- NO_LOCAL_FILE_MSG = "Project file at ./.tmuxinator.yml doesn't exist."
4
+ NO_LOCAL_FILE_MSG =
5
+ "Project file at ./.tmuxinator.yml doesn't exist.".freeze
5
6
 
6
7
  class << self
7
- def root
8
- root_dir = File.expand_path("#{ENV['HOME']}/.tmuxinator")
9
- Dir.mkdir(root_dir) unless File.directory?(root_dir)
10
- root_dir
8
+ # The directory (created if needed) in which to store new projects
9
+ def directory
10
+ return environment if File.directory?(environment)
11
+ return xdg if File.directory?(xdg)
12
+ return home if File.directory?(home)
13
+ # No project directory specified or existant, default to XDG:
14
+ FileUtils::mkdir_p(xdg)
15
+ xdg
16
+ end
17
+
18
+ def home
19
+ ENV["HOME"] + "/.tmuxinator"
20
+ end
21
+
22
+ # Is ~/.config/tmuxinator unless $XDG_CONFIG_DIR is set
23
+ def xdg
24
+ XDG["CONFIG"].to_s + "/tmuxinator"
25
+ end
26
+
27
+ # $TMUXINATOR_CONFIG (and create directory) or "".
28
+ def environment
29
+ environment = ENV["TMUXINATOR_CONFIG"]
30
+ return "" if environment.to_s.empty? # variable is unset (nil) or blank
31
+ FileUtils::mkdir_p(environment) unless File.directory?(environment)
32
+ environment
11
33
  end
12
34
 
13
35
  def sample
@@ -15,69 +37,78 @@ module Tmuxinator
15
37
  end
16
38
 
17
39
  def default
18
- "#{ENV['HOME']}/.tmuxinator/default.yml"
40
+ "#{directory}/default.yml"
19
41
  end
20
42
 
21
43
  def default?
22
44
  exists?("default")
23
45
  end
24
46
 
25
- def installed?
26
- Kernel.system("type tmux > /dev/null")
27
- end
28
-
29
47
  def version
30
- `tmux -V`.split(" ")[1].to_f if installed?
48
+ `tmux -V`.split(" ")[1].to_f if Tmuxinator::Doctor.installed?
31
49
  end
32
50
 
33
51
  def default_path_option
34
52
  version && version < 1.8 ? "default-path" : "-c"
35
53
  end
36
54
 
37
- def editor?
38
- !ENV["EDITOR"].nil? && !ENV["EDITOR"].empty?
39
- end
40
-
41
- def shell?
42
- !ENV["SHELL"].nil? && !ENV["SHELL"].empty?
43
- end
44
-
45
55
  def exists?(name)
46
56
  File.exist?(project(name))
47
57
  end
48
58
 
49
- def project_in_root(name)
50
- projects = Dir.glob("#{root}/**/*.yml")
51
- projects.detect { |project| File.basename(project, ".yml") == name }
59
+ def local?
60
+ local_project
52
61
  end
53
62
 
54
- def local?
55
- project_in_local
63
+ # Pathname of given project searching only global directories
64
+ def global_project(name)
65
+ project_in(environment, name) ||
66
+ project_in(xdg, name) ||
67
+ project_in(home, name)
56
68
  end
57
69
 
58
- def project_in_local
70
+ def local_project
59
71
  [LOCAL_DEFAULT].detect { |f| File.exist?(f) }
60
72
  end
61
73
 
62
74
  def default_project(name)
63
- "#{root}/#{name}.yml"
75
+ "#{directory}/#{name}.yml"
64
76
  end
65
77
 
78
+ # Pathname of the given project
66
79
  def project(name)
67
- project_in_root(name) || project_in_local || default_project(name)
80
+ global_project(name) || local_project || default_project(name)
68
81
  end
69
82
 
70
83
  def template
71
84
  asset_path "template.erb"
72
85
  end
73
86
 
87
+ def stop_template
88
+ asset_path "template-stop.erb"
89
+ end
90
+
74
91
  def wemux_template
75
92
  asset_path "wemux_template.erb"
76
93
  end
77
94
 
95
+ # Sorted list of all .yml files, including duplicates
78
96
  def configs
79
- Dir["#{Tmuxinator::Config.root}/**/*.yml"].sort.map do |path|
80
- path.gsub("#{Tmuxinator::Config.root}/", "").gsub(".yml", "")
97
+ directories.map do |directory|
98
+ Dir["#{directory}/**/*.yml"].map do |path|
99
+ path.gsub("#{directory}/", "").gsub(".yml", "")
100
+ end
101
+ end.flatten.sort
102
+ end
103
+
104
+ # Existant directories which may contain project files
105
+ # Listed in search order
106
+ # Used by `implode` and `list` commands
107
+ def directories
108
+ if !environment.nil? && !environment.empty?
109
+ [environment]
110
+ else
111
+ [xdg, home].select { |d| File.directory? d }
81
112
  end
82
113
  end
83
114
 
@@ -89,7 +120,7 @@ module Tmuxinator
89
120
  project_file = if name.nil?
90
121
  raise NO_LOCAL_FILE_MSG \
91
122
  unless Tmuxinator::Config.local?
92
- project_in_local
123
+ local_project
93
124
  else
94
125
  raise "Project #{name} doesn't exist." \
95
126
  unless Tmuxinator::Config.exists?(name)
@@ -98,11 +129,24 @@ module Tmuxinator
98
129
  Tmuxinator::Project.load(project_file, options).validate!
99
130
  end
100
131
 
132
+ # Deprecated methods: ignore the 1st, use the 2nd
133
+ alias :root :directory
134
+ alias :project_in_root :global_project
135
+ alias :project_in_local :local_project
136
+
101
137
  private
102
138
 
103
139
  def asset_path(asset)
104
140
  "#{File.dirname(__FILE__)}/assets/#{asset}"
105
141
  end
142
+
143
+ # The first pathname of the project named 'name' found while
144
+ # recursively searching 'directory'
145
+ def project_in(directory, name)
146
+ return nil if String(directory).empty?
147
+ projects = Dir.glob("#{directory}/**/*.yml").sort
148
+ projects.detect { |project| File.basename(project, ".yml") == name }
149
+ end
106
150
  end
107
151
  end
108
152
  end