automux 0.0.1
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.
- data/.gitignore +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +20 -0
- data/LICENSE.txt +22 -0
- data/VERSION +1 -0
- data/automux.gemspec +18 -0
- data/bin/automux +22 -0
- data/data/automux/blueprints/default.yml +26 -0
- data/data/automux/recipes/default.sh.erb +46 -0
- data/features/custom_recipes.feature +63 -0
- data/features/error_messages.feature +8 -0
- data/features/general_usage.feature +180 -0
- data/features/managing_blueprints.feature +66 -0
- data/features/runtime_options.feature +84 -0
- data/features/session_and_window_options.feature +67 -0
- data/features/session_hooks.feature +68 -0
- data/features/setup_windows.feature +22 -0
- data/features/step_definitions/custom_recipes_steps.rb +9 -0
- data/features/step_definitions/managing_blueprints_steps.rb +9 -0
- data/features/step_definitions/setup_windows_steps.rb +18 -0
- data/features/step_definitions/shared_steps.rb +43 -0
- data/features/support/env.rb +8 -0
- data/features/support/hooks.rb +4 -0
- data/features/support/transformations.rb +6 -0
- data/lib/automux.rb +9 -0
- data/lib/automux/cache.rb +10 -0
- data/lib/automux/cache/blueprint.rb +20 -0
- data/lib/automux/cache/recipe.rb +27 -0
- data/lib/automux/controller.rb +14 -0
- data/lib/automux/controller/base.rb +18 -0
- data/lib/automux/controller/blueprints.rb +45 -0
- data/lib/automux/controller/messages.rb +11 -0
- data/lib/automux/controller/recipes.rb +39 -0
- data/lib/automux/controller/setup.rb +11 -0
- data/lib/automux/controller/support.rb +5 -0
- data/lib/automux/controller/support/filters.rb +55 -0
- data/lib/automux/controller/support/rendering.rb +54 -0
- data/lib/automux/core.rb +11 -0
- data/lib/automux/core/base.rb +7 -0
- data/lib/automux/core/blueprint.rb +31 -0
- data/lib/automux/core/error.rb +15 -0
- data/lib/automux/core/hook.rb +26 -0
- data/lib/automux/core/option.rb +20 -0
- data/lib/automux/core/recipe.rb +12 -0
- data/lib/automux/core/support.rb +6 -0
- data/lib/automux/core/support/custom_accessors.rb +15 -0
- data/lib/automux/core/support/hooks_helper.rb +28 -0
- data/lib/automux/core/support/options_helper.rb +18 -0
- data/lib/automux/core/tmux.rb +6 -0
- data/lib/automux/core/tmux/pane.rb +19 -0
- data/lib/automux/core/tmux/session.rb +137 -0
- data/lib/automux/core/tmux/window.rb +76 -0
- data/lib/automux/initializers.rb +2 -0
- data/lib/automux/initializers/custom_hooks.rb +4 -0
- data/lib/automux/initializers/setup_caches.rb +3 -0
- data/lib/automux/library.rb +5 -0
- data/lib/automux/library/mini_erb.rb +22 -0
- data/lib/automux/library/yaml_parser.rb +41 -0
- data/lib/automux/paths.rb +41 -0
- data/lib/automux/setup.rb +0 -0
- data/lib/automux/views/blueprints/copy.sh.erb +2 -0
- data/lib/automux/views/blueprints/create.sh.erb +2 -0
- data/lib/automux/views/blueprints/edit.sh.erb +1 -0
- data/lib/automux/views/messages/error.sh.erb +3 -0
- data/lib/automux/views/setup/clone_defaults.sh.erb +21 -0
- data/test/session_test.rb +16 -0
- data/test/support/common_methods.rb +0 -0
- data/test/support/factories.rb +13 -0
- data/test/support/hash_factory.rb +19 -0
- data/test/support/test_helper.rb +7 -0
- metadata +136 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Automux
|
2
|
+
module Core
|
3
|
+
module Tmux
|
4
|
+
class Pane < Base
|
5
|
+
attr_reader :command, :window
|
6
|
+
private :window
|
7
|
+
|
8
|
+
def initialize(window, command)
|
9
|
+
@window = window
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
def index
|
14
|
+
window.panes.find_index(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Automux
|
2
|
+
module Core
|
3
|
+
module Tmux
|
4
|
+
class Session < Base
|
5
|
+
include Support::HooksHelper
|
6
|
+
include Support::OptionsHelper
|
7
|
+
|
8
|
+
attr_reader :data, :name, :root, :data_windows, :flags, :base_index
|
9
|
+
dup_attr_reader :windows, :hooks, :options
|
10
|
+
private :data, :data_windows
|
11
|
+
|
12
|
+
def initialize(blueprint_data)
|
13
|
+
@data = blueprint_data
|
14
|
+
@name = data['name']
|
15
|
+
@root = data['root'] || '.'
|
16
|
+
@data_windows = data['windows'] || []
|
17
|
+
@data_hooks = data['hooks'] || []
|
18
|
+
@windows = []
|
19
|
+
@flags = data['flags']
|
20
|
+
@hooks = []
|
21
|
+
@data_options = data['options'] || []
|
22
|
+
@options = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_server
|
26
|
+
%[tmux start-server]
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_session
|
30
|
+
%[#{ tmux_with_flags } new-session -d -s #{ name }]
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_option(option)
|
34
|
+
%[tmux set-option #{ option.name } '#{ option.value }']
|
35
|
+
end
|
36
|
+
|
37
|
+
def new_window(window)
|
38
|
+
%[tmux new-window -t #{ name }:#{ window.index } 2> /dev/null]
|
39
|
+
end
|
40
|
+
|
41
|
+
def rename_window(window, window_name = window.name)
|
42
|
+
%[tmux rename-window -t #{ name }:#{ window.index } #{ window_name }]
|
43
|
+
end
|
44
|
+
|
45
|
+
def send_keys(identifier, command)
|
46
|
+
window = get_window(identifier)
|
47
|
+
%[tmux send-keys -t #{ name }:#{ window.index } "#{ command }" C-m]
|
48
|
+
end
|
49
|
+
|
50
|
+
def attach_session
|
51
|
+
%[#{ tmux_with_flags } attach-session -t #{ name }]
|
52
|
+
end
|
53
|
+
|
54
|
+
def select_layout(window)
|
55
|
+
%[tmux select-layout -t #{ name }:#{ window.index } #{ window.layout }]
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_pane
|
59
|
+
%[tmux split-window]
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_binding
|
63
|
+
binding
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup
|
67
|
+
setup_options
|
68
|
+
setup_base_index
|
69
|
+
setup_windows
|
70
|
+
setup_hooks
|
71
|
+
end
|
72
|
+
|
73
|
+
def window_indexes
|
74
|
+
@windows.map(&:index).compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def next_available_window_index
|
78
|
+
n = base_index + number_of_windows
|
79
|
+
(Array(base_index..n) - window_indexes).first
|
80
|
+
end
|
81
|
+
|
82
|
+
private ###
|
83
|
+
|
84
|
+
def tmux_with_flags
|
85
|
+
%[tmux #{ flags }].strip
|
86
|
+
end
|
87
|
+
|
88
|
+
def number_of_windows
|
89
|
+
@windows.length
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_window(window)
|
93
|
+
@windows << window
|
94
|
+
end
|
95
|
+
|
96
|
+
# When multiple windows have been assigned the same index by the user, the additional window indexes will be removed.
|
97
|
+
def remove_duplicate_indexes(original_data)
|
98
|
+
non_indexed_data = original_data.select { |h| h['index'].to_s.empty? }
|
99
|
+
indexed_data = original_data - non_indexed_data
|
100
|
+
|
101
|
+
uniq_indexed_data = indexed_data.uniq { |h| h['index'] }
|
102
|
+
conflicting_data = indexed_data - uniq_indexed_data
|
103
|
+
removed_index_data = conflicting_data.each { |h| h.delete('index') }
|
104
|
+
|
105
|
+
non_indexed_data + uniq_indexed_data + removed_index_data
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_windows(windows_data)
|
109
|
+
windows_data.each do |window_data|
|
110
|
+
window = Automux::Core::Tmux::Window.new(self, window_data)
|
111
|
+
add_window(window) if window.opted_in?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_windows
|
116
|
+
windows_data = remove_duplicate_indexes(data_windows)
|
117
|
+
add_windows(windows_data)
|
118
|
+
@windows.each(&:update_index)
|
119
|
+
@windows.each(&:setup)
|
120
|
+
end
|
121
|
+
|
122
|
+
def setup_base_index
|
123
|
+
@base_index = 0
|
124
|
+
if option = options.find { |option| option.name == 'base-index' }
|
125
|
+
@base_index = option.value.to_i
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def get_window(identifier)
|
130
|
+
return identifier if identifier.is_a?(Window)
|
131
|
+
|
132
|
+
@windows.find { |window| [window.index, window.name].include?(identifier) }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Automux
|
2
|
+
module Core
|
3
|
+
module Tmux
|
4
|
+
class Window < Base
|
5
|
+
include Support::HooksHelper
|
6
|
+
include Support::OptionsHelper
|
7
|
+
|
8
|
+
attr_reader :data, :session, :index, :root
|
9
|
+
dup_attr_reader :panes, :hooks, :options
|
10
|
+
private :data, :session
|
11
|
+
|
12
|
+
def initialize(session, data)
|
13
|
+
@session = session
|
14
|
+
@data = data
|
15
|
+
@opt = data['opt']
|
16
|
+
@index = data['index']
|
17
|
+
@root = data['root']
|
18
|
+
@data_hooks = data['hooks'] || []
|
19
|
+
@hooks = []
|
20
|
+
@data_options = data['options'] || []
|
21
|
+
@options = []
|
22
|
+
@panes = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_option(option)
|
26
|
+
%[tmux set-window-option -t #{ session.name }:#{ index } #{ option.name } '#{ option.value }']
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
data['name']
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup
|
34
|
+
setup_options
|
35
|
+
setup_panes
|
36
|
+
setup_hooks
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_panes?
|
40
|
+
!@panes.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def layout
|
44
|
+
data['layout']
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_index
|
48
|
+
@index ||= session.next_available_window_index
|
49
|
+
end
|
50
|
+
|
51
|
+
def opted_in?
|
52
|
+
return true if @opt.nil?
|
53
|
+
|
54
|
+
@opt
|
55
|
+
end
|
56
|
+
|
57
|
+
def change_root_command
|
58
|
+
%[cd #{ root }]
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_binding
|
62
|
+
binding
|
63
|
+
end
|
64
|
+
|
65
|
+
private ###
|
66
|
+
|
67
|
+
def setup_panes
|
68
|
+
[data['panes']].flatten.each do |command|
|
69
|
+
pane = Automux::Core::Tmux::Pane.new(self, command)
|
70
|
+
@panes << pane
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Automux
|
4
|
+
module Library
|
5
|
+
class MiniErb < ERB
|
6
|
+
|
7
|
+
def initialize(template)
|
8
|
+
modified_template = erb_parseable_template(template)
|
9
|
+
super(modified_template, nil, '%<>')
|
10
|
+
end
|
11
|
+
|
12
|
+
private ###
|
13
|
+
|
14
|
+
# - [1, 2, 3] => % [1, 2, 3]
|
15
|
+
# = [1, 2, 3] => <%= [1, 2, 3] %>
|
16
|
+
def erb_parseable_template(string)
|
17
|
+
string.gsub(/^\s*-(.+)$/, '%\1').
|
18
|
+
gsub(/^\s*=(.+)$/, '<%=\1 %> ')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Automux
|
5
|
+
module Library
|
6
|
+
class YamlParser
|
7
|
+
class << self
|
8
|
+
def load_file(file)
|
9
|
+
data_string = File.read(file)
|
10
|
+
opts_replaced_string = replace_opts_with_user_input(data_string)
|
11
|
+
YAML.load(opts_replaced_string)
|
12
|
+
end
|
13
|
+
|
14
|
+
private ###
|
15
|
+
|
16
|
+
# Scan blueprint for patterns like "-\w" or '-\w' to get a options list.
|
17
|
+
# The options list is used by OptionsParser to read the options from commandline.
|
18
|
+
def replace_opts_with_user_input(string)
|
19
|
+
blueprint_opts = string.scan(/['|"]-(\w:?)['|"]/m).flatten
|
20
|
+
user_input_values = get_options(blueprint_opts)
|
21
|
+
replace_opts(string, user_input_values)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replace patterns like "-\w" with matching commandline option.
|
25
|
+
def replace_opts(string, values)
|
26
|
+
values.each do |k, v|
|
27
|
+
# Interpolate #{ v } - to substitute literal booleans.
|
28
|
+
string.gsub!(/['|"]-(#{ k }):?['|"]/m, "#{ v }")
|
29
|
+
end
|
30
|
+
|
31
|
+
string
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_options(opts)
|
35
|
+
ARGV.shift while ARGV[0].to_s.match(/^\w/)
|
36
|
+
ARGV.getopts(*opts)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Automux
|
2
|
+
module Paths
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def default_blueprint
|
6
|
+
File.join(data, 'blueprints', 'default.yml')
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_recipe
|
10
|
+
File.join(data, 'recipes', 'default.sh.erb')
|
11
|
+
end
|
12
|
+
|
13
|
+
def data
|
14
|
+
File.join(root, 'data/automux')
|
15
|
+
end
|
16
|
+
|
17
|
+
def root
|
18
|
+
File.expand_path('../../../', __FILE__)
|
19
|
+
end
|
20
|
+
|
21
|
+
def views
|
22
|
+
'lib/automux/views'
|
23
|
+
end
|
24
|
+
|
25
|
+
def user_assets
|
26
|
+
File.join(ENV['HOME'], '.automux')
|
27
|
+
end
|
28
|
+
|
29
|
+
%w(blueprints recipes).each do |name|
|
30
|
+
define_method(name) do
|
31
|
+
Dir[File.join(user_assets, name, '*')]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
%w(blueprints recipes).each do |name|
|
36
|
+
define_method("#{ name }_container") do
|
37
|
+
File.join(user_assets, name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
$EDITOR <%= path %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
setup() {
|
2
|
+
mkdir -p <%= Automux::Paths.blueprints_container %>
|
3
|
+
mkdir -p <%= Automux::Paths.recipes_container %>
|
4
|
+
|
5
|
+
cp <%= Automux::Paths.default_blueprint %> <%= Automux::Paths.blueprints_container %>
|
6
|
+
cp <%= Automux::Paths.default_recipe %> <%= Automux::Paths.recipes_container %>
|
7
|
+
|
8
|
+
echo default files copied
|
9
|
+
}
|
10
|
+
|
11
|
+
if [ -d <%= Automux::Paths.blueprints_container %> ]; then
|
12
|
+
read -r -p "This will replace the default blueprint and recipe under ~/.automux. Continue? [Y/n] " response
|
13
|
+
|
14
|
+
case $response in
|
15
|
+
[yY][eE][sS]|[yY])
|
16
|
+
setup
|
17
|
+
;;
|
18
|
+
esac
|
19
|
+
else
|
20
|
+
setup
|
21
|
+
fi
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'support/test_helper'
|
2
|
+
|
3
|
+
describe 'Session' do
|
4
|
+
it "should assign window indexes" do
|
5
|
+
|
6
|
+
windows_data = [nil, nil, 1, 0].map do |i|
|
7
|
+
HashFactory.build(:window, index: i)
|
8
|
+
end
|
9
|
+
data = HashFactory.build(:session, windows: windows_data)
|
10
|
+
|
11
|
+
session = Automux::Core::Tmux::Session.new(data)
|
12
|
+
session.setup_windows_and_hooks
|
13
|
+
|
14
|
+
session.window_indexes.must_equal [2, 3, 1, 0]
|
15
|
+
end
|
16
|
+
end
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class HashFactory
|
2
|
+
@@factories = {}
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def define(name, &block)
|
6
|
+
@@factories[name.to_s] = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(name, attrs = {})
|
10
|
+
defaults = stringify(@@factories[name.to_s].call)
|
11
|
+
attributes = stringify(attrs)
|
12
|
+
defaults.merge(attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def stringify(data)
|
16
|
+
Hash[data.map { |k, v| [k.to_s, v] }]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|