teamocil 0.4.5 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +14 -4
- data/.rubocop.yml +48 -0
- data/.travis.yml +2 -2
- data/Gemfile +1 -1
- data/{LICENSE → LICENSE.md} +0 -0
- data/README.md +124 -166
- data/Rakefile +12 -27
- data/bin/teamocil +3 -4
- data/lib/teamocil.rb +51 -4
- data/lib/teamocil/cli.rb +23 -90
- data/lib/teamocil/command/new_window.rb +16 -0
- data/lib/teamocil/command/rename_session.rb +9 -0
- data/lib/teamocil/command/rename_window.rb +9 -0
- data/lib/teamocil/command/select_layout.rb +9 -0
- data/lib/teamocil/command/select_pane.rb +9 -0
- data/lib/teamocil/command/select_window.rb +9 -0
- data/lib/teamocil/command/send_keys.rb +9 -0
- data/lib/teamocil/command/send_keys_to_pane.rb +9 -0
- data/lib/teamocil/command/split_window.rb +15 -0
- data/lib/teamocil/layout.rb +58 -36
- data/lib/teamocil/tmux/pane.rb +15 -0
- data/lib/teamocil/tmux/session.rb +28 -0
- data/lib/teamocil/tmux/window.rb +47 -0
- data/lib/teamocil/utils/closed_struct.rb +16 -0
- data/lib/teamocil/utils/option_parser.rb +56 -0
- data/lib/teamocil/version.rb +1 -1
- data/teamocil.gemspec +15 -16
- metadata +27 -54
- data/examples/four-splits.yml +0 -8
- data/examples/one-and-three-splits.yml +0 -8
- data/examples/six-splits.yml +0 -10
- data/examples/two-horizontal-splits.yml +0 -6
- data/examples/two-vertical-splits.yml +0 -6
- data/lib/teamocil/error.rb +0 -6
- data/lib/teamocil/layout/pane.rb +0 -66
- data/lib/teamocil/layout/session.rb +0 -30
- data/lib/teamocil/layout/window.rb +0 -77
- data/spec/cli_spec.rb +0 -79
- data/spec/fixtures/.my-fancy-layouts-directory/sample-3.yml +0 -10
- data/spec/fixtures/.teamocil/sample-2.yml +0 -10
- data/spec/fixtures/.teamocil/sample.yml +0 -10
- data/spec/fixtures/layouts.yml +0 -76
- data/spec/layout_spec.rb +0 -229
- data/spec/mock/cli.rb +0 -35
- data/spec/mock/layout.rb +0 -16
- data/spec/spec_helper.rb +0 -17
data/Rakefile
CHANGED
@@ -1,28 +1,13 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
desc "Generate YARD Documentation"
|
16
|
-
YARD::Rake::YardocTask.new do |task|
|
17
|
-
task.options = [
|
18
|
-
"-o", File.expand_path("../doc", __FILE__),
|
19
|
-
"--readme=README.md",
|
20
|
-
"--markup=markdown",
|
21
|
-
"--markup-provider=maruku",
|
22
|
-
"--no-private",
|
23
|
-
"--no-cache",
|
24
|
-
"--protected",
|
25
|
-
"--title=Teamocil",
|
26
|
-
]
|
27
|
-
task.files = ["lib/**/*.rb"]
|
1
|
+
require 'bundler'
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
desc 'Start an IRB session with the gem'
|
6
|
+
task :console do
|
7
|
+
$LOAD_PATH.unshift File.expand_path('..', __FILE__)
|
8
|
+
require 'teamocil'
|
9
|
+
require 'irb'
|
10
|
+
|
11
|
+
ARGV.clear
|
12
|
+
IRB.start
|
28
13
|
end
|
data/bin/teamocil
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w(.. lib))
|
2
3
|
|
3
|
-
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
|
-
|
5
|
-
require 'yaml'
|
6
4
|
require 'teamocil'
|
7
5
|
|
8
|
-
Teamocil::CLI.new(ARGV, ENV)
|
6
|
+
runner = Teamocil::CLI.new(arguments: ARGV, environment: ENV)
|
7
|
+
runner.run!
|
data/lib/teamocil.rb
CHANGED
@@ -1,7 +1,54 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
|
1
|
+
require 'yaml'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
# Version
|
5
|
+
require 'teamocil/version'
|
6
|
+
|
7
|
+
# Utils
|
8
|
+
require 'teamocil/utils/closed_struct'
|
9
|
+
require 'teamocil/utils/option_parser'
|
10
|
+
|
11
|
+
# Teamocil
|
12
|
+
require 'teamocil/layout'
|
13
|
+
require 'teamocil/cli'
|
14
|
+
|
15
|
+
# Command classes
|
16
|
+
require 'teamocil/command/new_window'
|
17
|
+
require 'teamocil/command/rename_session'
|
18
|
+
require 'teamocil/command/rename_window'
|
19
|
+
require 'teamocil/command/select_layout'
|
20
|
+
require 'teamocil/command/select_pane'
|
21
|
+
require 'teamocil/command/select_window'
|
22
|
+
require 'teamocil/command/send_keys'
|
23
|
+
require 'teamocil/command/send_keys_to_pane'
|
24
|
+
require 'teamocil/command/split_window'
|
25
|
+
|
26
|
+
# Tmux classes
|
27
|
+
require 'teamocil/tmux/session'
|
28
|
+
require 'teamocil/tmux/window'
|
29
|
+
require 'teamocil/tmux/pane'
|
5
30
|
|
6
31
|
module Teamocil
|
32
|
+
class << self
|
33
|
+
attr_reader :options
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.bail(*args)
|
37
|
+
print '[teamocil error] '
|
38
|
+
puts(*args)
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.puts(*args)
|
43
|
+
STDOUT.puts(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.system(*args)
|
47
|
+
Kernel.system(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.parse_options!(arguments:)
|
51
|
+
parser = OptionParser.new(arguments: arguments)
|
52
|
+
@options = parser.parsed_options
|
53
|
+
end
|
7
54
|
end
|
data/lib/teamocil/cli.rb
CHANGED
@@ -1,105 +1,38 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'erb'
|
4
|
-
|
5
1
|
module Teamocil
|
6
|
-
|
7
|
-
|
8
|
-
attr_accessor :layout, :layouts
|
9
|
-
|
10
|
-
# Initialize a new run of `tmux`
|
11
|
-
#
|
12
|
-
# @param argv [Hash] the command line parameters hash (usually `ARGV`).
|
13
|
-
# @param env [Hash] the environment variables hash (usually `ENV`).
|
14
|
-
def initialize(argv, env)
|
15
|
-
parse_options! argv
|
16
|
-
layout_path = env["TEAMOCIL_PATH"] || File.join("#{env["HOME"]}", ".teamocil")
|
2
|
+
class CLI < ClosedStruct.new(:arguments, :environment)
|
3
|
+
DIRECTORY = '$HOME/.teamocil'
|
17
4
|
|
18
|
-
|
19
|
-
|
20
|
-
return print_layouts
|
21
|
-
end
|
22
|
-
|
23
|
-
if @options[:layout].nil? && argv[0].nil?
|
24
|
-
bail "You must supply a layout for teamocil to use. See `teamocil --help` for more options."
|
25
|
-
end
|
5
|
+
def run!
|
6
|
+
Teamocil.parse_options!(arguments: arguments)
|
26
7
|
|
27
|
-
|
8
|
+
# List available layouts
|
9
|
+
return Layout.print_available_layouts(directory: root) if Teamocil.options[:list]
|
28
10
|
|
29
|
-
|
30
|
-
|
31
|
-
Kernel.system("${EDITOR:-vim} \"#{file}\"")
|
32
|
-
elsif @options[:show]
|
33
|
-
::FileUtils.touch file unless File.exists?(file)
|
34
|
-
Kernel.system("cat \"#{file}\"")
|
35
|
-
else
|
36
|
-
bail "There is no file \"#{file}\"." unless File.exists?(file)
|
37
|
-
bail "You must be in a tmux session to use teamocil." unless env["TMUX"]
|
11
|
+
# Fetch the Layout object
|
12
|
+
layout = Layout.new(path: layout_file_path)
|
38
13
|
|
39
|
-
|
14
|
+
# Open layout file in $EDITOR
|
15
|
+
return layout.edit! if Teamocil.options[:edit]
|
40
16
|
|
41
|
-
|
17
|
+
# Output the layout raw content
|
18
|
+
return layout.show! if Teamocil.options[:show]
|
42
19
|
|
43
|
-
|
44
|
-
|
45
|
-
rescue Teamocil::Error::LayoutError => e
|
46
|
-
bail e.message
|
47
|
-
end
|
48
|
-
@layout.execute_commands(@layout.generate_commands)
|
49
|
-
end
|
20
|
+
# Nothing? Let’s execute this layout!
|
21
|
+
layout.execute!
|
50
22
|
end
|
51
23
|
|
52
|
-
|
53
|
-
def parse_options!(args)
|
54
|
-
@options = {}
|
55
|
-
opts = ::OptionParser.new do |opts|
|
56
|
-
opts.banner = "Usage: teamocil [options] <layout>
|
57
|
-
|
58
|
-
Options:
|
59
|
-
"
|
60
|
-
opts.on("--here", "Set up the first window in the current window") do
|
61
|
-
@options[:here] = true
|
62
|
-
end
|
63
|
-
|
64
|
-
opts.on("--edit", "Edit the YAML layout file instead of using it") do
|
65
|
-
@options[:edit] = true
|
66
|
-
end
|
67
|
-
|
68
|
-
opts.on("--layout [LAYOUT]", "Use a specific layout file, instead of `~/.teamocil/<layout>.yml`") do |layout|
|
69
|
-
@options[:layout] = layout
|
70
|
-
end
|
24
|
+
private
|
71
25
|
|
72
|
-
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
opts.on("--show", "Show the content of the layout file instead of executing it") do
|
77
|
-
@options[:show] = true
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
opts.parse! args
|
82
|
-
end
|
83
|
-
|
84
|
-
# Return an array of available layouts
|
85
|
-
#
|
86
|
-
# @param path [String] the path used to look for layouts
|
87
|
-
def get_layouts(path)
|
88
|
-
Dir.glob(File.join(path, "*.yml")).map { |file| File.basename(file).gsub(/\..+$/, "") }.sort
|
26
|
+
def root
|
27
|
+
DIRECTORY.sub('$HOME', environment['HOME'])
|
89
28
|
end
|
90
29
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# Print an error message and exit the utility
|
98
|
-
#
|
99
|
-
# @param msg [Mixed] something to print before exiting.
|
100
|
-
def bail(msg)
|
101
|
-
STDERR.puts "[teamocil] #{msg}"
|
102
|
-
exit 1
|
30
|
+
def layout_file_path
|
31
|
+
if layout = Teamocil.options[:layout]
|
32
|
+
layout
|
33
|
+
else
|
34
|
+
File.join(root, "#{arguments.first}.yml")
|
35
|
+
end
|
103
36
|
end
|
104
37
|
end
|
105
38
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Teamocil
|
2
|
+
module Command
|
3
|
+
class NewWindow < ClosedStruct.new(:name, :root)
|
4
|
+
def to_s
|
5
|
+
"new-window #{options.join(' ')}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def options
|
9
|
+
[].tap do |options|
|
10
|
+
options << "-n '#{name}'" if name
|
11
|
+
options << "-c '#{root}'" if root
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/teamocil/layout.rb
CHANGED
@@ -1,44 +1,66 @@
|
|
1
|
-
require "teamocil/layout/session"
|
2
|
-
require "teamocil/layout/window"
|
3
|
-
require "teamocil/layout/pane"
|
4
|
-
|
5
1
|
module Teamocil
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
2
|
+
class Layout < ClosedStruct.new(:path, :options)
|
3
|
+
def execute!
|
4
|
+
if Teamocil.options[:debug]
|
5
|
+
Teamocil.puts(shell_commands.join("\n"))
|
6
|
+
else
|
7
|
+
Teamocil.system(shell_commands.join('; '))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def show!
|
12
|
+
Teamocil.puts(raw_content)
|
13
|
+
end
|
14
|
+
|
15
|
+
def edit!
|
16
|
+
Teamocil.system("$EDITOR #{path}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.print_available_layouts(directory:)
|
20
|
+
files = Dir.glob(File.join(directory, '*.yml'))
|
21
|
+
|
22
|
+
files.map! do |file|
|
23
|
+
extname = File.extname(file)
|
24
|
+
File.basename(file).gsub(extname, '')
|
25
|
+
end
|
26
|
+
|
27
|
+
# Always return files in alphabetical order, even if `Dir.glob` almost
|
28
|
+
# always does it
|
29
|
+
files.sort!
|
30
|
+
|
31
|
+
Teamocil.puts(files)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def shell_commands
|
37
|
+
commands = parsed_layout.as_tmux
|
38
|
+
commands.flatten.map { |command| "tmux #{command}" }
|
39
|
+
end
|
40
|
+
|
41
|
+
def parsed_layout
|
42
|
+
begin
|
43
|
+
yaml_content = YAML.load(raw_content)
|
44
|
+
rescue
|
45
|
+
Teamocil.bail("There was a YAML error when parsing `#{path}`")
|
46
|
+
end
|
47
|
+
|
48
|
+
if valid?
|
49
|
+
Session.new(yaml_content)
|
32
50
|
else
|
33
|
-
|
51
|
+
Teamocil.bail("The layout at `#{path}` is not valid.")
|
34
52
|
end
|
35
53
|
end
|
36
54
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
55
|
+
def valid?
|
56
|
+
# TODO: Actually validate if the layout is valid
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def raw_content
|
61
|
+
File.read(path)
|
62
|
+
rescue
|
63
|
+
Teamocil.bail("Cannot find a layout at `#{path}`")
|
42
64
|
end
|
43
65
|
end
|
44
66
|
end
|