Module: TmuxConnector

Defined in:
lib/tmux-connector.rb,
lib/tmux-connector/host.rb,
lib/tmux-connector/layout.rb,
lib/tmux-connector/session.rb,
lib/tmux-connector/version.rb,
lib/tmux-connector/tmux_handler.rb,
lib/tmux-connector/commands/list.rb,
lib/tmux-connector/commands/send.rb,
lib/tmux-connector/commands/start.rb,
lib/tmux-connector/config_handler.rb,
lib/tmux-connector/command_handler.rb,
lib/tmux-connector/commands/delete.rb,
lib/tmux-connector/commands/resume.rb,
lib/tmux-connector/persistence_handler.rb

Defined Under Namespace

Classes: Delete, FakeHost, Host, Layout, List, Pane, Resume, Send, Session, Start, TmuxSession

Constant Summary collapse

TCON_DOC =
<<HERE
tcon enables establishing connections (ssh) to multiple servers and executing
commands on those servers. The sessions can be persisted (actually recreated)
even after computer restarts. Complex sessions with different layouts for
different kinds of servers can be easily created.

Usage:
  tcon start ( <config-file> | --quick-session=<qs-args> )
             [--ssh-config=<file>] [--session-name=<name>]
             [--purpose=<description>]
  tcon resume <session-name>
  tcon delete (<session-name> | --all)
  tcon list
  tcon send <session-name> ( <command> | --command-file=<file> )
            [ --server-filter=<filter> | --group-filter=<regex>
              | --filter=<regex> | --window=<index> ]
            [--verbose]
  tcon --help
  tcon --version

Options:
  <config-file>                Path to configuration file. Configuration file
                               describes how new session is started. YAML.
  <qs-args>                    Arguments needed to start a quick session.
  <session-name>               Name to identify the session. Must be unique.
  <command>                    Command to be executed on remote server[s].
  <regex>                      String that represents valid Ruby regex.
  <index>                      0-based index.
  <filter>                     Filter consisting of a valid ruby regex and
                               optionally of a special predicate.
                               For more information see README file.
  -q --quick-session=qs-args   Start the seesion without a configuration file.
                               Specify necessary argumenst instead.
  -s --ssh-config=file         Path to ssh config file. [default: ~/.ssh/config]
  -n --session-name=name       Name of the session.
  -p --purpose=description     Description of session's purpose.
  --all                        Delete all existing sessions.
  -f --server-filter=filter    Filter to select a subset of the servers via
                               host names.
  -g --group-filter=regex      Filter to select a subset of the servers via
                               group membership.
  -r --filter=regex            Filter to select a subset of the servers via
                               host names or group membership.
                               Combines --server-filter and --group-filter.
  -w --window=index            Select a window via (0-based) index.
  -c --command-file=file       File containing the list of commands to be
                               executed on remote server[s].
  -v --verbose                 Report how many servers were affected by the
                               send command.
  -h --help                    Show this screen.
  --version                    Show version.
HERE
QUICK_GROUP_ID =
'quick_group_id'
VERSION =
"1.0.8"
DEFAULT_CONFIG_FILE =
'lib/tmux-connector/default_config.yml'
COMMANDS =
%w[ start resume delete list send ]
BASE_DIR =
File.expand_path '~/.tmux-connector'
MAIN_FILE =
File.join BASE_DIR, '_sessions.yml'
SESSION_BASE_NAME =
"s#"

Class Method Summary collapse

Class Method Details

.delete_allObject



42
43
44
# File 'lib/tmux-connector/persistence_handler.rb', line 42

def self.delete_all()
  FileUtils.rm_rf BASE_DIR
end

.delete_all_tmux_sessionsObject



7
8
9
10
11
# File 'lib/tmux-connector/tmux_handler.rb', line 7

def self.delete_all_tmux_sessions()
  sessions_list = %x( tmux list-sessions 2> /dev/null)
  sessions = sessions_list.scan(/^([^:]+): /).map(&:first)
  sessions.each { |e| delete_tmux_session e }
end

.delete_session(session_name) ⇒ Object



60
61
62
63
64
65
66
67
68
# File 'lib/tmux-connector/persistence_handler.rb', line 60

def self.delete_session(session_name)
  data = list_sessions
  raise "session not found: '#{ session_name }'" if data[session_name].nil?

  file = data[session_name]['file']
  data.delete session_name
  open(MAIN_FILE, 'w') { |f| f.write data.to_yaml }
  File.delete(file) rescue nil
end

.delete_tmux_session(name) ⇒ Object



2
3
4
5
# File 'lib/tmux-connector/tmux_handler.rb', line 2

def self.delete_tmux_session(name)
  system "tmux detach -s #{ name } &> /dev/null"
  system "tmux kill-session -t #{ name } &> /dev/null"
end

.detect_command(args) ⇒ Object



18
19
20
21
# File 'lib/tmux-connector/command_handler.rb', line 18

def self.detect_command(args)
  COMMANDS.each { |e| return e if args[e] }
  raise 'unkonwn command'
end

.expand_layout(config) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/tmux-connector/config_handler.rb', line 44

def self.expand_layout(config)
  if config['tmux']
    config['tmux']['max-panes'] ||= 9
  else
    config['custom']['panes-flow'] ||= 'horizontal'
  end
end

.get_class(command) ⇒ Object



23
24
25
26
# File 'lib/tmux-connector/command_handler.rb', line 23

def self.get_class(command)
  class_name = command.split('-').map { |e| e.capitalize }.join
  return TmuxConnector.const_get class_name
end

.get_config(config_file) ⇒ Object



7
8
9
10
11
# File 'lib/tmux-connector/config_handler.rb', line 7

def self.get_config(config_file)
  config = read_config config_file
  process_config!(config) rescue raise 'configuration file parsing failed'
  return config
end

.get_new_session_name(args) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/tmux-connector/persistence_handler.rb', line 77

def self.get_new_session_name(args)
  specified = args["--session-name"]

  if File.exists? MAIN_FILE
    existing_names = list_sessions.keys

    if specified
      raise "session with name '#{ specified }' already exists." if existing_names.include? specified
      name = specified
    else
      re = /#{ SESSION_BASE_NAME }(\d+)/

      last_index = existing_names.reduce(0) do |acc, e|
        index = ( e.match(re)[1].to_i rescue 0 )
        [ acc, index].max
      end

      name = "#{ SESSION_BASE_NAME }#{ last_index + 1 }"
    end
  else
    name = specified || "#{ SESSION_BASE_NAME }1"
  end

  return name
end

.list_sessions(options = {}) ⇒ Object



70
71
72
73
74
75
# File 'lib/tmux-connector/persistence_handler.rb', line 70

def self.list_sessions(options={})
  unless options[:suppress_error]
    raise "session file (#{ MAIN_FILE }) not found" unless File.exist? MAIN_FILE
  end
  return ( YAML.load_file(MAIN_FILE) rescue {} ) || {}
end

.load_session(session_name) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/tmux-connector/persistence_handler.rb', line 22

def self.load_session(session_name)
  data = list_sessions
  raise "session not found: '#{ session_name }'" if data[session_name].nil?

  file = data[session_name]['file']
  raise "session file (#{ file }) not found" unless File.exist? file

  session = nil
  open(file, 'rb') { |f| session = Marshal.load f }
  return session
end

.main(input_args) ⇒ Object



61
62
63
64
65
66
67
68
69
70
# File 'lib/tmux-connector.rb', line 61

def self.main(input_args)
  begin
    args = Docopt.docopt TCON_DOC, argv: input_args, version: VERSION
    process_command args
  rescue Docopt::Exit => e
    puts e.message
  rescue => e
    puts "Something went wrong: #{ e.message }"
  end
end

.prepare_if_necessaryObject



34
35
36
37
38
39
40
# File 'lib/tmux-connector/persistence_handler.rb', line 34

def self.prepare_if_necessary()
  unless File.exists?(BASE_DIR) && File.directory?(BASE_DIR)
    Dir.mkdir(BASE_DIR)
  end

  FileUtils.touch MAIN_FILE unless File.exists? MAIN_FILE
end

.process_command(args) ⇒ Object



11
12
13
14
15
16
# File 'lib/tmux-connector/command_handler.rb', line 11

def self.process_command(args)
  command = detect_command args
  klass = get_class command
  command_obj = klass.new args
  command_obj.run
end

.process_config!(config) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/tmux-connector/config_handler.rb', line 21

def self.process_config!(config)
  config['regex'] = Regexp.new config['regex']
  config['reject-regex'] = Regexp.new(config['reject-regex']) if config['reject-regex']
  if (c = config['name'])
    c['regex-ignore-parts'] ||= []
    c['separator'] ||= '-'
    c['prefix'] ||= ''
  end

  layout = config['layout'] ||= {}

  if layout['default'].nil?
    layout['default'] = {
      'tmux' => { 'layout' => 'tiled' }
    }
  end
  expand_layout layout['default']

  if layout['group-layouts']
    layout['group-layouts'].each { |k, v| expand_layout v }
  end
end

.read_config(config_file) ⇒ Object



13
14
15
16
17
18
19
# File 'lib/tmux-connector/config_handler.rb', line 13

def self.read_config(config_file)
  full_path = File.expand_path config_file
  raise "configuration file (#{config_file}) not found" unless File.exist? full_path
  config = YAML.load_file full_path

  return config
end

.save_session(session_name, session_obj) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/tmux-connector/persistence_handler.rb', line 9

def self.save_session(session_name, session_obj)
  prepare_if_necessary

  created = Time.now.strftime('%Y-%m-%d %H:%M')

  file = File.join(BASE_DIR, "#{ session_name.gsub(/[^a-zA-Z0-9_-]+/, '-') }.bin")
  file.sub!('.bin', "__#{ created.gsub(/[ :]/, '_') }.bin") if File.exists? file

  update_main_file(session_name, created, file, session_obj.args['--purpose'])

  open(file, 'wb') { |f| Marshal.dump session_obj, f }
end

.update_main_file(session_name, created, file, purpose) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/tmux-connector/persistence_handler.rb', line 46

def self.update_main_file(session_name, created, file, purpose)
  data = list_sessions

  data[session_name] = {
    'created' => created,
    'file' => file
  }
  data[session_name]['purpose'] = purpose if purpose

  open(MAIN_FILE, 'w') { |f| f.write data.to_yaml }

  return file
end