Class: Sesh::TmuxControl

Inherits:
Object
  • Object
show all
Defined in:
lib/sesh/tmux_control.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project, options = {}) ⇒ TmuxControl

Returns a new instance of TmuxControl.



9
10
11
12
13
# File 'lib/sesh/tmux_control.rb', line 9

def initialize(project, options={})
  @project = project || Inferences::infer_project_from_current_directory
  @options = {}.merge(DEFAULT_OPTIONS[:tmux]).merge options
  @socket_file = @options[:socket_file] || "/tmp/#{@project}.sock"
end

Class Method Details

.get_running_projectsObject



15
16
17
18
19
20
21
# File 'lib/sesh/tmux_control.rb', line 15

def self.get_running_projects
  output = Sesh.format_and_run_command <<-BASH
    ps aux | grep tmux | grep "sesh begin" | grep -v "[g]rep" \
           | sed -e "s/.*\\/tmp\\/\\(.*\\)\\.sock.*/\\1/"
  BASH
  output.lines.map(&:strip)
end

Instance Method Details

#already_running?Boolean

Returns:

  • (Boolean)


22
# File 'lib/sesh/tmux_control.rb', line 22

def already_running?; self.class.get_running_projects.include? @project end

#begin_tmuxinator_session!Object



100
101
# File 'lib/sesh/tmux_control.rb', line 100

def begin_tmuxinator_session!
%x[env TMUX='' mux start #{@project}] end

#connected_client_devicesObject



128
129
130
# File 'lib/sesh/tmux_control.rb', line 128

def connected_client_devices
  `tmux -S "#{@socket_file}" list-clients 2>/dev/null | cut -d : -f 1 | cut -d / -f 3`.strip.lines.map(&:strip)
end

#connected_clientsObject



144
145
# File 'lib/sesh/tmux_control.rb', line 144

def connected_clients
connected_client_devices.map{|devname| get_ip_from_device(devname) } end

#connection_commandObject



48
# File 'lib/sesh/tmux_control.rb', line 48

def connection_command; "tmux -S #{@socket_file} a" end

#disconnect_client!(identifier) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/sesh/tmux_control.rb', line 154

def disconnect_client!(identifier)
  identifier = '127.0.0.1' if identifier.nil? || identifier.length == 0
  if identifier.to_i.to_s == identifier.to_s # It's an integer
    disconnect_client_by_index! identifier.to_i - 1
  elsif identifier =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ # It's an ip
    disconnect_client_by_ip! identifier
  elsif identifier =~ /.*@.*/ || identifier =~ /\.local$/
    ping_output = `ping -a -c 1 #{identifier} 2>&1`.strip.lines
    if $? && ping_output.length > 1 && ping_output[1] =~ / from /
      resolved_ip = ping_output[1].split(' from ')[1].split(': ')[0]
      disconnect_client_by_ip! resolved_ip
    else
      puts "Ping output: #{ping_output}"
    end
  else
    ssh_identifier =
      `awk '/Host #{identifier}/ {getline; print $2}' ~/.ssh/config`.strip
    return disconnect_client!(ssh_identifier) if ssh_identifier.length > 0
    fatal("Client")
  end
end

#disconnect_client_by_device!(devname) ⇒ Object



146
147
# File 'lib/sesh/tmux_control.rb', line 146

def disconnect_client_by_device!(devname)
`tmux -S "#{@socket_file}" detach-client -t "/dev/#{devname}"`.strip.length == 0 end

#disconnect_client_by_index!(index) ⇒ Object



152
153
# File 'lib/sesh/tmux_control.rb', line 152

def disconnect_client_by_index!(index)
disconnect_client_by_device! connected_client_devices[index] end

#disconnect_client_by_ip!(ip) ⇒ Object



148
149
150
151
# File 'lib/sesh/tmux_control.rb', line 148

def disconnect_client_by_ip!(ip)
device = get_device_from_ip(ip)
Logger.fatal("#{ip} is not connected to project \"#{@project}\".") if device.nil?
disconnect_client_by_device! device end

#do_shell_operation!(options = ) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/sesh/tmux_control.rb', line 199

def do_shell_operation!(options=DEFAULT_OPTIONS[:shell])
  unless options[:spec].nil?
    rspec_cmd = if options[:rspec_prefix].nil? then 'rspec'
                else "#{options[:rspec_prefix]} rspec" end
    options[:command] ||= "#{rspec_cmd} #{options[:spec]}"
  end
  if options[:and_return]
    options[:command] = "return_to_sesh; #{options[:command]}"
  end
  inferred_location = Inferences.infer_tmux_location
  # puts "Inferred location: #{inferred_location}"
  if options[:pane].nil?
    # puts inferred_location.inspect
    if inferred_location[:project] != @project
      interrupt_and_send_command_to_project! options[:command]
      return 0
    else do_shell_operation_here! options[:command] end
  else
    if inferred_location != { project: @project, pane: options[:pane] }
      interrupt_and_send_command_to_pane! options[:pane], options[:command]
      return 0
    else do_shell_operation_here! options[:command] end
  end
end

#do_shell_operation_here!(cmd, allow_zeus_retry = true) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/sesh/tmux_control.rb', line 223

def do_shell_operation_here!(cmd, allow_zeus_retry=true)
  # system cmd
  raw = ''
  PTY.spawn(cmd) do |reader, writer|
    reader.sync = true
    writer.sync = true
    Thread.new { loop { writer.print(STDIN.getc.chr) } }
    while (c=reader.getc)
      raw << c
      print c
    end
  end
  if allow_zeus_retry && raw =~ /Could not find command "rspec /
    return do_shell_operation_here! cmd, false
  end
  $?.exitstatus
end

#get_device_from_ip(ip) ⇒ Object



136
137
138
139
140
141
142
143
# File 'lib/sesh/tmux_control.rb', line 136

def get_device_from_ip(ip)
  return if ( connected_devs = connected_client_devices ).length == 0
  return connected_devs.find{|d| get_ip_from_device(ip) == ip } if ip == '127.0.0.1'
  who_lines = `who -a 2> /dev/null | grep "#{ Regexp.escape("(#{ip})") }"`.strip.lines
  puts connected_devs.inspect
  return if who_lines.length == 0
  connected_devs.find{|d| who_lines.find{|l| l =~ / #{d} / } }
end

#get_ip_from_device(devname) ⇒ Object



131
132
133
134
135
# File 'lib/sesh/tmux_control.rb', line 131

def get_ip_from_device(devname)
  ip_line = `who -a 2> /dev/null | grep " #{devname} "`.strip
  return '127.0.0.1' unless ip_line.length > 0 && ip_line =~ /\)$/
  ip_line.split('(')[-1][0..-2]
end

#interrupt_and_send_command_to_pane!(pane, command) ⇒ Object



194
195
# File 'lib/sesh/tmux_control.rb', line 194

def interrupt_and_send_command_to_pane!(pane, command)
interrupt_and_send_keys_to_pane!(pane, "\"#{command}\" Enter") end

#interrupt_and_send_command_to_project!(command) ⇒ Object



190
191
# File 'lib/sesh/tmux_control.rb', line 190

def interrupt_and_send_command_to_project!(command)
send_interrupt!; send_command_to_project!(command) end

#interrupt_and_send_keys_to_pane!(pane, keys) ⇒ Object



185
186
187
# File 'lib/sesh/tmux_control.rb', line 185

def interrupt_and_send_keys_to_pane!(pane, keys)
move_cursor_to_pane_and_interrupt! pane
send_keys_to_project! keys end

#interrupt_and_send_keys_to_project!(keys) ⇒ Object



180
181
# File 'lib/sesh/tmux_control.rb', line 180

def interrupt_and_send_keys_to_project!(keys)
send_interrupt!; send_keys_to_project! keys end

#issue_stop_command!Object



41
42
43
44
45
46
# File 'lib/sesh/tmux_control.rb', line 41

def issue_stop_command!
  # puts 'issuing stop command...'
  output = `ps -ef | grep "[t]mux -u attach-session -t #{Regexp.escape(@project)}\\$" | grep -v grep | awk '{print $2}' | xargs kill -9`
  puts "stop command issued! Output: #{output}"
  output
end

#kill_process!(pid) ⇒ Object

if File.exists? @options

File.readlines(@options[:pids_file]).each{|pid|
  puts "Killing #{pid}"
  kill_process! pid }
File.delete @options[:pids_file]

end



98
# File 'lib/sesh/tmux_control.rb', line 98

def kill_process!(pid); `kill -9 #{pid} 2>&1` end

#kill_running_processesObject



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/sesh/tmux_control.rb', line 82

def kill_running_processes
  pane_count = `tmux list-panes -s -F "\#{pane_pid} \#{pane_current_command}" -t "#{@project}" 2>/dev/null`.strip.lines.count
  return if pane_count == 0
  pane_count.times{|i| move_cursor_to_pane_and_interrupt! i; sleep 0.1 }
  sleep 1; puts 'Killing pids from session...'
  obtain_pids_from_session.each{|pid|
    puts `ps aux | grep #{pid} | grep -v grep`.strip
    kill_process! pid }
  puts 'All pids from session have been killed.'
  # if File.exists? @options[:pids_file]
  #   File.readlines(@options[:pids_file]).each{|pid|
  #     puts "Killing #{pid}"
  #     kill_process! pid }
  #   File.delete @options[:pids_file]
  # end
end

#list_running_processesObject



78
79
80
81
# File 'lib/sesh/tmux_control.rb', line 78

def list_running_processes
  obtain_pids_from_session.each{|pid|
    puts `ps aux | grep #{pid} | grep -v grep`.strip }
end

#move_cursor_to_pane!(pane) ⇒ Object



196
# File 'lib/sesh/tmux_control.rb', line 196

def move_cursor_to_pane!(pane); send_keys_to_project! "C-a q #{pane}" end

#move_cursor_to_pane_and_interrupt!(pane) ⇒ Object



197
198
# File 'lib/sesh/tmux_control.rb', line 197

def move_cursor_to_pane_and_interrupt!(pane)
move_cursor_to_pane!(pane); send_interrupt! end

#obtain_child_pids_from_pid(pid) ⇒ Object



71
72
73
74
75
76
# File 'lib/sesh/tmux_control.rb', line 71

def obtain_child_pids_from_pid(pid)
  output = `ps -ef | grep #{pid} | grep -v grep | grep -v "sesh begin #{@project}" | awk '{print $2}'`.strip.lines.map(&:strip)
  output -= [pid]
  output += output.map{|cpid| obtain_child_pids_from_pid(cpid) - [pid] }.flatten
  output.reverse
end

#obtain_pids_from_sessionObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/sesh/tmux_control.rb', line 50

def obtain_pids_from_session
  tmux_processes =
    `tmux list-panes -s -F "\#{pane_pid} \#{pane_current_command}" -t "#{@project}" 2> /dev/null | grep -v tmux | awk '{print $1}'`.strip.lines.map(&:strip) +
    `tmux -S "#{@socket_file}" list-panes -s -F "\#{pane_pid} \#{pane_current_command}" 2> /dev/null | grep -v tmux | awk '{print $1}'`.strip.lines.map(&:strip)
  tmux_processes.delete_if {|pid| `ps aux | grep #{pid} | grep -v grep | grep -v "sesh begin #{@project}"`.strip.length == 0 }
  puts; puts "Tmux Processes:"
  tmux_processes.each{|pid| puts `ps aux | grep #{pid} | grep -v grep`.strip }
  return [] unless tmux_processes.any?
  spring_processes = []; other_processes = []
  spring_app_pid = `ps -ef | grep "[s]pring app .*| #{@project} |" | grep -v grep | awk '{print $2}'`.strip
  spring_processes += `ps -ef | grep #{spring_app_pid} | grep -v grep | grep -v "[s]pring app" | awk '{print $2}'`.strip.lines.map(&:strip) if spring_app_pid.length > 0
  spring_processes += `ps -ef | grep "[s]pring.*| #{@project} |" | grep -v grep | awk '{print $2}'`.strip.lines.map(&:strip)
  puts; puts 'Spring Processes:'
  spring_processes.each{|pid| puts `ps aux | grep #{pid} | grep -v grep`.strip }
  tmux_processes.each{|pid|
    other_processes += obtain_child_pids_from_pid(pid) - tmux_processes }
  puts; puts 'Other Processes:'
  other_processes.each{|pid| puts `ps aux | grep #{pid} | grep -v grep`.strip }
  puts
  ( spring_processes + other_processes + tmux_processes ).uniq
end

#optionsObject



243
# File 'lib/sesh/tmux_control.rb', line 243

def options; @options end

#projectObject

Getter methods for passthru to SshControl class



242
# File 'lib/sesh/tmux_control.rb', line 242

def project; @project end

#project_name_matcherObject

‘ps aux | grep “##project_name_matcher”`.strip.length > 0 end



25
26
27
# File 'lib/sesh/tmux_control.rb', line 25

def project_name_matcher
  "[t]mux.*#{Regexp.escape(@project)}.*"
end

#restart_project!Object



126
# File 'lib/sesh/tmux_control.rb', line 126

def restart_project!; stop_project!; sleep 0.5; start_project! end

#send_command_to_pane!(pane, command) ⇒ Object



192
193
# File 'lib/sesh/tmux_control.rb', line 192

def send_command_to_pane!(pane, command)
send_keys_to_pane! pane, "\"#{command}\" Enter" end

#send_command_to_project!(command) ⇒ Object



188
189
# File 'lib/sesh/tmux_control.rb', line 188

def send_command_to_project!(command)
send_keys_to_project! "\"#{command}\" Enter" end

#send_interrupt!Object



179
# File 'lib/sesh/tmux_control.rb', line 179

def send_interrupt!; send_keys_to_project! 'C-c' end

#send_keys_to_pane!(pane, keys) ⇒ Object



182
183
184
# File 'lib/sesh/tmux_control.rb', line 182

def send_keys_to_pane!(pane, keys)
move_cursor_to_pane! pane
send_keys_to_project! keys end

#send_keys_to_project!(keys) ⇒ Object



176
177
178
# File 'lib/sesh/tmux_control.rb', line 176

def send_keys_to_project!(keys)
  `tmux -S "#{@socket_file}" send-keys #{keys}`.strip.length == 0
end

#start_project!Object



103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/sesh/tmux_control.rb', line 103

def start_project!
  if already_running?
    Logger.fatal "Sesh project '#{@project}' is already running!" end
  Logger.info "Starting Sesh project '#{@project}'..."
  kill_running_processes
  if issue_start_command! && Logger.show_progress_until(-> { already_running? })
    sleep 1
    if already_running?
      Logger.success 'Sesh started successfully.'
      puts
    else Logger.fatal 'Sesh failed to start!' end
  else Logger.fatal 'Sesh failed to start after ten seconds!' end
end

#stop_project!Object



116
117
118
119
120
121
122
123
124
125
# File 'lib/sesh/tmux_control.rb', line 116

def stop_project!
  unless already_running?
    Logger.fatal "Sesh project '#{@project}' is not running!" end
  Logger.info "Stopping Sesh project '#{@project}'..."
  kill_running_processes
  if issue_stop_command! && Logger.show_progress_until(-> { !already_running? })
    Logger.success 'Sesh stopped successfully.'
    puts
  else Logger.fatal 'Sesh failed to stop after ten seconds!' end
end