Skip to content

Commit

Permalink
Initial coolline support.
Browse files Browse the repository at this point in the history
This is the initial coolline based interactor, that makes
use of Ruby 1.9.3 io/console library.
  • Loading branch information
netzpirat committed Jun 12, 2012
1 parent 89e4648 commit 7fca843
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 81 deletions.
6 changes: 3 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ gemspec

gem 'rake'

# platform :ruby do
# gem 'rb-readline'
# end
platform :ruby do
gem 'coolline'
end

group :guard do
gem 'guard-ronn'
Expand Down
23 changes: 15 additions & 8 deletions lib/guard/interactor.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Guard

autoload :ReadlineInteractor, 'guard/interactors/readline'
autoload :CoollineInteractor, 'guard/interactors/coolline'
autoload :SimpleInteractor, 'guard/interactors/simple'

# The interactor triggers specific action from input
Expand Down Expand Up @@ -52,8 +53,10 @@ def self.interactor=(interactor)
#
def self.fabricate
case @interactor
when :coolline
CoollineInteractor.new if CoollineInteractor.available?
when :readline
ReadlineInteractor.new
ReadlineInteractor.new if ReadlineInteractor.available?
when :simple
SimpleInteractor.new
when :off
Expand All @@ -77,15 +80,19 @@ def self.fabricate
# @return [Interactor] an interactor implementation
#
def self.auto_detect
require 'readline'

if defined?(RbReadline) || defined?(JRUBY_VERSION) || RbConfig::CONFIG['target_os'] =~ /linux/i
ReadlineInteractor.new
else
SimpleInteractor.new
end
[CoollineInteractor, ReadlineInteractor, SimpleInteractor].detect { |interactor| interactor.available?(true) }.new
end

# Template method for checking if the Interactor is
# available in the current environment?
#
# @param [Boolean] silent true if no error messages should be shown
# @return [Boolean] the availability status
#
def self.available?(silent = false)
true
end

# Start the line reader in its own thread.
#
def start
Expand Down
30 changes: 30 additions & 0 deletions lib/guard/interactors/completion.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Guard

# Module for providing word completion to an interactor.
#
module Completion

COMPLETION_ACTIONS = %w[help reload exit pause notification]

# Auto complete the given word.
#
# @param [String] word the partial word
# @return [Array<String>] the matching words
#
def auto_complete(word)
completion_list.grep(/^#{ Regexp.escape(word) }/)
end

# Get the auto completion list.
#
# @return [Array<String>] the list of words
#
def completion_list
groups = ::Guard.groups.map { |group| group.name.to_s }
guards = ::Guard.guards.map { |guard| guard.class.to_s.downcase.sub('guard::', '') }

COMPLETION_ACTIONS + groups + guards - ['default']
end

end
end
59 changes: 59 additions & 0 deletions lib/guard/interactors/coolline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require 'guard/interactors/completion'

module Guard

# Interactor that uses coolline for getting the user input.
# This enables history support and auto-completion,
#
class CoollineInteractor < Interactor
include ::Guard::Completion

# Template method for checking if the Interactor is
# available in the current environment?
#
# @param [Boolean] silent true if no error messages should be shown
# @return [Boolean] the availability status
#
def self.available?(silent = false)
if RbConfig::CONFIG['RUBY_PROGRAM_VERSION'] == '1.9.3'
require 'coolline'
true
else
::Guard::UI.error 'The :coolline interactor runs only on Ruby 1.9.3.' unless silent
false
end

rescue
::Guard::UI.error "Please add \"gem 'coolline'\" to your Gemfile and run Guard with \"bundle exec\"." unless silent
false
end

# Read a line from stdin with Readline.
#
def read_line
coolline = Coolline.new do |cool|
cool.transform_proc = proc do
cool.line
end

cool.completion_proc = proc do
word = cool.completed_word
auto_complete(word)
end
end

while line = coolline.readline(prompt)
process_input(line)
end
end

# The current interactor prompt
#
# @return [String] the prompt to show
#
def prompt
::Guard.listener.paused? ? 'p> ' : '>> '
end

end
end
46 changes: 21 additions & 25 deletions lib/guard/interactors/readline.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'guard/interactors/completion'

module Guard

# Interactor that used readline for getting the user input.
Expand All @@ -7,18 +9,30 @@ module Guard
# @see http://bugs.ruby-lang.org/issues/5539
#
class ReadlineInteractor < Interactor
include ::Guard::Completion

COMPLETION_ACTIONS = %w[help reload exit pause notification]
# Template method for checking if the Interactor is
# available in the current environment?
#
# @param [Boolean] silent true if no error messages should be shown
# @return [Boolean] the availability status
#
def self.available?(silent = false)
require 'readline'

if defined?(RbReadline) || defined?(JRUBY_VERSION) || RbConfig::CONFIG['target_os'] =~ /linux/i
true
else
::Guard::UI.error 'The :readline interactor runs only fine on JRuby, Linux or with the gem \'rb-readline\' installed.' unless silent
false
end
end

# Initialize the interactor.
#
def initialize
require 'readline'

unless defined?(RbReadline) || defined?(JRUBY_VERSION) || RbConfig::CONFIG['target_os'] =~ /linux/i
::Guard::UI.info 'Please add rb-readline for proper Readline support.'
end

Readline.completion_proc = proc { |word| auto_complete(word) }

begin
Expand All @@ -45,6 +59,8 @@ def stop
# Read a line from stdin with Readline.
#
def read_line
require 'readline'

while line = Readline.readline(prompt, true)
line.gsub!(/^\W*/, '')
if line =~ /^\s*$/ or Readline::HISTORY.to_a[-2] == line
Expand All @@ -55,26 +71,6 @@ def read_line
end
end

# Auto complete the given word.
#
# @param [String] word the partial word
# @return [Array<String>] the matching words
#
def auto_complete(word)
completion_list.grep(/^#{ Regexp.escape(word) }/)
end

# Get the auto completion list.
#
# @return [Array<String>] the list of words
#
def completion_list
groups = ::Guard.groups.map { |group| group.name.to_s }
guards = ::Guard.guards.map { |guard| guard.class.to_s.downcase.sub('guard::', '') }

COMPLETION_ACTIONS + groups + guards - ['default']
end

# The current interactor prompt
#
# @return [String] the prompt to show
Expand Down
72 changes: 69 additions & 3 deletions spec/guard/interactor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,40 @@
end

describe '.fabricate' do
it 'returns the Readline interactor for a :readline symbol' do
Guard::Interactor.interactor = :readline
Guard::Interactor.fabricate.should be_an_instance_of(Guard::ReadlineInteractor)
context 'with coolline available' do
before { Guard::CoollineInteractor.stub(:available?).and_return true }

it 'returns the Coolline interactor for a :coolline symbol' do
Guard::Interactor.interactor = :coolline
Guard::Interactor.fabricate.should be_an_instance_of(Guard::CoollineInteractor)
end
end

context 'with coolline unavailable' do
before { Guard::CoollineInteractor.stub(:available?).and_return false }

it 'returns nil' do
Guard::Interactor.interactor = :coolline
Guard::Interactor.fabricate.should be_nil
end
end

context 'with readline available' do
before { Guard::ReadlineInteractor.stub(:available?).and_return true }

it 'returns the Readline interactor for a :readline symbol' do
Guard::Interactor.interactor = :readline
Guard::Interactor.fabricate.should be_an_instance_of(Guard::ReadlineInteractor)
end
end

context 'with readline unavailable' do
before { Guard::ReadlineInteractor.stub(:available?).and_return false }

it 'returns nil' do
Guard::Interactor.interactor = :readline
Guard::Interactor.fabricate.should be_nil
end
end

it 'returns the Gets interactor for a :simple symbol' do
Expand All @@ -32,6 +63,41 @@
end
end

describe '.auto_detect' do
context 'when all interactors are available' do
before do
Guard::CoollineInteractor.stub(:available?).and_return true
Guard::ReadlineInteractor.stub(:available?).and_return true
end

it 'chooses the coolline interactor ' do
Guard::Interactor.auto_detect.should be_an_instance_of(Guard::CoollineInteractor)
end
end

context 'when only the coolline interactor is unavailable available' do
before do
Guard::CoollineInteractor.stub(:available?).and_return false
Guard::ReadlineInteractor.stub(:available?).and_return true
end

it 'chooses the readline interactor ' do
Guard::Interactor.auto_detect.should be_an_instance_of(Guard::ReadlineInteractor)
end
end

context 'when coolline and readline interactors are unavailable available' do
before do
Guard::CoollineInteractor.stub(:available?).and_return false
Guard::ReadlineInteractor.stub(:available?).and_return false
end

it 'chooses the simple interactor ' do
Guard::Interactor.auto_detect.should be_an_instance_of(Guard::SimpleInteractor)
end
end
end

describe '#process_input' do
it 'shows the help on help action' do
subject.should_receive(:extract_scopes_and_action).with('help').and_return [{ }, :help]
Expand Down
48 changes: 48 additions & 0 deletions spec/guard/interactors/completion_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require 'spec_helper'
require 'guard/interactors/completion'

describe Guard::Completion do
subject { Guard::ReadlineInteractor.new }

describe '#auto_complete' do
it 'returns the matching list of words' do
subject.should_receive(:completion_list).any_number_of_times.and_return %w[help reload exit pause notification backend frontend foo foobar]
subject.auto_complete('f').should =~ ['frontend', 'foo', 'foobar']
subject.auto_complete('foo').should =~ ['foo', 'foobar']
subject.auto_complete('he').should =~ ['help']
subject.auto_complete('re').should =~ ['reload']
end
end

describe "#completion_list" do
before(:all) do
class Guard::Foo < Guard::Guard; end
class Guard::FooBar < Guard::Guard; end
end

before(:each) do
guard = ::Guard
guard.setup_guards
guard.setup_groups
@backend_group = guard.add_group(:backend)
@frontend_group = guard.add_group(:frontend)
@foo_guard = guard.add_guard(:foo, [], [], { :group => :backend })
@foo_bar_guard = guard.add_guard('foo-bar', [], [], { :group => :frontend })
end

after(:all) do
::Guard.instance_eval do
remove_const(:Foo)
remove_const(:FooBar)
end
end

it 'creates the list of string to auto complete' do
subject.completion_list.should =~ %w[help reload exit pause notification backend frontend foo foobar]
end

it 'does not include the default scope' do
subject.completion_list.should_not include('default')
end
end
end
Loading

0 comments on commit 7fca843

Please sign in to comment.