tmuxinator 0.9.0 → 0.10.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 (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