diff --git a/.gitignore b/.gitignore index 43c68bc..a8fa9d0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .rvmrc .local* .yardoc +doc diff --git a/lib/teamocil/cli.rb b/lib/teamocil/cli.rb index 90d9e04..1e4f18e 100644 --- a/lib/teamocil/cli.rb +++ b/lib/teamocil/cli.rb @@ -26,7 +26,8 @@ module Teamocil bail "There is no file \"#{file}\"" unless File.exists?(file) parsed_layout = YAML.load_file(file) layout = Teamocil::Layout.new(parsed_layout, @options) - layout.to_tmux + layout.compile! + layout.execute_commands(layout.generate_commands) end end # }}} diff --git a/lib/teamocil/layout.rb b/lib/teamocil/layout.rb index e9a3e74..276d708 100644 --- a/lib/teamocil/layout.rb +++ b/lib/teamocil/layout.rb @@ -1,7 +1,137 @@ module Teamocil - # This class act as a wrapper around a tmux YAML layout file. + + # This class act as a wrapper around a tmux YAML layout file 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, index| Window.new(self, 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 + + # 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"] + @filters = attrs["filters"] + @splits = attrs["splits"].each_with_index.map { |split, index| Split.new(self, index, split) } + @index = index + @session = session + + @options ||= {} + @filters ||= {} + @filters["before"] ||= [] + @filters["after"] ||= [] + 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 + + # 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? + unless @index == 0 + if !@width.nil? + commands << "tmux split-window -h -p #{@width}" + elsif !@height.nil? + commands << "tmux split-window -p #{@height}" + else + commands << "tmux split-window" + end + commands << " -t #{@target}" unless @target.nil? + 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? + + # 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 + # Initialize a new layout from a hash # # @param layout [Hash] the parsed layout @@ -11,79 +141,22 @@ module Teamocil @options = options end # }}} - # Generate commands and sends them to tmux - def to_tmux # {{{ - commands = generate_commands - execute_commands(commands) - 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 # {{{ - output = [] + @session.generate_commands + end # }}} - # Support renaming of current session + # Compile the layout into objects + # + # @return [Session] + def compile! # {{{ if @layout["session"].nil? - windows = @layout["windows"] + @session = Session.new @options, "windows" => @layout["windows"] else - output << "tmux rename-session \"#{@layout["session"]["name"]}\"" if @layout["session"]["name"] - windows = @layout["session"]["windows"] + @session = Session.new @options, @layout["session"] end - - windows.each_with_index do |window, window_index| - - # Create a new window unless we used the `--here` option - if @options.include?(:here) and window_index == 0 - output << "tmux rename-window \"#{window["name"]}\"" - else - output << "tmux new-window -n \"#{window["name"]}\"" - end - - # Make sure we have all the keys we need - window["options"] ||= {} - window["filters"] ||= {} - window["filters"]["before"] ||= [] - window["filters"]["after"] ||= [] - - # Create splits - window["splits"].each_with_index do |split, split_index| - unless split_index == 0 - if split.include?("width") - cmd = "tmux split-window -h -p #{split["width"]}" - elsif split.include?("height") - cmd = "tmux split-window -p #{split["height"]}" - else - cmd = "tmux split-window" - end - cmd << " -t #{split["target"]}" if split.include?("target") - output << cmd - end - - # Wrap all commands around filters - split["cmd"] = [window["filters"]["before"]] + [split["cmd"]] + [window["filters"]["after"]] - - # If a `root` key exist, start each split in this directory - split["cmd"].unshift "cd \"#{window["root"]}\"" if window.include?("root") - - # Execute each split command - split["cmd"].flatten.compact.each do |command| - output << "tmux send-keys -t #{split_index} \"#{command}\"" - output << "tmux send-keys -t #{split_index} Enter" - end - end - - # Set tmux options - window["options"].each_pair do |option, value| - value = "on" if value === true - value = "off" if value === false - output << "tmux set-window-option #{option} #{value}" - end - - end - - # Set the focus in the first split - output << "tmux select-pane -t 0" end # }}} # Execute each command in the shell diff --git a/spec/fixtures/layouts.yml b/spec/fixtures/layouts.yml index 9fe32a9..6bedc5c 100644 --- a/spec/fixtures/layouts.yml +++ b/spec/fixtures/layouts.yml @@ -2,36 +2,33 @@ 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'" + - cmd: + - "echo 'bar'" + - "echo 'bar in an array'" - cmd: "echo 'bar again'" width: 50 -# Simple two windows layout in session -two-windows-in-a-session: - session: - name: my-new-session - windows: - - name: "foo" - splits: - - cmd: "echo 'foo'" - - cmd: "echo 'foo again'" - width: 50 - - name: "bar" - splits: - - cmd: "echo 'bar'" - - cmd: "echo 'bar again'" - width: 50 -four-splits: +# 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 1" - - cmd: "echo 2" - - cmd: "echo 3" - - cmd: "echo 4" + - cmd: "echo 'foo'" + - cmd: "echo 'foo again'" + width: 50 diff --git a/spec/layout_spec.rb b/spec/layout_spec.rb index ececdc8..89a1896 100644 --- a/spec/layout_spec.rb +++ b/spec/layout_spec.rb @@ -1,25 +1,60 @@ require File.join(File.dirname(__FILE__), "spec_helper.rb") describe Teamocil::Layout do + context "initializing" do + end - it "creates windows" do # {{{ - layout = Teamocil::Layout.new(layouts["two-windows"], {}) - commands = layout.generate_commands - commands.grep(/new-window/).length.should == 2 + context "compiling" do + + before :each do # {{{ + @layout = Teamocil::Layout.new(layouts["two-windows"], {}) end # }}} - it "renames the current session" do # {{{ - layout = Teamocil::Layout.new(layouts["two-windows-in-a-session"], {}) - commands = layout.generate_commands - commands.grep(/rename-session/).first.should == "tmux rename-session \"my-new-session\"" - commands.grep(/new-window/).length.should == 2 + it "creates windows with names" do # {{{ + session = @layout.compile! + session.windows[0].name.should == "foo" + session.windows[1].name.should == "bar" end # }}} - it "creates splits" do # {{{ - layout = Teamocil::Layout.new(layouts["four-splits"], {}) - commands = layout.generate_commands - commands.grep(/split-window/).length.should == 3 + 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 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 # }}} end