guard 0.8.8 → 0.9.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 (65) hide show
  1. data/CHANGELOG.md +16 -1
  2. data/README.md +665 -293
  3. data/bin/fsevent_watch_guard +0 -0
  4. data/lib/guard.rb +66 -31
  5. data/lib/guard/cli.rb +9 -3
  6. data/lib/guard/dsl.rb +32 -5
  7. data/lib/guard/dsl_describer.rb +3 -2
  8. data/lib/guard/guard.rb +2 -2
  9. data/lib/guard/interactor.rb +179 -48
  10. data/lib/guard/listener.rb +32 -17
  11. data/lib/guard/listeners/darwin.rb +5 -9
  12. data/lib/guard/listeners/linux.rb +6 -10
  13. data/lib/guard/listeners/windows.rb +4 -2
  14. data/lib/guard/notifier.rb +171 -258
  15. data/lib/guard/notifiers/gntp.rb +114 -0
  16. data/lib/guard/notifiers/growl.rb +98 -0
  17. data/lib/guard/notifiers/growl_notify.rb +91 -0
  18. data/lib/guard/notifiers/libnotify.rb +96 -0
  19. data/lib/guard/notifiers/rb_notifu.rb +101 -0
  20. data/lib/guard/ui.rb +2 -2
  21. data/lib/guard/version.rb +1 -1
  22. data/lib/guard/watcher.rb +1 -1
  23. data/lib/vendor/darwin/Gemfile +6 -0
  24. data/lib/vendor/darwin/Guardfile +8 -0
  25. data/lib/vendor/darwin/LICENSE +20 -0
  26. data/lib/vendor/darwin/README.rdoc +254 -0
  27. data/lib/vendor/darwin/Rakefile +21 -0
  28. data/lib/vendor/darwin/ext/extconf.rb +61 -0
  29. data/lib/vendor/darwin/ext/fsevent/fsevent_watch.c +226 -0
  30. data/lib/vendor/darwin/lib/rb-fsevent.rb +2 -0
  31. data/lib/vendor/darwin/lib/rb-fsevent/fsevent.rb +105 -0
  32. data/lib/vendor/darwin/lib/rb-fsevent/version.rb +3 -0
  33. data/lib/vendor/darwin/rb-fsevent.gemspec +24 -0
  34. data/lib/vendor/darwin/spec/fixtures/folder1/file1.txt +0 -0
  35. data/lib/vendor/darwin/spec/fixtures/folder1/folder2/file2.txt +0 -0
  36. data/lib/vendor/darwin/spec/rb-fsevent/fsevent_spec.rb +75 -0
  37. data/lib/vendor/darwin/spec/spec_helper.rb +24 -0
  38. data/lib/vendor/linux/MIT-LICENSE +20 -0
  39. data/lib/vendor/linux/README.md +66 -0
  40. data/lib/vendor/linux/Rakefile +54 -0
  41. data/lib/vendor/linux/VERSION +1 -0
  42. data/lib/vendor/linux/lib/rb-inotify.rb +17 -0
  43. data/lib/vendor/linux/lib/rb-inotify/event.rb +139 -0
  44. data/lib/vendor/linux/lib/rb-inotify/native.rb +31 -0
  45. data/lib/vendor/linux/lib/rb-inotify/native/flags.rb +89 -0
  46. data/lib/vendor/linux/lib/rb-inotify/notifier.rb +308 -0
  47. data/lib/vendor/linux/lib/rb-inotify/watcher.rb +83 -0
  48. data/lib/vendor/linux/rb-inotify.gemspec +53 -0
  49. data/lib/vendor/windows/Gemfile +4 -0
  50. data/lib/vendor/windows/README.md +34 -0
  51. data/lib/vendor/windows/Rakefile +18 -0
  52. data/lib/vendor/windows/lib/rb-fchange.rb +14 -0
  53. data/lib/vendor/windows/lib/rb-fchange/event.rb +29 -0
  54. data/lib/vendor/windows/lib/rb-fchange/native.rb +45 -0
  55. data/lib/vendor/windows/lib/rb-fchange/native/flags.rb +78 -0
  56. data/lib/vendor/windows/lib/rb-fchange/notifier.rb +149 -0
  57. data/lib/vendor/windows/lib/rb-fchange/version.rb +3 -0
  58. data/lib/vendor/windows/lib/rb-fchange/watcher.rb +99 -0
  59. data/lib/vendor/windows/rb-fchange.gemspec +34 -0
  60. data/lib/vendor/windows/spec/fixtures/folder1/file1.txt +0 -0
  61. data/lib/vendor/windows/spec/fixtures/folder1/folder2/file2.txt +0 -0
  62. data/lib/vendor/windows/spec/rb-fchange/fchange_spec.rb +119 -0
  63. data/lib/vendor/windows/spec/spec_helper.rb +21 -0
  64. metadata +87 -22
  65. data/lib/guard/version.rbc +0 -180
Binary file
@@ -21,7 +21,7 @@ module Guard
21
21
  class << self
22
22
  attr_accessor :options, :interactor, :listener, :lock
23
23
 
24
- # Creates the initial Guardfile template or add a Guard implementation
24
+ # Creates the initial Guardfile template and/or add a Guard implementation
25
25
  # Guardfile template to an existing Guardfile.
26
26
  #
27
27
  # @see Guard::Guard.init
@@ -43,45 +43,54 @@ module Guard
43
43
  end
44
44
  end
45
45
 
46
- # Initialize the Guard singleton.
46
+ # Initialize the Guard singleton:
47
+ #
48
+ # - Initialize the internal Guard state.
49
+ # - Create the interactor when necessary for user interaction.
50
+ # - Select and initialize the file change listener.
47
51
  #
48
52
  # @option options [Boolean] clear if auto clear the UI should be done
49
53
  # @option options [Boolean] notify if system notifications should be shown
50
- # @option options [Boolean] debug if debug output should be shown
54
+ # @option options [Boolean] verbose if verbose output should be shown
51
55
  # @option options [Array<String>] group the list of groups to start
52
56
  # @option options [String] watchdir the director to watch
53
57
  # @option options [String] guardfile the path to the Guardfile
54
58
  # @option options [Boolean] watch_all_modifications watches all file modifications if true
55
59
  #
56
60
  def setup(options = {})
57
- @lock = Mutex.new
58
-
61
+ @lock = Mutex.new
59
62
  @options = options
60
63
  @guards = []
61
64
  self.reset_groups
62
- @interactor = Interactor.new unless @options[:no_interactions]
63
- @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options)
64
-
65
- @options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
65
+ @interactor = Interactor.new unless options[:no_interactions]
66
+ @listener = Listener.select_and_init(options[:watchdir] && File.expand_path(options[:watchdir]), options)
66
67
 
67
68
  UI.clear if @options[:clear]
68
-
69
- debug_command_execution if @options[:debug]
69
+ debug_command_execution if @options[:verbose]
70
70
 
71
71
  self
72
72
  end
73
73
 
74
74
  # Smart accessor for retrieving a specific guard or several guards at once.
75
75
  #
76
- # @param [String, Symbol] filter return the guard with the given name, or nil if not found
77
- # @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found
78
- # @param [Hash] filter returns all guards matching the given Hash.
79
- # Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found
80
- # @param [NilClass] filter returns all guards
81
- #
82
76
  # @see Guard.groups
83
77
  #
78
+ # @example Filter Guards by String or Symbol
79
+ # Guard.guards('rspec')
80
+ # Guard.guards(:rspec)
81
+ #
82
+ # @example Filter Guards by Regexp
83
+ # Guard.guards(/rsp.+/)
84
+ #
85
+ # @example Filter Guards by Hash
86
+ # Guard.guards({ :name => 'rspec', :group => 'backend' })
87
+ #
88
+ # @param [String, Symbol, Regexp, Hash] filter the filter to apply to the Guards
89
+ # @return [Array<Guard>] the filtered Guards
90
+ #
84
91
  def guards(filter = nil)
92
+ @guards ||= []
93
+
85
94
  case filter
86
95
  when String, Symbol
87
96
  @guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
@@ -102,12 +111,18 @@ module Guard
102
111
 
103
112
  # Smart accessor for retrieving a specific group or several groups at once.
104
113
  #
105
- # @param [NilClass] filter returns all groups
106
- # @param [String, Symbol] filter return the group with the given name, or nil if not found
107
- # @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found
108
- #
109
114
  # @see Guard.guards
110
115
  #
116
+ # @example Filter groups by String or Symbol
117
+ # Guard.groups('backend')
118
+ # Guard.groups(:backend)
119
+ #
120
+ # @example Filter groups by Regexp
121
+ # Guard.groups(/(back|front)end/)
122
+ #
123
+ # @param [String, Symbol, Regexp] filter the filter to apply to the Groups
124
+ # @return [Array<Group>] the filtered groups
125
+ #
111
126
  def groups(filter = nil)
112
127
  case filter
113
128
  when String, Symbol
@@ -129,6 +144,13 @@ module Guard
129
144
 
130
145
  # Start Guard by evaluate the `Guardfile`, initialize the declared Guards
131
146
  # and start the available file change listener.
147
+ # Main method for Guard that is called from the CLI when guard starts.
148
+ #
149
+ # - Setup Guard internals
150
+ # - Evaluate the `Guardfile`
151
+ # - Configure Notifiers
152
+ # - Initialize the declared Guards
153
+ # - Start the available file change listener
132
154
  #
133
155
  # @option options [Boolean] clear if auto clear the UI should be done
134
156
  # @option options [Boolean] notify if system notifications should be shown
@@ -142,6 +164,8 @@ module Guard
142
164
 
143
165
  Dsl.evaluate_guardfile(options)
144
166
 
167
+ options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
168
+
145
169
  listener.on_change do |files|
146
170
  Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
147
171
  listener.changed_files += files if Watcher.match_files?(guards, files)
@@ -172,7 +196,7 @@ module Guard
172
196
 
173
197
  # Reload all Guards currently enabled.
174
198
  #
175
- # @param [Hash] An hash with a guard or a group scope
199
+ # @param [Hash] scopes an hash with a guard or a group scope
176
200
  #
177
201
  def reload(scopes)
178
202
  run do
@@ -184,7 +208,7 @@ module Guard
184
208
 
185
209
  # Trigger `run_all` on all Guards currently enabled.
186
210
  #
187
- # @param [Hash] An hash with a guard or a group scope
211
+ # @param [Hash] scopes an hash with a guard or a group scope
188
212
  #
189
213
  def run_all(scopes)
190
214
  run do
@@ -236,16 +260,17 @@ module Guard
236
260
  end
237
261
  end
238
262
 
239
- # Loop through all groups and run the given task (as block) for each Guard.
263
+ # Loop through all groups and run the given task for each Guard.
240
264
  #
241
265
  # Stop the task run for the all Guards within a group if one Guard
242
266
  # throws `:task_has_failed`.
243
267
  #
244
- # @param [Hash] An hash with a guard or a group scope
268
+ # @param [Hash] scopes an hash with a guard or a group scope
269
+ # @yield the task to run
245
270
  #
246
271
  def run_on_guards(scopes = {})
247
- if guard = scopes[:guard]
248
- yield(guard)
272
+ if scope_guard = scopes[:guard]
273
+ yield(scope_guard)
249
274
  else
250
275
  groups = scopes[:group] ? [scopes[:group]] : @groups
251
276
  groups.each do |group|
@@ -390,7 +415,17 @@ module Guard
390
415
  group
391
416
  end
392
417
 
393
- # Tries to load the Guard main class.
418
+ # Tries to load the Guard main class. This transforms the supplied Guard
419
+ # name into a class name:
420
+ #
421
+ # * `guardname` will become `Guard::Guardname`
422
+ # * `dashed-guard-name` will become `Guard::DashedGuardName`
423
+ # * `underscore_guard_name` will become `Guard::UnderscoreGuardName`
424
+ #
425
+ # When no class is found with the strict case sensitive rules, another
426
+ # try is made to locate the class without matching case:
427
+ #
428
+ # * `rspec` will find a class `Guard::RSpec`
394
429
  #
395
430
  # @param [String] name the name of the Guard
396
431
  # @return [Class, nil] the loaded class
@@ -398,10 +433,10 @@ module Guard
398
433
  def get_guard_class(name)
399
434
  name = name.to_s
400
435
  try_require = false
401
- const_name = name.downcase.gsub('-', '')
436
+ const_name = name.gsub(/\/(.?)/) { "::#{ $1.upcase }" }.gsub(/(?:^|[_-])(.)/) { $1.upcase }
402
437
  begin
403
438
  require "guard/#{ name.downcase }" if try_require
404
- self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
439
+ self.const_get(self.constants.find { |c| c.to_s == const_name } || self.constants.find { |c| c.to_s.downcase == const_name.downcase })
405
440
  rescue TypeError
406
441
  unless try_require
407
442
  try_require = true
@@ -439,7 +474,7 @@ module Guard
439
474
  Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
440
475
  else
441
476
  Gem.source_index.find_name(/^guard-/)
442
- end.map { |x| x.name.sub /^guard-/, '' }
477
+ end.map { |x| x.name.sub(/^guard-/, '') }
443
478
  end
444
479
 
445
480
  # Adds a command logger in debug mode. This wraps common command
@@ -25,11 +25,11 @@ module Guard
25
25
  :aliases => '-n',
26
26
  :banner => 'Notifications feature (growl/libnotify)'
27
27
 
28
- method_option :debug,
28
+ method_option :verbose,
29
29
  :type => :boolean,
30
30
  :default => false,
31
- :aliases => '-d',
32
- :banner => 'Print debug messages'
31
+ :aliases => '-v',
32
+ :banner => 'Show verbose messages'
33
33
 
34
34
  method_option :group,
35
35
  :type => :array,
@@ -47,6 +47,12 @@ module Guard
47
47
  :aliases => '-G',
48
48
  :banner => 'Specify a Guardfile'
49
49
 
50
+ method_option :no_vendor,
51
+ :type => :boolean,
52
+ :default => false,
53
+ :aliases => '-I',
54
+ :banner => 'Ignore vendored dependencies'
55
+
50
56
  method_option :watch_all_modifications,
51
57
  :type => :boolean,
52
58
  :default => false,
@@ -9,6 +9,11 @@ module Guard
9
9
  # You can optionally group the Guards with the `group` keyword and ignore certain paths
10
10
  # with the `ignore_paths` keyword.
11
11
  #
12
+ # You can set your preferred system notification library with `notification` and pass
13
+ # some optional configuration options for the library. If you don't configure a library,
14
+ # Guard will automatically pick one with default options (if you don't want notifications,
15
+ # specify `:off` as library). @see ::Guard::Notifier for more information about the supported libraries.
16
+ #
12
17
  # A more advanced DSL use is the `callback` keyword that allows you to execute arbitrary
13
18
  # code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change`
14
19
  # Guards' method. You can even insert more hooks inside these methods.
@@ -25,6 +30,8 @@ module Guard
25
30
  #
26
31
  # @example A sample of a complex Guardfile
27
32
  #
33
+ # notification :growl
34
+ #
28
35
  # group 'frontend' do
29
36
  # guard 'passenger', :ping => true do
30
37
  # watch('config/application.rb')
@@ -102,6 +109,7 @@ module Guard
102
109
  def reevaluate_guardfile
103
110
  ::Guard.guards.clear
104
111
  ::Guard.reset_groups
112
+ ::Guard::Notifier.notifications.clear
105
113
  @@options.delete(:guardfile_contents)
106
114
  Dsl.evaluate_guardfile(@@options)
107
115
  msg = 'Guardfile has been re-evaluated.'
@@ -131,7 +139,7 @@ module Guard
131
139
 
132
140
  # Read the current `Guardfile` content.
133
141
  #
134
- # @param [String] the path to the Guardfile
142
+ # @param [String] guardfile_path the path to the Guardfile
135
143
  #
136
144
  def read_guardfile(guardfile_path)
137
145
  @@options[:guardfile_path] = guardfile_path
@@ -224,7 +232,7 @@ module Guard
224
232
  # The path to the `Guardfile` that is located at
225
233
  # the directory, where Guard has been started from.
226
234
  #
227
- # @param [String] the path to the local Guardfile
235
+ # @return [String] the path to the local Guardfile
228
236
  #
229
237
  def local_guardfile_path
230
238
  File.join(Dir.pwd, 'Guardfile')
@@ -233,7 +241,7 @@ module Guard
233
241
  # The path to the `.Guardfile` that is located at
234
242
  # the users home directory.
235
243
  #
236
- # @param [String] the path to ~/.Guardfile
244
+ # @return [String] the path to ~/.Guardfile
237
245
  #
238
246
  def home_guardfile_path
239
247
  File.expand_path(File.join('~', '.Guardfile'))
@@ -242,7 +250,7 @@ module Guard
242
250
  # The path to the user configuration `.guard.rb`
243
251
  # that is located at the users home directory.
244
252
  #
245
- # @param [String] the path to ~/.guard.rb
253
+ # @return [String] the path to ~/.guard.rb
246
254
  #
247
255
  def user_config_path
248
256
  File.expand_path(File.join('~', '.guard.rb'))
@@ -250,6 +258,24 @@ module Guard
250
258
 
251
259
  end
252
260
 
261
+ # Set notification options for the system notifications.
262
+ # You can set multiple notification, which allows you to show local
263
+ # system notifications and remote notifications with separate libraries.
264
+ # You can also pass `:off` as library to turn off notifications.
265
+ #
266
+ # @example Define multiple notifications
267
+ # notification :growl_notify
268
+ # notification :ruby_gntp, :host => '192.168.1.5'
269
+ #
270
+ # @see Guard::Notifier for available notifier and its options.
271
+ #
272
+ # @param [Symbol, String] notifier the name of the notifier to use
273
+ # @param [Hash] options the notification library options
274
+ #
275
+ def notification(notifier, options = {})
276
+ ::Guard::Notifier.add_notification(notifier.to_sym, options, false)
277
+ end
278
+
253
279
  # Declares a group of guards to be run with `guard start --group group_name`.
254
280
  #
255
281
  # @example Declare two groups of Guards
@@ -310,10 +336,11 @@ module Guard
310
336
  def guard(name, options = {})
311
337
  @watchers = []
312
338
  @callbacks = []
339
+ @current_group ||= :default
313
340
 
314
341
  yield if block_given?
315
342
 
316
- options.update(:group => (@current_group || :default))
343
+ options.update(:group => @current_group)
317
344
  ::Guard.add_guard(name.to_s.downcase, @watchers, @callbacks, options)
318
345
  end
319
346
 
@@ -47,7 +47,7 @@ module Guard
47
47
  def list(options)
48
48
  evaluate_guardfile(options)
49
49
 
50
- installed = guardfile_structure.inject([]) do |installed, group|
50
+ installed_guards = guardfile_structure.inject([]) do |installed, group|
51
51
  group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
52
52
  installed
53
53
  end
@@ -55,7 +55,7 @@ module Guard
55
55
  UI.info 'Available guards:'
56
56
 
57
57
  ::Guard.guard_gem_names.sort.uniq.each do |name|
58
- UI.info " #{ name }#{ installed.include?(name) ? '*' : '' }"
58
+ UI.info " #{ name }#{ installed_guards.include?(name) ? '*' : '' }"
59
59
  end
60
60
 
61
61
  UI.info ''
@@ -141,6 +141,7 @@ module Guard
141
141
  # @see Guard::Dsl#guard
142
142
  #
143
143
  def guard(name, options = { })
144
+ @group ||= false
144
145
  node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
145
146
 
146
147
  node[:guards] << { :name => name, :options => options }
@@ -42,8 +42,8 @@ module Guard
42
42
  #
43
43
  # @param [Array<Guard::Watcher>] watchers the Guard file watchers
44
44
  # @param [Hash] options the custom Guard options
45
- # @options [Symbol] group the group this Guard belongs to
46
- # @options [Boolean] any_return allow any object to be returned from a watcher
45
+ # @option options [Symbol] group the group this Guard belongs to
46
+ # @option options [Boolean] any_return allow any object to be returned from a watcher
47
47
  #
48
48
  def initialize(watchers = [], options = {})
49
49
  @group = options[:group] ? options.delete(:group).to_sym : :default
@@ -1,52 +1,66 @@
1
+ require 'readline'
2
+
1
3
  module Guard
2
4
 
3
5
  # The interactor reads user input and triggers
4
6
  # specific action upon them unless its locked.
5
7
  #
8
+ # It used the readline library for history and
9
+ # completion support.
10
+ #
6
11
  # Currently the following actions are implemented:
7
12
  #
8
- # - stop, quit, exit, s, q, e => Exit Guard
9
- # - reload, r, z => Reload Guard
10
- # - pause, p => Pause Guard
11
- # - Everything else => Run all
13
+ # - h, help => Show help
14
+ # - e, exit => Exit Guard
15
+ # - r, reload => Reload Guard
16
+ # - p, pause => Toggle file modification listener
17
+ # - n, notification => Toggle notifications
18
+ # - <enter> => Run all
12
19
  #
13
20
  # It's also possible to scope `reload` and `run all` actions to only a specified group or a guard.
14
21
  #
15
- # @example `backend reload` will only reload backend group
16
- # @example `spork reload` will only reload rspec guard
17
- # @example `jasmine` will only run all jasmine specs
22
+ # @example Reload backend group
23
+ # backend reload
24
+ #
25
+ # @example Reload rspec guard
26
+ # spork reload
27
+ #
28
+ # @example Run all jasmine specs
29
+ # jasmine
18
30
  #
19
31
  class Interactor
20
32
 
21
- STOP_ACTIONS = %w[stop quit exit s q e]
22
- RELOAD_ACTIONS = %w[reload r z]
23
- PAUSE_ACTIONS = %w[pause p]
33
+ HELP_ENTRIES = %w[help h]
34
+ RELOAD_ENTRIES = %w[reload r]
35
+ STOP_ENTRIES = %w[exit e]
36
+ PAUSE_ENTRIES = %w[pause p]
37
+ NOTIFICATION_ENTRIES = %w[notification n]
24
38
 
25
- # Start the interactor in its own thread.
39
+ COMPLETION_ACTIONS = %w[help reload exit pause notification]
40
+
41
+ # Initialize the interactor.
26
42
  #
27
- def start
28
- return if ENV["GUARD_ENV"] == 'test'
29
-
30
- if !@thread || !@thread.alive?
31
- @thread = Thread.new do
32
- while entry = $stdin.gets.chomp
33
- scopes, action = extract_scopes_and_action(entry)
34
- case action
35
- when :stop
36
- ::Guard.stop
37
- when :pause
38
- ::Guard.pause
39
- when :reload
40
- ::Guard::Dsl.reevaluate_guardfile if scopes.empty?
41
- ::Guard.reload(scopes)
42
- when :run_all
43
- ::Guard.run_all(scopes)
44
- end
45
- end
46
- end
43
+ def initialize
44
+ unless defined?(RbReadline) || defined?(JRUBY_VERSION)
45
+ ::Guard::UI.info 'Please add rb-readline for proper Readline support.'
46
+ end
47
+
48
+ Readline.completion_proc = proc { |word| auto_complete(word) }
49
+
50
+ begin
51
+ Readline.completion_append_character = ' '
52
+ rescue NotImplementedError
53
+ # Ignore, we just don't support it then
47
54
  end
48
55
  end
49
56
 
57
+ # Start the line reader in its own thread.
58
+ #
59
+ def start
60
+ return if ENV['GUARD_ENV'] == 'test'
61
+ @thread = Thread.new { read_line } if !@thread || !@thread.alive?
62
+ end
63
+
50
64
  # Kill interactor thread if not current
51
65
  #
52
66
  def stop
@@ -54,18 +68,128 @@ module Guard
54
68
  @thread.kill
55
69
  end
56
70
  end
57
-
58
- # Extract guard or group scope and action from Interactor entry
71
+
72
+ # Read a line from stdin with Readline.
73
+ #
74
+ def read_line
75
+ while line = Readline.readline(prompt, true)
76
+ process_input(line)
77
+ end
78
+ end
79
+
80
+ # Auto complete the given word.
81
+ #
82
+ # @param [String] word the partial word
83
+ # @return [Array<String>] the matching words
84
+ #
85
+ def auto_complete(word)
86
+ completion_list.grep(/^#{ Regexp.escape(word) }/)
87
+ end
88
+
89
+ # Get the auto completion list.
90
+ #
91
+ # @return [Array<String>] the list of words
92
+ #
93
+ def completion_list
94
+ groups = ::Guard.groups.map { |group| group.name.to_s }
95
+ guards = ::Guard.guards.map { |guard| guard.class.to_s.downcase.sub('guard::', '') }
96
+
97
+ COMPLETION_ACTIONS + groups + guards - ['default']
98
+ end
99
+
100
+ # The current interactor prompt
101
+ #
102
+ # @return [String] the prompt to show
103
+ #
104
+ def prompt
105
+ ::Guard.listener.paused? ? 'p> ' : '> '
106
+ end
107
+
108
+ # Process the input from readline.
109
+ #
110
+ # @param [String] line the input line
111
+ #
112
+ def process_input(line)
113
+ if line =~ /^\s*$/ or Readline::HISTORY.to_a[-2] == line
114
+ Readline::HISTORY.pop
115
+ end
116
+
117
+ scopes, action = extract_scopes_and_action(line)
118
+
119
+ case action
120
+ when :help
121
+ help
122
+ when :stop
123
+ ::Guard.stop
124
+ when :pause
125
+ ::Guard.pause
126
+ when :reload
127
+ reload(scopes)
128
+ when :run_all
129
+ ::Guard.run_all(scopes)
130
+ when :notification
131
+ toggle_notification
132
+ else
133
+ ::Guard::UI.error "Unknown command #{ line }"
134
+ end
135
+ end
136
+
137
+ # Execute the reload action.
138
+ #
139
+ # @param [Hash] scopes the reload scopes
140
+ #
141
+ def reload(scopes)
142
+ ::Guard::UI.info 'Reload'
143
+ ::Guard::Dsl.reevaluate_guardfile if scopes.empty?
144
+ ::Guard.reload(scopes)
145
+ end
146
+
147
+ # Toggle the system notifications on/off
148
+ #
149
+ def toggle_notification
150
+ if ENV['GUARD_NOTIFY'] == 'true'
151
+ ::Guard::UI.info 'Turn off notifications'
152
+ ::Guard::Notifier.turn_off
153
+ else
154
+ ::Guard::Notifier.turn_on
155
+ end
156
+ end
157
+
158
+ # Show the help.
159
+ #
160
+ def help
161
+ puts ''
162
+ puts 'e, exit Exit Guard'
163
+ puts 'p, pause Toggle file modification listener'
164
+ puts 'r, reload Reload Guard'
165
+ puts 'n, notification Toggle notifications'
166
+ puts '<enter> Run all Guards'
167
+ puts ''
168
+ puts 'You can scope the reload action to a specific guard or group:'
169
+ puts ''
170
+ puts 'rspec reload Reload the RSpec Guard'
171
+ puts 'backend reload Reload the backend group'
172
+ puts ''
173
+ puts 'You can also run only a specific Guard or all Guards in a specific group:'
174
+ puts ''
175
+ puts 'jasmine Run the jasmine Guard'
176
+ puts 'frontend Run all Guards in the frontend group'
177
+ puts ''
178
+ end
179
+
180
+ # Extract the Guard or group scope and action from the
181
+ # input line.
59
182
  #
60
183
  # @example `spork reload` will only reload rspec
61
184
  # @example `jasmine` will only run all jasmine specs
62
185
  #
63
- # @param [String] Interactor entry gets from $stdin
64
- # @return [Array] entry group or guard scope hash and action
186
+ # @param [String] line the readline input
187
+ # @return [Array] the group or guard scope and the action
65
188
  #
66
- def extract_scopes_and_action(entry)
67
- scopes = {}
68
- entries = entry.split(' ')
189
+ def extract_scopes_and_action(line)
190
+ scopes = { }
191
+ entries = line.split(' ')
192
+
69
193
  case entries.length
70
194
  when 1
71
195
  unless action = action_from_entry(entries[0])
@@ -75,18 +199,21 @@ module Guard
75
199
  scopes = scopes_from_entry(entries[0])
76
200
  action = action_from_entry(entries[1])
77
201
  end
78
- action ||= :run_all
202
+
203
+ action = :run_all if !action && (!scopes.empty? || entries.empty?)
79
204
 
80
205
  [scopes, action]
81
206
  end
82
207
 
208
+ private
209
+
83
210
  # Extract guard or group scope from entry if valid
84
211
  #
85
- # @param [String] Interactor entry gets from $stdin
86
- # @return [Hash] An hash with a guard or a group scope
212
+ # @param [String] entry the possible scope entry
213
+ # @return [Hash] a hash with a Guard or a group scope
87
214
  #
88
215
  def scopes_from_entry(entry)
89
- scopes = {}
216
+ scopes = { }
90
217
  if guard = ::Guard.guards(entry)
91
218
  scopes[:guard] = guard
92
219
  end
@@ -97,18 +224,22 @@ module Guard
97
224
  scopes
98
225
  end
99
226
 
100
- # Extract action from entry if an existing action is present
227
+ # Find the action for the given input entry.
101
228
  #
102
- # @param [String] Interactor entry gets from $stdin
103
- # @return [Symbol] A guard action
229
+ # @param [String] entry the possible action entry
230
+ # @return [Symbol] a Guard action
104
231
  #
105
232
  def action_from_entry(entry)
106
- if STOP_ACTIONS.include?(entry)
233
+ if STOP_ENTRIES.include?(entry)
107
234
  :stop
108
- elsif RELOAD_ACTIONS.include?(entry)
235
+ elsif RELOAD_ENTRIES.include?(entry)
109
236
  :reload
110
- elsif PAUSE_ACTIONS.include?(entry)
237
+ elsif PAUSE_ENTRIES.include?(entry)
111
238
  :pause
239
+ elsif HELP_ENTRIES.include?(entry)
240
+ :help
241
+ elsif NOTIFICATION_ENTRIES.include?(entry)
242
+ :notification
112
243
  end
113
244
  end
114
245