Compare commits
No commits in common. "gh-pages" and "master" have entirely different histories.
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
*.gem
|
||||||
|
.rvmrc
|
||||||
|
.local*
|
||||||
|
.yardoc
|
||||||
|
doc
|
||||||
|
pkg
|
2
.travis.yml
Normal file
2
.travis.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
rvm: 1.9.2
|
||||||
|
script: "bundle exec rake spec"
|
32
Gemfile.lock
Normal file
32
Gemfile.lock
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
PATH
|
||||||
|
remote: .
|
||||||
|
specs:
|
||||||
|
teamocil (0.3.2)
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: http://rubygems.org/
|
||||||
|
specs:
|
||||||
|
diff-lcs (1.1.3)
|
||||||
|
maruku (0.6.0)
|
||||||
|
syntax (>= 1.0.0)
|
||||||
|
rake (0.9.2)
|
||||||
|
rspec (2.6.0)
|
||||||
|
rspec-core (~> 2.6.0)
|
||||||
|
rspec-expectations (~> 2.6.0)
|
||||||
|
rspec-mocks (~> 2.6.0)
|
||||||
|
rspec-core (2.6.4)
|
||||||
|
rspec-expectations (2.6.0)
|
||||||
|
diff-lcs (~> 1.1.2)
|
||||||
|
rspec-mocks (2.6.0)
|
||||||
|
syntax (1.0.0)
|
||||||
|
yard (0.7.5)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
maruku
|
||||||
|
rake
|
||||||
|
rspec
|
||||||
|
teamocil!
|
||||||
|
yard
|
3
LICENSE
Normal file
3
LICENSE
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Copyright 2011-2012 Rémi Prévost.
|
||||||
|
You may use this work without restrictions, as long as this notice is included.
|
||||||
|
The work is provided "as is" without warranty of any kind, neither express nor implied.
|
204
README.md
Normal file
204
README.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Teamocil
|
||||||
|
|
||||||
|
Teamocil is a simple tool used to automatically create sessions, windows and splits in [tmux](http://tmux.sourceforge.net/) with YAML files.
|
||||||
|
|
||||||
|
[![Build Status](https://secure.travis-ci.org/remiprev/teamocil.png)](http://travis-ci.org/remiprev/teamocil)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ gem install teamocil
|
||||||
|
$ mkdir ~/.teamocil
|
||||||
|
$ teamocil --edit sample
|
||||||
|
$ tmux
|
||||||
|
$ teamocil sample
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
* `--here` opens the session in the current window, it doesn’t create an empty first window.
|
||||||
|
* `--layout` takes a custom file path to a YAML layout file.
|
||||||
|
* `--edit` opens the layout file (whether or not `--layout` is used) with `$EDITOR`.
|
||||||
|
* `--list` lists all available layouts.
|
||||||
|
|
||||||
|
## Layout file structure
|
||||||
|
|
||||||
|
A layout file is a single YAML file located in `~/.teamocil` (eg. `~/.teamocil/my-project.yml`).
|
||||||
|
|
||||||
|
### Session
|
||||||
|
|
||||||
|
You can wrap your entire layout file in a `session` and Teamocil will rename the current session (so that you can find it more easily when running `tmux list-sessions`) before creating your windows.
|
||||||
|
|
||||||
|
#### Keys
|
||||||
|
|
||||||
|
* `name` (the name of the session)
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
session:
|
||||||
|
name: "my-awesome-session"
|
||||||
|
windows:
|
||||||
|
[windows list]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
If you are not using a top-level `session` key, then the first key of your layout file will be `windows`, an array of window items.
|
||||||
|
|
||||||
|
#### Item keys
|
||||||
|
|
||||||
|
* `name` (the name that will appear in `tmux` statusbar)
|
||||||
|
* `root` (the directory in which every split will be created)
|
||||||
|
* `filters` (a hash of `before` and `after` commands to run for each split)
|
||||||
|
* `splits` (an array of split items)
|
||||||
|
* `options` (a hash of tmux options, see `man tmux` for a list)
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
windows:
|
||||||
|
- name: "my-first-window"
|
||||||
|
options:
|
||||||
|
synchronize-panes: true
|
||||||
|
root: "~/Projects/foo-www"
|
||||||
|
filters:
|
||||||
|
before:
|
||||||
|
- "echo 'Let’s use ruby-1.9.2 for each split in this window.'"
|
||||||
|
- "rvm use 1.9.2"
|
||||||
|
splits:
|
||||||
|
[splits list]
|
||||||
|
- name: "my-second-window"
|
||||||
|
root: "~/Projects/foo-api"
|
||||||
|
splits:
|
||||||
|
[splits list]
|
||||||
|
- name: "my-third-window"
|
||||||
|
root: "~/Projects/foo-daemons"
|
||||||
|
splits:
|
||||||
|
[splits list]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Splits
|
||||||
|
|
||||||
|
Every window must define an array of splits that will be created within it. A vertical or horizontal split will be created, depending on whether the `width` or `height` parameter is used.
|
||||||
|
|
||||||
|
#### Item keys
|
||||||
|
|
||||||
|
* `cmd` (the commands to initially execute in the split)
|
||||||
|
* `width` (the split width, in percentage)
|
||||||
|
* `height` (the split width, in percentage)
|
||||||
|
* `target` (the split to set focus on, before creating the current one)
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
windows:
|
||||||
|
- name: "my-first-window"
|
||||||
|
root: "~/Projects/foo-www"
|
||||||
|
filters:
|
||||||
|
before: "rvm use 1.9.2"
|
||||||
|
after: "echo 'I am done initializing this split.'"
|
||||||
|
splits:
|
||||||
|
- cmd: "git status"
|
||||||
|
- cmd: "bundle exec rails server --port 4000"
|
||||||
|
width: 50
|
||||||
|
- cmd:
|
||||||
|
- "sudo service memcached start"
|
||||||
|
- "sudo service mongodb start"
|
||||||
|
height: 50
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout examples
|
||||||
|
|
||||||
|
See more example files in the `examples` directory.
|
||||||
|
|
||||||
|
### Simple two splits window
|
||||||
|
|
||||||
|
#### Content of `~/.teamocil/sample-1.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
windows:
|
||||||
|
- name: "sample-two-splits"
|
||||||
|
root: "~/Code/sample/www"
|
||||||
|
splits:
|
||||||
|
- cmd: ["pwd", "ls -la"]
|
||||||
|
- cmd: "rails server --port 3000"
|
||||||
|
width: 50
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Result of `$ teamocil sample-1`
|
||||||
|
|
||||||
|
.------------------.------------------.
|
||||||
|
| (0) | (1) |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
'------------------'------------------'
|
||||||
|
|
||||||
|
### Four tiled splits window
|
||||||
|
|
||||||
|
#### Content of `~/.teamocil/sample-2.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
windows:
|
||||||
|
- name: "sample-four-splits"
|
||||||
|
root: "~/Code/sample/www"
|
||||||
|
splits:
|
||||||
|
- cmd: "pwd"
|
||||||
|
- cmd: "pwd"
|
||||||
|
width: 50
|
||||||
|
- cmd: "pwd"
|
||||||
|
height: 50
|
||||||
|
target: "bottom-right"
|
||||||
|
- cmd: "pwd"
|
||||||
|
height: 50
|
||||||
|
target: "bottom-left"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Result of `$ teamocil sample-2`
|
||||||
|
|
||||||
|
.------------------.------------------.
|
||||||
|
| (0) | (1) |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
|------------------|------------------|
|
||||||
|
| (3) | (2) |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
'------------------'------------------'
|
||||||
|
|
||||||
|
## Extras
|
||||||
|
|
||||||
|
### Zsh autocompletion
|
||||||
|
|
||||||
|
To get autocompletion when typing `teamocil <Tab>` in a zsh session, add this line to your `~/.zshrc` file:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
compctl -g '~/.teamocil/*(:t:r)' teamocil
|
||||||
|
```
|
||||||
|
|
||||||
|
## Todo list
|
||||||
|
|
||||||
|
* Making sure the layout is valid before executing it (ie. throw exceptions).
|
||||||
|
* Add more specs.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
Feel free to contribute and submit issues/pull requests [on GitHub](https://github.com/remiprev/teamocil/issues), just like these fine folks did:
|
||||||
|
|
||||||
|
* Samuel Garneau ([garno](https://github.com/garno))
|
||||||
|
* Jimmy Bourassa ([jbourassa](https://github.com/jbourassa))
|
||||||
|
|
||||||
|
Take a look at the `spec` folder before you do, and make sure `bundle exec rake spec` passes after your modifications :)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Teamocil is © 2011-2012 [Rémi Prévost](http://exomel.com) and may be freely distributed under the [LITL license](http://litl.info/). See the `LICENSE` file.
|
28
Rakefile
Normal file
28
Rakefile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
require "bundler"
|
||||||
|
Bundler.require(:development)
|
||||||
|
|
||||||
|
require "bundler/gem_tasks"
|
||||||
|
require "rspec/core/rake_task"
|
||||||
|
|
||||||
|
task :default => :spec
|
||||||
|
|
||||||
|
desc "Run all specs"
|
||||||
|
RSpec::Core::RakeTask.new(:spec) do |task| # {{{
|
||||||
|
task.pattern = "spec/**/*_spec.rb"
|
||||||
|
task.rspec_opts = "--colour --format=documentation"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
desc "Generate YARD Documentation"
|
||||||
|
YARD::Rake::YardocTask.new do |task| # {{{
|
||||||
|
task.options = [
|
||||||
|
"-o", File.expand_path("../doc", __FILE__),
|
||||||
|
"--readme=README.md",
|
||||||
|
"--markup=markdown",
|
||||||
|
"--markup-provider=maruku",
|
||||||
|
"--no-private",
|
||||||
|
"--no-cache",
|
||||||
|
"--protected",
|
||||||
|
"--title=Teamocil",
|
||||||
|
]
|
||||||
|
task.files = ["lib/**/*.rb"]
|
||||||
|
end # }}}
|
8
bin/teamocil
Executable file
8
bin/teamocil
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
||||||
|
|
||||||
|
require 'yaml'
|
||||||
|
require 'teamocil'
|
||||||
|
|
||||||
|
Teamocil::CLI.new(ARGV, ENV)
|
12
examples/simple-four-splits.yml
Normal file
12
examples/simple-four-splits.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
windows:
|
||||||
|
- name: simple-four-splits
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'first split'"
|
||||||
|
- cmd: "echo 'second split'"
|
||||||
|
width: 50
|
||||||
|
- cmd: "echo 'fourth split'"
|
||||||
|
height: 50
|
||||||
|
target: bottom-right
|
||||||
|
- cmd: "echo 'third split'"
|
||||||
|
height: 50
|
||||||
|
target: bottom-left
|
12
examples/simple-one-and-three-splits.yml
Normal file
12
examples/simple-one-and-three-splits.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
windows:
|
||||||
|
- name: simple-one-and-three-splits
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'first split'"
|
||||||
|
- cmd: "echo 'second split'"
|
||||||
|
width: 50
|
||||||
|
- cmd: "echo 'third split'"
|
||||||
|
height: 66
|
||||||
|
target: bottom-right
|
||||||
|
- cmd: "echo 'fourth split'"
|
||||||
|
height: 50
|
||||||
|
target: bottom-right
|
18
examples/simple-six-splits.yml
Normal file
18
examples/simple-six-splits.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
windows:
|
||||||
|
- name: simple-six-splits
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'first split'"
|
||||||
|
- cmd: "echo 'second split'"
|
||||||
|
width: 50
|
||||||
|
- cmd: "echo 'fourth split'"
|
||||||
|
height: 66
|
||||||
|
target: bottom-right
|
||||||
|
- cmd: "echo 'third split'"
|
||||||
|
height: 66
|
||||||
|
target: bottom-left
|
||||||
|
- cmd: "echo 'sixth split'"
|
||||||
|
height: 50
|
||||||
|
target: bottom-right
|
||||||
|
- cmd: "echo 'fifth split'"
|
||||||
|
height: 50
|
||||||
|
target: bottom-left
|
6
examples/simple-two-horitonzal-splits.yml
Normal file
6
examples/simple-two-horitonzal-splits.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
windows:
|
||||||
|
- name: simple-two-horizontal-splits
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'first split'"
|
||||||
|
- cmd: "echo 'second split'"
|
||||||
|
height: 50
|
6
examples/simple-two-vertical-splits.yml
Normal file
6
examples/simple-two-vertical-splits.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
windows:
|
||||||
|
- name: simple-two-vertical-splits
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'first split'"
|
||||||
|
- cmd: "echo 'second split'"
|
||||||
|
width: 50
|
15
index.html
15
index.html
@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title></title>
|
|
||||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/documentup/latest.min.js"></script>
|
|
||||||
<script>
|
|
||||||
DocumentUp.document({
|
|
||||||
repo: "remiprev/teamocil",
|
|
||||||
travis: true
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body></body>
|
|
||||||
</html>
|
|
5
lib/teamocil.rb
Normal file
5
lib/teamocil.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module Teamocil
|
||||||
|
VERSION = "0.3.2"
|
||||||
|
autoload :Layout, "teamocil/layout"
|
||||||
|
autoload :CLI, "teamocil/cli"
|
||||||
|
end
|
94
lib/teamocil/cli.rb
Normal file
94
lib/teamocil/cli.rb
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
require 'optparse'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
module Teamocil
|
||||||
|
# This class handles interaction with the `tmux` utility.
|
||||||
|
class CLI
|
||||||
|
|
||||||
|
attr_accessor :layout, :layouts
|
||||||
|
|
||||||
|
# Initialize a new run of `tmux`
|
||||||
|
#
|
||||||
|
# @param argv [Hash] the command line parameters hash (usually `ARGV`).
|
||||||
|
# @param env [Hash] the environment variables hash (usually `ENV`).
|
||||||
|
def initialize(argv, env) # {{{
|
||||||
|
parse_options! argv
|
||||||
|
layout_path = File.join("#{env["HOME"]}", ".teamocil")
|
||||||
|
|
||||||
|
if @options.include?(:list)
|
||||||
|
@layouts = get_layouts(layout_path)
|
||||||
|
return print_layouts
|
||||||
|
end
|
||||||
|
|
||||||
|
if @options.include?(:layout)
|
||||||
|
file = @options[:layout]
|
||||||
|
else
|
||||||
|
file = ::File.join(layout_path, "#{argv[0]}.yml")
|
||||||
|
end
|
||||||
|
|
||||||
|
if @options[:edit]
|
||||||
|
::FileUtils.touch file unless File.exists?(file)
|
||||||
|
|
||||||
|
Kernel.system("$EDITOR \"#{file}\"")
|
||||||
|
else
|
||||||
|
bail "There is no file \"#{file}\"" unless File.exists?(file)
|
||||||
|
bail "You must be in a tmux session to use teamocil" unless env["TMUX"]
|
||||||
|
|
||||||
|
parsed_layout = YAML.load_file(file)
|
||||||
|
@layout = Teamocil::Layout.new(parsed_layout, @options)
|
||||||
|
@layout.compile!
|
||||||
|
@layout.execute_commands(@layout.generate_commands)
|
||||||
|
end
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Parse the command line options
|
||||||
|
def parse_options!(args) # {{{
|
||||||
|
@options = {}
|
||||||
|
opts = ::OptionParser.new do |opts|
|
||||||
|
opts.banner = "Usage: teamocil [options] <layout>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
"
|
||||||
|
opts.on("--here", "Set up the first window in the current window") do
|
||||||
|
@options[:here] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.on("--edit", "Edit the YAML layout file instead of using it") do
|
||||||
|
@options[:edit] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.on("--layout [LAYOUT]", "Use a specific layout file, instead of `~/.teamocil/<layout>.yml`") do |layout|
|
||||||
|
@options[:layout] = layout
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.on("--list", "List all available layouts in `~/.teamocil/`") do
|
||||||
|
@options[:list] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
opts.parse! args
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Return an array of available layouts
|
||||||
|
#
|
||||||
|
# @param path [String] the path used to look for layouts
|
||||||
|
def get_layouts(path) # {{{
|
||||||
|
Dir.glob(File.join(path, "*.yml")).map { |file| File.basename(file).gsub(/\..+$/, "") }.sort
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Print each layout on a single line
|
||||||
|
def print_layouts # {{{
|
||||||
|
STDOUT.puts @layouts.join("\n")
|
||||||
|
exit 0
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Print an error message and exit the utility
|
||||||
|
#
|
||||||
|
# @param msg [Mixed] something to print before exiting.
|
||||||
|
def bail(msg) # {{{
|
||||||
|
STDERR.puts "[teamocil] #{msg}"
|
||||||
|
exit 1
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
46
lib/teamocil/layout.rb
Normal file
46
lib/teamocil/layout.rb
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
module Teamocil
|
||||||
|
|
||||||
|
# This class act as a wrapper around a tmux YAML layout file
|
||||||
|
class Layout
|
||||||
|
autoload :Session, "teamocil/layout/session"
|
||||||
|
autoload :Window, "teamocil/layout/window"
|
||||||
|
autoload :Split, "teamocil/layout/split"
|
||||||
|
|
||||||
|
attr_reader :session
|
||||||
|
|
||||||
|
# Initialize a new layout from a hash
|
||||||
|
#
|
||||||
|
# @param layout [Hash] the parsed layout
|
||||||
|
# @param options [Hash] some options
|
||||||
|
def initialize(layout, options={}) # {{{
|
||||||
|
@layout = layout
|
||||||
|
@options = options
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Generate tmux commands based on the data found in the layout file
|
||||||
|
#
|
||||||
|
# @return [Array] an array of shell commands to send
|
||||||
|
def generate_commands # {{{
|
||||||
|
@session.generate_commands
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Compile the layout into objects
|
||||||
|
#
|
||||||
|
# @return [Session]
|
||||||
|
def compile! # {{{
|
||||||
|
if @layout["session"].nil?
|
||||||
|
@session = Session.new @options, "windows" => @layout["windows"]
|
||||||
|
else
|
||||||
|
@session = Session.new @options, @layout["session"]
|
||||||
|
end
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Execute each command in the shell
|
||||||
|
#
|
||||||
|
# @param commands [Array] an array of complete commands to send to the shell
|
||||||
|
def execute_commands(commands) # {{{
|
||||||
|
`#{commands.join("; ")}`
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
31
lib/teamocil/layout/session.rb
Normal file
31
lib/teamocil/layout/session.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
module Teamocil
|
||||||
|
class Layout
|
||||||
|
|
||||||
|
# This class represents a session within tmux
|
||||||
|
class Session
|
||||||
|
attr_reader :options, :windows, :name
|
||||||
|
|
||||||
|
# Initialize a new tmux session
|
||||||
|
#
|
||||||
|
# @param options [Hash] the options, mostly passed by the CLI
|
||||||
|
# @param attrs [Hash] the session data from the layout file
|
||||||
|
def initialize(options, attrs={}) # {{{
|
||||||
|
@name = attrs["name"]
|
||||||
|
@windows = attrs["windows"].each_with_index.map { |window, window_index| Window.new(self, window_index, window) }
|
||||||
|
@options = options
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Generate commands to send to tmux
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
def generate_commands # {{{
|
||||||
|
commands = []
|
||||||
|
commands << "tmux rename-session \"#{@name}\"" unless @name.nil?
|
||||||
|
commands << @windows.map(&:generate_commands)
|
||||||
|
commands << "tmux select-pane -t 0"
|
||||||
|
commands
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
64
lib/teamocil/layout/split.rb
Normal file
64
lib/teamocil/layout/split.rb
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
module Teamocil
|
||||||
|
class Layout
|
||||||
|
|
||||||
|
# This class represents a split within a tmux window
|
||||||
|
class Split
|
||||||
|
attr_reader :width, :height, :cmd, :index, :target
|
||||||
|
|
||||||
|
# Initialize a new tmux split
|
||||||
|
#
|
||||||
|
# @param session [Session] the window where the split is initialized
|
||||||
|
# @param index [Fixnnum] the split index
|
||||||
|
# @param attrs [Hash] the split data from the layout file
|
||||||
|
def initialize(window, index, attrs={}) # {{{
|
||||||
|
@height = attrs["height"]
|
||||||
|
@width = attrs["width"]
|
||||||
|
@cmd = attrs["cmd"]
|
||||||
|
@target = attrs["target"]
|
||||||
|
|
||||||
|
@window = window
|
||||||
|
@index = index
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Generate commands to send to tmux
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
def generate_commands # {{{
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
# Is it a vertical or horizontal split?
|
||||||
|
init_command = ""
|
||||||
|
unless @index == 0
|
||||||
|
if !@width.nil?
|
||||||
|
init_command = "tmux split-window -h -p #{@width}"
|
||||||
|
elsif !@height.nil?
|
||||||
|
init_command = "tmux split-window -p #{@height}"
|
||||||
|
else
|
||||||
|
init_command = "tmux split-window"
|
||||||
|
end
|
||||||
|
init_command << " -t #{@target}" unless @target.nil?
|
||||||
|
commands << init_command
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrap all commands around filters
|
||||||
|
@cmd = [@window.filters["before"]] + [@cmd] + [@window.filters["after"]]
|
||||||
|
|
||||||
|
# If a `root` key exist, start each split in this directory
|
||||||
|
@cmd.unshift "cd \"#{@window.root}\"" unless @window.root.nil?
|
||||||
|
|
||||||
|
# Set the TEAMOCIL environment variable
|
||||||
|
@cmd.unshift "export TEAMOCIL=1"
|
||||||
|
|
||||||
|
# Execute each split command
|
||||||
|
@cmd.flatten.compact.each do |command|
|
||||||
|
commands << "tmux send-keys -t #{@index} \"#{command}\""
|
||||||
|
commands << "tmux send-keys -t #{@index} Enter"
|
||||||
|
end
|
||||||
|
|
||||||
|
commands
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
55
lib/teamocil/layout/window.rb
Normal file
55
lib/teamocil/layout/window.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
module Teamocil
|
||||||
|
class Layout
|
||||||
|
|
||||||
|
# This class represents a window within tmux
|
||||||
|
class Window
|
||||||
|
attr_reader :filters, :root, :splits, :options, :index, :name
|
||||||
|
|
||||||
|
# Initialize a new tmux window
|
||||||
|
#
|
||||||
|
# @param session [Session] the session where the window is initialized
|
||||||
|
# @param index [Fixnnum] the window index
|
||||||
|
# @param attrs [Hash] the window data from the layout file
|
||||||
|
def initialize(session, index, attrs={}) # {{{
|
||||||
|
@name = attrs["name"]
|
||||||
|
@root = attrs["root"]
|
||||||
|
@options = attrs["options"] || {}
|
||||||
|
|
||||||
|
@splits = attrs["splits"] || []
|
||||||
|
@splits = @splits.each_with_index.map { |split, split_index| Split.new(self, split_index, split) }
|
||||||
|
|
||||||
|
@filters = attrs["filters"] || {}
|
||||||
|
@filters["before"] ||= []
|
||||||
|
@filters["after"] ||= []
|
||||||
|
|
||||||
|
@index = index
|
||||||
|
@session = session
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
# Generate commands to send to tmux
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
def generate_commands # {{{
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
if @session.options.include?(:here) and @index == 0
|
||||||
|
commands << "tmux rename-window \"#{@name}\""
|
||||||
|
else
|
||||||
|
commands << "tmux new-window -n \"#{@name}\""
|
||||||
|
end
|
||||||
|
|
||||||
|
commands << @splits.map(&:generate_commands)
|
||||||
|
|
||||||
|
@options.each_pair do |option, value|
|
||||||
|
value = "on" if value === true
|
||||||
|
value = "off" if value === false
|
||||||
|
commands << "tmux set-window-option #{option} #{value}"
|
||||||
|
end
|
||||||
|
|
||||||
|
commands
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
43
spec/cli_spec.rb
Normal file
43
spec/cli_spec.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
require File.join(File.dirname(__FILE__), "spec_helper.rb")
|
||||||
|
|
||||||
|
describe Teamocil::CLI do
|
||||||
|
|
||||||
|
context "executing" do
|
||||||
|
|
||||||
|
context "not in tmux" do
|
||||||
|
|
||||||
|
before do # {{{
|
||||||
|
@fake_env = { "TMUX" => 1, "HOME" => File.join(File.dirname(__FILE__), "fixtures") }
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "should allow editing" do # {{{
|
||||||
|
FileUtils.stub(:touch)
|
||||||
|
Kernel.should_receive(:system).with(any_args())
|
||||||
|
|
||||||
|
@cli = Teamocil::CLI.new(["--edit", "my-layout"], @fake_env)
|
||||||
|
end # }}}
|
||||||
|
end
|
||||||
|
|
||||||
|
context "in tmux" do
|
||||||
|
|
||||||
|
before do # {{{
|
||||||
|
@fake_env = { "TMUX" => 1, "HOME" => File.join(File.dirname(__FILE__), "fixtures") }
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates a layout" do # {{{
|
||||||
|
@cli = Teamocil::CLI.new(["sample"], @fake_env)
|
||||||
|
@cli.layout.session.name.should == "sample"
|
||||||
|
@cli.layout.session.windows.length.should == 2
|
||||||
|
@cli.layout.session.windows.first.name.should == "foo"
|
||||||
|
@cli.layout.session.windows.last.name.should == "bar"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "lists available layouts" do # {{{
|
||||||
|
@cli = Teamocil::CLI.new(["--list"], @fake_env)
|
||||||
|
@cli.layouts.should == ["sample", "sample-2"]
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
10
spec/fixtures/.teamocil/sample-2.yml
vendored
Normal file
10
spec/fixtures/.teamocil/sample-2.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
session:
|
||||||
|
name: sample-2
|
||||||
|
root: ~
|
||||||
|
windows:
|
||||||
|
- name: "foo"
|
||||||
|
splits:
|
||||||
|
- cmd: "pwd"
|
||||||
|
- name: "bar"
|
||||||
|
splits:
|
||||||
|
- cmd: "pwd"
|
10
spec/fixtures/.teamocil/sample.yml
vendored
Normal file
10
spec/fixtures/.teamocil/sample.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
session:
|
||||||
|
name: sample
|
||||||
|
root: ~
|
||||||
|
windows:
|
||||||
|
- name: "foo"
|
||||||
|
splits:
|
||||||
|
- cmd: "pwd"
|
||||||
|
- name: "bar"
|
||||||
|
splits:
|
||||||
|
- cmd: "pwd"
|
43
spec/fixtures/layouts.yml
vendored
Normal file
43
spec/fixtures/layouts.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Simple two windows layout
|
||||||
|
two-windows:
|
||||||
|
windows:
|
||||||
|
- name: "foo"
|
||||||
|
root: "/foo"
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'foo'"
|
||||||
|
- cmd: "echo 'foo again'"
|
||||||
|
width: 50
|
||||||
|
- name: "bar"
|
||||||
|
root: "/bar"
|
||||||
|
splits:
|
||||||
|
- cmd:
|
||||||
|
- "echo 'bar'"
|
||||||
|
- "echo 'bar in an array'"
|
||||||
|
target: bottom-right
|
||||||
|
- cmd: "echo 'bar again'"
|
||||||
|
width: 50
|
||||||
|
|
||||||
|
# Simple two windows layout with filters
|
||||||
|
two-windows-with-filters:
|
||||||
|
windows:
|
||||||
|
- name: "foo"
|
||||||
|
root: "/foo"
|
||||||
|
filters:
|
||||||
|
before:
|
||||||
|
- "echo first before filter"
|
||||||
|
- "echo second before filter"
|
||||||
|
after:
|
||||||
|
- "echo first after filter"
|
||||||
|
- "echo second after filter"
|
||||||
|
splits:
|
||||||
|
- cmd: "echo 'foo'"
|
||||||
|
- cmd: "echo 'foo again'"
|
||||||
|
width: 50
|
||||||
|
|
||||||
|
three-windows-within-a-session:
|
||||||
|
session:
|
||||||
|
name: "my awesome session"
|
||||||
|
windows:
|
||||||
|
- name: "first window"
|
||||||
|
- name: "second window"
|
||||||
|
- name: "third window"
|
98
spec/layout_spec.rb
Normal file
98
spec/layout_spec.rb
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
require File.join(File.dirname(__FILE__), "spec_helper.rb")
|
||||||
|
|
||||||
|
describe Teamocil::Layout do
|
||||||
|
|
||||||
|
context "compiling" do
|
||||||
|
|
||||||
|
before do # {{{
|
||||||
|
@layout = Teamocil::Layout.new(layouts["two-windows"], {})
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates windows" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.each do |window|
|
||||||
|
window.should be_an_instance_of Teamocil::Layout::Window
|
||||||
|
end
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates windows with names" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows[0].name.should == "foo"
|
||||||
|
session.windows[1].name.should == "bar"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates windows with root paths" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows[0].root.should == "/foo"
|
||||||
|
session.windows[1].root.should == "/bar"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates splits" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.first.splits.each do |split|
|
||||||
|
split.should be_an_instance_of Teamocil::Layout::Split
|
||||||
|
end
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates splits with dimensions" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.first.splits[0].width.should == nil
|
||||||
|
session.windows.first.splits[1].width.should == 50
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates splits with commands specified in strings" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.first.splits[0].cmd.should == "echo 'foo'"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates splits with commands specified in an array" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.last.splits[0].cmd.length.should == 2
|
||||||
|
session.windows.last.splits[0].cmd.first.should == "echo 'bar'"
|
||||||
|
session.windows.last.splits[0].cmd.last.should == "echo 'bar in an array'"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates windows with before filters" do # {{{
|
||||||
|
layout = Teamocil::Layout.new(layouts["two-windows-with-filters"], {})
|
||||||
|
session = layout.compile!
|
||||||
|
session.windows.first.filters["before"].length.should == 2
|
||||||
|
session.windows.first.filters["before"].first.should == "echo first before filter"
|
||||||
|
session.windows.first.filters["before"].last.should == "echo second before filter"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "creates windows with after filters" do # {{{
|
||||||
|
layout = Teamocil::Layout.new(layouts["two-windows-with-filters"], {})
|
||||||
|
session = layout.compile!
|
||||||
|
session.windows.first.filters["after"].length.should == 2
|
||||||
|
session.windows.first.filters["after"].first.should == "echo first after filter"
|
||||||
|
session.windows.first.filters["after"].last.should == "echo second after filter"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "should handle blank filters" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.first.filters.should have_key "after"
|
||||||
|
session.windows.first.filters.should have_key "before"
|
||||||
|
session.windows.first.filters["after"].should be_empty
|
||||||
|
session.windows.first.filters["before"].should be_empty
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "should handle splits without a target" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.last.splits.last.target.should == nil
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "should handle splits with a target" do # {{{
|
||||||
|
session = @layout.compile!
|
||||||
|
session.windows.last.splits.first.target.should == "bottom-right"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
it "should handle windows within a session" do # {{{
|
||||||
|
layout = Teamocil::Layout.new(layouts["three-windows-within-a-session"], {})
|
||||||
|
session = layout.compile!
|
||||||
|
session.windows.length.should == 3
|
||||||
|
session.name.should == "my awesome session"
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
20
spec/mock/cli.rb
Normal file
20
spec/mock/cli.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module Teamocil
|
||||||
|
module Mock
|
||||||
|
module CLI
|
||||||
|
|
||||||
|
def self.included(base) # {{{
|
||||||
|
base.class_eval do
|
||||||
|
|
||||||
|
# Do not print anything
|
||||||
|
def print_layouts
|
||||||
|
# Nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Teamocil::CLI.send :include, Teamocil::Mock::CLI
|
20
spec/mock/layout.rb
Normal file
20
spec/mock/layout.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module Teamocil
|
||||||
|
module Mock
|
||||||
|
module Layout
|
||||||
|
|
||||||
|
def self.included(base) # {{{
|
||||||
|
base.class_eval do
|
||||||
|
|
||||||
|
# Do not execute anything
|
||||||
|
def execute_commands(commands)
|
||||||
|
# Nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Teamocil::Layout.send :include, Teamocil::Mock::Layout
|
19
spec/spec_helper.rb
Normal file
19
spec/spec_helper.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
||||||
|
|
||||||
|
require 'yaml'
|
||||||
|
require 'teamocil'
|
||||||
|
require File.join(File.dirname(__FILE__), "./mock/layout.rb")
|
||||||
|
require File.join(File.dirname(__FILE__), "./mock/cli.rb")
|
||||||
|
|
||||||
|
module Helpers
|
||||||
|
|
||||||
|
def layouts # {{{
|
||||||
|
return @@examples if defined?(@@examples)
|
||||||
|
@@examples = YAML.load_file(File.join(File.dirname(__FILE__), "fixtures/layouts.yml"))
|
||||||
|
end # }}}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |c|
|
||||||
|
c.include Helpers
|
||||||
|
end
|
28
teamocil.gemspec
Normal file
28
teamocil.gemspec
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
require "teamocil"
|
||||||
|
|
||||||
|
spec = Gem::Specification.new do |s|
|
||||||
|
# Metadata
|
||||||
|
s.name = "teamocil"
|
||||||
|
s.version = Teamocil::VERSION
|
||||||
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.authors = "Rémi Prévost"
|
||||||
|
s.email = "remi@exomel.com"
|
||||||
|
s.homepage = "http://github.com/remiprev/teamocil"
|
||||||
|
s.summary = "Easy window and split layouts for tmux"
|
||||||
|
s.description = "Teamocil helps you set up window and splits layouts for tmux using YAML configuration files."
|
||||||
|
|
||||||
|
# Manifest
|
||||||
|
s.files = `git ls-files`.split("\n")
|
||||||
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||||
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||||
|
s.require_paths = ["lib"]
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
s.add_development_dependency "rake"
|
||||||
|
s.add_development_dependency "rspec"
|
||||||
|
s.add_development_dependency "yard"
|
||||||
|
s.add_development_dependency "maruku"
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user