teamocil 0.4.5 → 1.0

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.
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