Permalink
Browse files

Add simple `#gets` based interactor.

This extracts the Readline support into a separate class and keeps only the
generic behavior in the main interactor class, thus enabling the return of
the previous simple `#gets` based interactor.

Guard auto detects if Readline support is suitable on your environment, but
you can also use the new `interactor` DSL method to force a specific
implementation. (Fixes #197 and #200)
  • Loading branch information...
netzpirat committed Dec 22, 2011
1 parent 408717d commit 4d2415e332b9a28b01a1969a56884d1458702f65
View
@@ -1,3 +1,17 @@
+## Master
+
+### Improvements
+
+- Add `interactor` to DSL to allow switching Guard interaction implementation. ([@netzpirat][])
+- Add quit action to the interactor. ([@Maher4Ever][])
+
+## 0.9.1 - December 19, 2011
+
+### Bug fix
+
+- Fix wrong `--no-vendor` option. ([@netzpirat][])
+- [#195](https://github.com/guard/guard/issues/195): Empty watch directory prohibit Guard from running. (reported by [@madtrick][], fixed by [@netzpirat][]
+
## 0.9.0 - December 19, 2011
### Bug fix
@@ -383,3 +397,6 @@
[@wereHamster]: https://github.com/wereHamster
[@yannlugrin]: https://github.com/yannlugrin
[@zonque]: https://github.com/zonque
+
+[@Maher4Ever]: https://github.com/Maher4Ever
+[@madtrick]: https://github.com/madtrick
View
@@ -34,10 +34,6 @@ Add Guard to your `Gemfile`:
```ruby
group :development do
gem 'guard'
-
- platforms :ruby do
- gem 'rb-readline'
- end
end
```
@@ -361,11 +357,7 @@ read more about these files in the shared configuration section below.
Interactions
------------
-You can interact with Guard and enter commands when Guard has nothing to do. You'll see a command prompt `>` when Guard
-is ready to accept a command. The command line supports history navigation with the `↑` and `↓` arrow keys, and
-command auto-completion with the `⇥` key.
-
-You can execute the following commands:
+You can interact with Guard and enter commands when Guard has nothing to do. Guard understands the following commands:
* `↩`: Run all Guards.
* `h`, `help`: Show a help of the available interactor commands.
@@ -399,6 +391,23 @@ This will reload only the Ronn Guard. You can also reload all Guards within a gr
> backend reload
```
+### Readline support
+
+With Readline enabled, you'll see a command prompt `>` when Guard is ready to accept a command. The command line
+supports history navigation with the `↑` and `↓` arrow keys, and command auto-completion with the `⇥` key.
+
+Unfortunately Readline [does not work on MRI](http://bugs.ruby-lang.org/issues/5539) on Mac OS X by default. You can
+work around the issue by installing a pure Ruby implementation:
+
+```ruby
+platforms :ruby do
+ gem 'rb-readline'
+end
+```
+
+Guard will automatically enable Readline support if your environment supports it, but you can disable Readline with the
+`interactor` DSL method or turn off completely with the `--no-interactions` option.
+
Guardfile DSL
-------------
@@ -529,6 +538,26 @@ or using the cli switch `-n`:
notification :off
```
+### interactor
+
+You can disable the interactor auto detection and for a specific implementation:
+
+```ruby
+interactor :readline
+```
+
+will select Readline interactor. You can also force the simple interactor without Readline support with:
+
+```ruby
+interactor :simple
+```
+
+If you do not need the keyboard interactions with Guard at all, you can turn them off:
+
+```ruby
+interactor :off
+```
+
### callback
The `callback` method allows you to execute arbitrary code before or after any of the `start`, `stop`, `reload`,
View
@@ -62,7 +62,6 @@ def setup(options = {})
@options = options
@guards = []
self.reset_groups
- @interactor = Interactor.new unless options[:no_interactions]
@listener = Listener.select_and_init(options)
UI.clear if @options[:clear]
@@ -177,7 +176,11 @@ def start(options = {})
run_supervised_task(guard, :start)
end
- interactor.start if interactor
+ unless options[:no_interactions]
+ @interactor = Interactor.fabricate
+ @interactor.start if @interactor
+ end
+
listener.start
end
View
@@ -276,6 +276,21 @@ def notification(notifier, options = {})
::Guard::Notifier.add_notification(notifier.to_sym, options, false)
end
+ # Sets the interactor to use.
+ #
+ # @example Use the readline interactor
+ # interactor :readline
+ #
+ # @example Use the gets interactor
+ # interactor :gets
+ #
+ # @example Turn off interactions
+ # interactor :off
+ #
+ def interactor(interactor)
+ ::Guard::Interactor.interactor = interactor.to_sym
+ end
+
# Declares a group of guards to be run with `guard start --group group_name`.
#
# @example Declare two groups of Guards
View
@@ -1,12 +1,10 @@
-require 'readline'
-
module Guard
- # The interactor reads user input and triggers
- # specific action upon them unless its locked.
- #
- # It used the readline library for history and
- # completion support.
+ autoload :ReadlineInteractor, 'guard/interactors/readline'
+ autoload :SimpleInteractor, 'guard/interactors/simple'
+
+ # The interactor triggers specific action from input
+ # read by a interactor implementation.
#
# Currently the following actions are implemented:
#
@@ -29,6 +27,8 @@ module Guard
# @example Run all jasmine specs
# jasmine
#
+ # @abstract
+ #
class Interactor
HELP_ENTRIES = %w[help h]
@@ -37,21 +37,52 @@ class Interactor
PAUSE_ENTRIES = %w[pause p]
NOTIFICATION_ENTRIES = %w[notification n]
- COMPLETION_ACTIONS = %w[help reload exit pause notification]
+ # Set the interactor implementation
+ #
+ # @param [Symbol] interactor the name of the interactor
+ #
+ def self.interactor=(interactor)
+ @interactor = interactor
+ end
- # Initialize the interactor.
+ # Get an instance of the currently configured
+ # interactor implementation.
#
- def initialize
- unless defined?(RbReadline) || defined?(JRUBY_VERSION)
- ::Guard::UI.info 'Please add rb-readline for proper Readline support.'
+ # @return [Interactor] an interactor implementation
+ #
+ def self.fabricate
+ case @interactor
+ when :readline
+ ReadlineInteractor.new
+ when :simple
+ SimpleInteractor.new
+ when :off
+ nil
+ else
+ auto_detect
end
+ end
- Readline.completion_proc = proc { |word| auto_complete(word) }
+ # Tries to detect an optimal interactor for the
+ # current environment.
+ #
+ # It returns the Readline implementation when:
+ #
+ # * rb-readline is installed
+ # * The Ruby implementation is JRuby
+ # * The current OS is not Mac OS X
+ #
+ # Otherwise the plain gets interactor is returned.
+ #
+ # @return [Interactor] an interactor implementation
+ #
+ def self.auto_detect
+ require 'readline'
- begin
- Readline.completion_append_character = ' '
- rescue NotImplementedError
- # Ignore, we just don't support it then
+ if defined?(RbReadline) || defined?(JRUBY_VERSION) || !RbConfig::CONFIG['target_os'] =~ /darwin/i
+ ReadlineInteractor.new
+ else
+ SimpleInteractor.new
end
end
@@ -70,51 +101,20 @@ def stop
end
end
- # Read a line from stdin with Readline.
+ # Read the user input. This method must be implemented
+ # by each interactor implementation.
#
- def read_line
- while line = Readline.readline(prompt, true)
- process_input(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
+ # @abstract
#
- 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
- #
- def prompt
- ::Guard.listener.paused? ? 'p> ' : '> '
+ def read_line
+ raise NotImplementedError
end
# Process the input from readline.
#
# @param [String] line the input line
#
def process_input(line)
- if line =~ /^\s*$/ or Readline::HISTORY.to_a[-2] == line
- Readline::HISTORY.pop
- end
-
scopes, action = extract_scopes_and_action(line)
case action
@@ -0,0 +1,72 @@
+module Guard
+
+ # Interactor that used readline for getting the user input.
+ # This enables history support and auto-completion, but is
+ # broken on OS X without installing `rb-readline` or using JRuby.
+ #
+ # @see http://bugs.ruby-lang.org/issues/5539
+ #
+ class ReadlineInteractor < Interactor
+
+ COMPLETION_ACTIONS = %w[help reload exit pause notification]
+
+ # Initialize the interactor.
+ #
+ def initialize
+ require 'readline'
+
+ unless defined?(RbReadline) || defined?(JRUBY_VERSION)
+ ::Guard::UI.info 'Please add rb-readline for proper Readline support.'
+ end
+
+ Readline.completion_proc = proc { |word| auto_complete(word) }
+
+ begin
+ Readline.completion_append_character = ' '
+ rescue NotImplementedError
+ # Ignore, we just don't support it then
+ end
+ end
+
+ # Read a line from stdin with Readline.
+ #
+ def read_line
+ while line = Readline.readline(prompt, true)
+ if line =~ /^\s*$/ or Readline::HISTORY.to_a[-2] == line
+ Readline::HISTORY.pop
+ end
+
+ process_input(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
+ #
+ def prompt
+ ::Guard.listener.paused? ? 'p> ' : '> '
+ end
+
+ end
+end
@@ -0,0 +1,17 @@
+module Guard
+
+ # Simple interactor that that reads user
+ # input from standard input.
+ #
+ class SimpleInteractor < Interactor
+
+ # Read a line from stdin with Readline.
+ #
+ def read_line
+ while line = $stdin.gets
+ process_input(line.chomp)
+ end
+ end
+
+ end
+end
Oops, something went wrong.

3 comments on commit 4d2415e

Owner

thibaudgg replied Dec 23, 2011

That's pure awesomeness!

Owner

rymai replied Dec 23, 2011

Too much love right now ! Thanks Michi! ❤️

Contributor

netzpirat replied Dec 23, 2011

Thanks. It allows us also to easily plug in other implementations. I already did some test with https://github.com/Mon-Ouie/coolline, looks promising but is not ready for prime time now.

Please sign in to comment.