teamocil 0.4.5 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -4
  3. data/.rubocop.yml +48 -0
  4. data/.travis.yml +2 -2
  5. data/Gemfile +1 -1
  6. data/{LICENSE → LICENSE.md} +0 -0
  7. data/README.md +124 -166
  8. data/Rakefile +12 -27
  9. data/bin/teamocil +3 -4
  10. data/lib/teamocil.rb +51 -4
  11. data/lib/teamocil/cli.rb +23 -90
  12. data/lib/teamocil/command/new_window.rb +16 -0
  13. data/lib/teamocil/command/rename_session.rb +9 -0
  14. data/lib/teamocil/command/rename_window.rb +9 -0
  15. data/lib/teamocil/command/select_layout.rb +9 -0
  16. data/lib/teamocil/command/select_pane.rb +9 -0
  17. data/lib/teamocil/command/select_window.rb +9 -0
  18. data/lib/teamocil/command/send_keys.rb +9 -0
  19. data/lib/teamocil/command/send_keys_to_pane.rb +9 -0
  20. data/lib/teamocil/command/split_window.rb +15 -0
  21. data/lib/teamocil/layout.rb +58 -36
  22. data/lib/teamocil/tmux/pane.rb +15 -0
  23. data/lib/teamocil/tmux/session.rb +28 -0
  24. data/lib/teamocil/tmux/window.rb +47 -0
  25. data/lib/teamocil/utils/closed_struct.rb +16 -0
  26. data/lib/teamocil/utils/option_parser.rb +56 -0
  27. data/lib/teamocil/version.rb +1 -1
  28. data/teamocil.gemspec +15 -16
  29. metadata +27 -54
  30. data/examples/four-splits.yml +0 -8
  31. data/examples/one-and-three-splits.yml +0 -8
  32. data/examples/six-splits.yml +0 -10
  33. data/examples/two-horizontal-splits.yml +0 -6
  34. data/examples/two-vertical-splits.yml +0 -6
  35. data/lib/teamocil/error.rb +0 -6
  36. data/lib/teamocil/layout/pane.rb +0 -66
  37. data/lib/teamocil/layout/session.rb +0 -30
  38. data/lib/teamocil/layout/window.rb +0 -77
  39. data/spec/cli_spec.rb +0 -79
  40. data/spec/fixtures/.my-fancy-layouts-directory/sample-3.yml +0 -10
  41. data/spec/fixtures/.teamocil/sample-2.yml +0 -10
  42. data/spec/fixtures/.teamocil/sample.yml +0 -10
  43. data/spec/fixtures/layouts.yml +0 -76
  44. data/spec/layout_spec.rb +0 -229
  45. data/spec/mock/cli.rb +0 -35
  46. data/spec/mock/layout.rb +0 -16
  47. data/spec/spec_helper.rb +0 -17
data/Rakefile CHANGED
@@ -1,28 +1,13 @@
1
- require "bundler"
2
- Bundler.require(:development)
3
-
4
- require "bundler/gem_tasks"
5
- require "rspec/core/rake_task"
6
-
7
- task :default => :spec
8
-
9
- desc "Run all specs"
10
- RSpec::Core::RakeTask.new(:spec) do |task|
11
- task.pattern = "spec/**/*_spec.rb"
12
- task.rspec_opts = "--colour --format=documentation"
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 "teamocil/version"
2
- require "teamocil/layout"
3
- require "teamocil/cli"
4
- require "teamocil/error"
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
- # This class handles interaction with the `tmux` utility.
7
- class CLI
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
- if @options.include?(:list)
19
- @layouts = get_layouts(layout_path)
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
- file = @options[:layout] || ::File.join(layout_path, "#{argv[0]}.yml")
8
+ # List available layouts
9
+ return Layout.print_available_layouts(directory: root) if Teamocil.options[:list]
28
10
 
29
- if @options[:edit]
30
- ::FileUtils.touch file unless File.exists?(file)
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
- yaml = ERB.new(File.read(file)).result
14
+ # Open layout file in $EDITOR
15
+ return layout.edit! if Teamocil.options[:edit]
40
16
 
41
- @layout = Teamocil::Layout.new(YAML.load(yaml), @options)
17
+ # Output the layout raw content
18
+ return layout.show! if Teamocil.options[:show]
42
19
 
43
- begin
44
- @layout.compile!
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
- # Parse the command line options
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
- opts.on("--list", "List all available layouts in `~/.teamocil/`") do
73
- @options[:list] = true
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
- # Print each layout on a single line
92
- def print_layouts
93
- STDOUT.puts @layouts.join("\n")
94
- exit 0
95
- end
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
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class RenameSession < ClosedStruct.new(:name)
4
+ def to_s
5
+ "rename-session '#{name}'"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class RenameWindow < ClosedStruct.new(:name)
4
+ def to_s
5
+ "rename-window '#{name}'"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class SelectLayout < ClosedStruct.new(:layout)
4
+ def to_s
5
+ "select-layout '#{layout}'"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class SelectPane < ClosedStruct.new(:index)
4
+ def to_s
5
+ "select-pane -t #{index}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class SelectWindow < ClosedStruct.new(:index)
4
+ def to_s
5
+ "select-window -t #{index}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class SendKeys < ClosedStruct.new(:keys)
4
+ def to_s
5
+ "send-keys '#{keys}'"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Teamocil
2
+ module Command
3
+ class SendKeysToPane < ClosedStruct.new(:index, :keys)
4
+ def to_s
5
+ "send-keys -t #{index} '#{keys}'"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Teamocil
2
+ module Command
3
+ class SplitWindow < ClosedStruct.new(:root)
4
+ def to_s
5
+ "split-window #{options.join(' ')}"
6
+ end
7
+
8
+ def options
9
+ [].tap do |options|
10
+ options << "-c '#{root}'" if root
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
- # This class act as a wrapper around a tmux YAML layout file
7
- class Layout
8
- attr_reader :session
9
-
10
- # Initialize a new layout from a hash
11
- #
12
- # @param layout [Hash] the parsed layout
13
- # @param options [Hash] some options
14
- def initialize(layout, options={})
15
- @layout = layout
16
- @options = options
17
- end
18
-
19
- # Generate tmux commands based on the data found in the layout file
20
- #
21
- # @return [Array] an array of shell commands to send
22
- def generate_commands
23
- @session.generate_commands
24
- end
25
-
26
- # Compile the layout into objects
27
- #
28
- # @return [Session]
29
- def compile!
30
- if @layout["session"].nil?
31
- @session = Session.new @options, "windows" => @layout["windows"]
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
- @session = Session.new @options, @layout["session"]
51
+ Teamocil.bail("The layout at `#{path}` is not valid.")
34
52
  end
35
53
  end
36
54
 
37
- # Execute each command in the shell
38
- #
39
- # @param commands [Array] an array of complete commands to send to the shell
40
- def execute_commands(commands)
41
- `#{commands.join("; ")}`
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