guard 1.4.0 → 1.5.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.
@@ -0,0 +1,35 @@
1
+ module Guard
2
+ class Interactor
3
+
4
+ RELOAD = Pry::CommandSet.new do
5
+ create_command 'reload' do
6
+
7
+ group 'Guard'
8
+ description 'Reload all plugins.'
9
+
10
+ banner <<-BANNER
11
+ Usage: reload <scope>
12
+
13
+ Run the Guard plugin `reload` action.
14
+
15
+ You may want to specify an optional scope to the action,
16
+ either the name of a Guard plugin or a plugin group.
17
+ BANNER
18
+
19
+ def process(*entries)
20
+ scopes, rest = ::Guard::Interactor.convert_scope(entries)
21
+
22
+ if rest.length == 0
23
+ ::Guard.reload scopes
24
+ else
25
+ output.puts "Unkown scope #{ rest.join(', ') }"
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+
35
+ Pry.commands.import ::Guard::Interactor::RELOAD
@@ -0,0 +1,27 @@
1
+ require 'guard/dsl_describer'
2
+
3
+ module Guard
4
+ class Interactor
5
+
6
+ SHOW = Pry::CommandSet.new do
7
+ create_command 'show' do
8
+
9
+ group 'Guard'
10
+ description 'Show all Guard plugins.'
11
+
12
+ banner <<-BANNER
13
+ Usage: show <scope>
14
+
15
+ Show all defined Guard plugins and their options.
16
+ BANNER
17
+
18
+ def process
19
+ ::Guard::DslDescriber.show(::Guard.options)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ Pry.commands.import ::Guard::Interactor::SHOW
data/lib/guard/dsl.rb CHANGED
@@ -16,7 +16,7 @@ module Guard
16
16
  #
17
17
  # A more advanced DSL use is the `callback` keyword that allows you to execute arbitrary
18
18
  # code before or after any of the `start`, `stop`, `reload`, `run_all`, `run_on_changes`,
19
- # `run_on_additions`, `run_on_modifications` and `run_on_removals` Guard plugins method.
19
+ # `run_on_additions`, `run_on_modifications` and `run_on_removals` Guard plugins method.
20
20
  # You can even insert more hooks inside these methods.
21
21
  # Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
22
22
  #
@@ -93,12 +93,21 @@ module Guard
93
93
 
94
94
  # Deprecation message for the `ignore_paths` method
95
95
  IGNORE_PATHS_DEPRECATION = <<-EOS.gsub(/^\s*/, '')
96
- Starting with Guard v1.1 the use of the 'ignore_paths' Guardfile dsl method is deprecated.
96
+ Starting with Guard v1.1 the use of the 'ignore_paths' Guardfile DSL method is deprecated.
97
97
 
98
98
  Please replace that method with the better 'ignore' or/and 'filter' methods.
99
99
  Documentation on the README: https://github.com/guard/guard#guardfile-dsl-ignore
100
100
  EOS
101
101
 
102
+ # Deprecation message for the `ignore_paths` method
103
+ INTERACTOR_DEPRECATION = <<-EOS.gsub(/^\s*/, '')
104
+ Starting with Guard v1.4 the use of the 'interactor' Guardfile DSL method is only used to
105
+ turn the Pry interactor off. All other usages are deprecated.
106
+
107
+ Please make use of the Pry plugin architecture to customize the interactions and place them
108
+ either in your `~/.guardrc` or the `Guardfile`.
109
+ EOS
110
+
102
111
  class << self
103
112
 
104
113
  @@options = nil
@@ -315,17 +324,15 @@ module Guard
315
324
 
316
325
  # Sets the interactor to use.
317
326
  #
318
- # @example Use the readline interactor
319
- # interactor :readline
320
- #
321
- # @example Use the gets interactor
322
- # interactor :gets
323
- #
324
327
  # @example Turn off interactions
325
328
  # interactor :off
326
329
  #
327
330
  def interactor(interactor)
328
- ::Guard::Interactor.interactor = interactor.to_sym
331
+ if interactor == :off
332
+ ::Guard.options[:no_interactions] = true
333
+ else
334
+ ::Guard::UI.deprecation(INTERACTOR_DEPRECATION)
335
+ end
329
336
  end
330
337
 
331
338
  # Declares a group of Guard plugins to be run with `guard start --group group_name`.
@@ -465,5 +472,62 @@ module Guard
465
472
  ::Guard.listener = ::Guard.listener.filter(*regexps)
466
473
  end
467
474
 
475
+ # Configure the Guard logger.
476
+ #
477
+ # * Log level must be either `:debug`, `:info`, `:warn` or `:error`.
478
+ # * Template supports the following placeholders: `:time`, `:severity`, `:progname`, `:pid`, `:unit_of_work_id` and `:message`
479
+ # * Time format directives are the same as Time#strftime or :milliseconds
480
+ # * The `:only` and `:except` options must be a RegExp.
481
+ #
482
+ # @example Set the log level
483
+ # logger :level => :warn
484
+ #
485
+ # @example Set a custom log template
486
+ # logger :template => '[Guard - :severity - :progname - :time] :message'
487
+ #
488
+ # @example Set a custom time format
489
+ # logger :time_format => '%h'
490
+ #
491
+ # @example Limit logging to a Guard plugin
492
+ # logger :only => :jasmine
493
+ #
494
+ # @example Log all but not the messages from a specific Guard plugin
495
+ # logger :except => :jasmine
496
+ #
497
+ # @param [Hash] options the log options
498
+ # @option options [String, Symbol] level the log level
499
+ # @option options [String] template the logger template
500
+ # @option options [String, Symbol] time_format the time format
501
+ # @option options [RegExp] only show only messages from the matching Guard plugin
502
+ # @option options [RegExp] except does not show messages from the matching Guard plugin
503
+ #
504
+ def logger(options)
505
+ if options[:level]
506
+ options[:level] = options[:level].to_sym
507
+
508
+ unless [:debug, :info, :warn, :error].include? options[:level]
509
+ ::Guard::UI.warning "Invalid log level `#{ options[:level] }` ignored. Please use either :debug, :info, :warn or :error."
510
+ options.delete :level
511
+ end
512
+ end
513
+
514
+ if options[:only] && options[:except]
515
+ ::Guard::UI.warning "You cannot specify the logger options :only and :except at the same time."
516
+
517
+ options.delete :only
518
+ options.delete :except
519
+ end
520
+
521
+ # Convert the :only and :except options to a regular expression
522
+ [:only, :except].each do |name|
523
+ if options[name]
524
+ list = [].push(options[name]).flatten.map { |plugin| Regexp.escape(plugin.to_s) }.join('|')
525
+ options[name] = Regexp.new(list, Regexp::IGNORECASE)
526
+ end
527
+ end
528
+
529
+ ::Guard::UI.options = ::Guard::UI.options.merge options
530
+ end
531
+
468
532
  end
469
533
  end
@@ -1,110 +1,123 @@
1
1
  module Guard
2
2
 
3
- # The interactor triggers specific action from input
4
- # read by a interactor implementation.
5
- #
6
- # Currently the following actions are implemented:
7
- #
8
- # - h, help => Show help
9
- # - e, exit,
10
- # q. quit => Exit Guard
11
- # - r, reload => Reload Guard
12
- # - p, pause => Toggle file modification listener
13
- # - n, notification => Toggle notifications
14
- # - s, show => Show Guard plugin configuration
15
- # - c, change => Trigger a file change
16
- # - <enter> => Run all
17
- #
18
- # It's also possible to scope `reload` and `run all` actions to only a specified group or a guard.
19
- #
20
- # @example Reload backend group
21
- # backend reload
22
- # reload backend
23
- #
24
- # @example Reload rspec guard
25
- # spork reload
26
- # reload spork
27
- #
28
- # @example Run all jasmine specs
29
- # jasmine
30
- #
31
- # @abstract
3
+ # The Guard interactor is a Pry REPL with a Guard
4
+ # specific command set.
32
5
  #
33
6
  class Interactor
34
7
 
8
+ require 'pry'
9
+
35
10
  require 'guard'
36
11
  require 'guard/ui'
37
- require 'guard/dsl_describer'
38
- require 'guard/notifier'
39
- require 'guard/interactors/readline'
40
- require 'guard/interactors/coolline'
41
- require 'guard/interactors/simple'
42
-
43
- ACTIONS = {
44
- :help => %w[help h],
45
- :reload => %w[reload r],
46
- :stop => %w[exit e quit q],
47
- :pause => %w[pause p],
48
- :notification => %w[notification n],
49
- :show => %w[show s],
50
- :change => %w[change c]
51
- }
52
-
53
- # Set the interactor implementation
54
- #
55
- # @param [Symbol] interactor the name of the interactor
12
+
13
+ require 'guard/commands/all'
14
+ require 'guard/commands/change'
15
+ require 'guard/commands/notification'
16
+ require 'guard/commands/pause'
17
+ require 'guard/commands/reload'
18
+ require 'guard/commands/show'
19
+
20
+ GUARD_RC = '~/.guardrc'
21
+ HISTORY_FILE = '~/.guard_history'
22
+
23
+ # Initialize the interactor. This configures
24
+ # Pry and creates some custom commands and aliases
25
+ # for Guard.
56
26
  #
57
- def self.interactor=(interactor)
58
- @interactor = interactor
27
+ def initialize
28
+ return if ENV['GUARD_ENV'] == 'test'
29
+
30
+ Pry.config.history.file = HISTORY_FILE
31
+
32
+ load_guard_rc
33
+
34
+ create_run_all_command
35
+ create_command_aliases
36
+ create_guard_commands
37
+ create_group_commands
38
+
39
+ configure_prompt
59
40
  end
60
41
 
61
- # Get an instance of the currently configured
62
- # interactor implementation.
42
+ # Loads the `~/.guardrc` file when pry has started.
63
43
  #
64
- # @return [Interactor] an interactor implementation
65
- #
66
- def self.fabricate
67
- case @interactor
68
- when :coolline
69
- ::Guard::CoollineInteractor.new if ::Guard::CoollineInteractor.available?
70
- when :readline
71
- ::Guard::ReadlineInteractor.new if ::Guard::ReadlineInteractor.available?
72
- when :simple
73
- ::Guard::SimpleInteractor.new
74
- when :off
75
- nil
76
- else
77
- auto_detect
44
+ def load_guard_rc
45
+ Pry.config.hooks.add_hook :when_started, :load_guard_rc do
46
+ load GUARD_RC if File.exist? File.expand_path GUARD_RC
78
47
  end
79
48
  end
80
49
 
81
- # Tries to detect an optimal interactor for the
82
- # current environment.
83
- #
84
- # It returns the Readline implementation when:
85
- #
86
- # * rb-readline is installed
87
- # * The Ruby implementation is JRuby
88
- # * The current OS is not Mac OS X
50
+ # Creates a command that triggers the `:run_all` action
51
+ # when the command is empty (just pressing enter on the
52
+ # beginning of a line).
89
53
  #
90
- # Otherwise the plain gets interactor is returned.
54
+ def create_run_all_command
55
+ Pry.commands.block_command /^$/, 'Hit enter to run all' do
56
+ Pry.run_command 'all'
57
+ end
58
+ end
59
+
60
+ # Creates command aliases for the commands
61
+ # `help`, `reload`, `change`, `show`, `notification`, `pause`, `exit` and `quit`,
62
+ # which will be the first letter of the command.
91
63
  #
92
- # @return [Interactor] an interactor implementation
64
+ def create_command_aliases
65
+ %w(help reload change show notification pause exit quit).each do |command|
66
+ Pry.commands.alias_command command[0].chr, command
67
+ end
68
+ end
69
+
70
+ # Create a shorthand command to run the `:run_all`
71
+ # action on a specific Guard plugin. For example,
72
+ # when guard-rspec is available, then a command
73
+ # `rspec` is created that runs `all rspec`.
93
74
  #
94
- def self.auto_detect
95
- [::Guard::CoollineInteractor, ::Guard::ReadlineInteractor, ::Guard::SimpleInteractor].detect do |interactor|
96
- interactor.available?(true)
97
- end.new
75
+ def create_guard_commands
76
+ ::Guard.guards.each do |guard|
77
+ name = guard.class.to_s.downcase.sub('guard::', '')
78
+
79
+ Pry.commands.create_command name, "Run all #{ name }" do
80
+ group 'Guard'
81
+
82
+ def process
83
+ Pry.run_command "all #{ match }"
84
+ end
85
+ end
86
+ end
98
87
  end
99
88
 
100
- # Template method for checking if the Interactor is
101
- # available in the current environment?
89
+ # Create a shorthand command to run the `:run_all`
90
+ # action on a specific Guard group. For example,
91
+ # when you have a group `frontend`, then a command
92
+ # `frontend` is created that runs `all frontend`.
102
93
  #
103
- # @param [Boolean] silent true if no error messages should be shown
104
- # @return [Boolean] the availability status
94
+ def create_group_commands
95
+ ::Guard.groups.each do |group|
96
+ name = group.name.to_s
97
+ next if name == 'default'
98
+
99
+ Pry.commands.create_command name, "Run all #{ name }" do
100
+ group 'Guard'
101
+
102
+ def process
103
+ Pry.run_command "all #{ match }"
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ # Configure the pry prompt to see `guard` instead of
110
+ # `pry`.
105
111
  #
106
- def self.available?(silent = false)
107
- true
112
+ def configure_prompt
113
+ Pry.config.prompt = [
114
+ proc do |target_self, nest_level, pry|
115
+ "[#{ pry.input_array.size }] #{ ::Guard.listener.paused? ? 'pause' : 'guard' }(#{ Pry.view_clip(target_self) })#{":#{ nest_level }" unless nest_level.zero? }> "
116
+ end,
117
+ proc do |target_self, nest_level, pry|
118
+ "[#{ pry.input_array.size }] #{ ::Guard.listener.paused? ? 'pause' : 'guard' }(#{ Pry.view_clip(target_self) })#{":#{ nest_level }" unless nest_level.zero? }* "
119
+ end
120
+ ]
108
121
  end
109
122
 
110
123
  # Start the line reader in its own thread.
@@ -112,8 +125,17 @@ module Guard
112
125
  def start
113
126
  return if ENV['GUARD_ENV'] == 'test'
114
127
 
115
- ::Guard::UI.debug 'Start interactor'
116
- @thread = Thread.new { read_line } if !@thread || !@thread.alive?
128
+ store_terminal_settings if stty_exists?
129
+
130
+ if !@thread || !@thread.alive?
131
+ ::Guard::UI.debug 'Start interactor'
132
+
133
+ @thread = Thread.new do
134
+ Pry.start
135
+ ::Guard.stop
136
+ exit
137
+ end
138
+ end
117
139
  end
118
140
 
119
141
  # Kill interactor thread if not current
@@ -121,157 +143,57 @@ module Guard
121
143
  def stop
122
144
  return if !@thread || ENV['GUARD_ENV'] == 'test'
123
145
 
124
- ::Guard::UI.debug 'Stop interactor'
125
146
  unless Thread.current == @thread
147
+ ::Guard::UI.debug 'Stop interactor'
126
148
  @thread.kill
127
149
  end
128
- end
129
150
 
130
- # Read the user input. This method must be implemented
131
- # by each interactor implementation.
132
- #
133
- # @abstract
134
- #
135
- def read_line
136
- raise NotImplementedError
151
+ restore_terminal_settings if stty_exists?
137
152
  end
138
153
 
139
- # Process the input from readline.
140
- #
141
- # @param [String] line the input line
154
+ # Detects whether or not the stty command exists
155
+ # on the user machine.
142
156
  #
143
- def process_input(line)
144
- scopes, action, rest = extract_scopes_and_action(line)
145
-
146
- case action
147
- when :help
148
- help
149
- when :show
150
- ::Guard::DslDescriber.show(::Guard.options)
151
- when :stop
152
- ::Guard.stop
153
- exit
154
- when :pause
155
- ::Guard.pause
156
- when :reload
157
- ::Guard.reload(scopes)
158
- when :change
159
- ::Guard.within_preserved_state do
160
- ::Guard.runner.run_on_changes(rest, [], [])
161
- end
162
- when :run_all
163
- ::Guard.run_all(scopes)
164
- when :notification
165
- toggle_notification
166
- else
167
- ::Guard::UI.error "Unknown command #{ line }"
168
- end
169
- end
170
-
171
- # Toggle the system notifications on/off
157
+ # @return [Boolean] the status of stty
172
158
  #
173
- def toggle_notification
174
- if ENV['GUARD_NOTIFY'] == 'true'
175
- ::Guard::UI.info 'Turn off notifications'
176
- ::Guard::Notifier.turn_off
177
- else
178
- ::Guard::Notifier.turn_on
179
- end
159
+ def stty_exists?
160
+ @stty_exists ||= system('hash', 'stty')
180
161
  end
181
162
 
182
- # Show the help.
163
+ # Stores the terminal settings so we can resore them
164
+ # when stopping.
183
165
  #
184
- def help
185
- puts ''
186
- puts '[e]xit, [q]uit Exit Guard'
187
- puts '[p]ause Toggle file modification listener'
188
- puts '[r]eload Reload Guard'
189
- puts '[n]otification Toggle notifications'
190
- puts '[s]how Show available Guard plugins'
191
- puts '[c]hange <file> Trigger a file change'
192
- puts '<enter> Run all Guard plugins'
193
- puts ''
194
- puts 'You can scope the reload action to a specific guard or group:'
195
- puts ''
196
- puts 'rspec reload Reload the RSpec Guard'
197
- puts 'backend reload Reload the backend group'
198
- puts ''
199
- puts 'You can also run only a specific Guard or all Guard plugins in a specific group:'
200
- puts ''
201
- puts 'jasmine Run the jasmine Guard'
202
- puts 'frontend Run all Guard plugins in the frontend group'
203
- puts ''
166
+ def store_terminal_settings
167
+ @stty_save = `stty -g 2>/dev/null`.chomp
204
168
  end
205
169
 
206
- # Extract the Guard or group scope and action from the
207
- # input line. There's no strict order for scopes and
208
- # actions.
209
- #
210
- # @example `spork reload` will only reload rspec
211
- # @example `jasmine` will only run all jasmine specs
170
+ # Restore terminal settings
212
171
  #
213
- # @param [String] line the readline input
214
- # @return [Array] the group or guard scope, the action and the rest
215
- #
216
- def extract_scopes_and_action(line)
217
- entries = line.split(' ')
218
-
219
- scopes = extract_scopes(entries)
220
- action = extract_action(entries)
221
-
222
- action = :run_all if !action && (!scopes.empty? || entries.empty?)
223
-
224
- [scopes, action, entries]
172
+ def restore_terminal_settings
173
+ system("stty #{ @stty_save } 2>/dev/null") if @stty_save
225
174
  end
226
175
 
227
- private
228
-
229
- # Extract a guard or group scope from entry if valid.
230
- # Any entry found will be removed from the entries.
176
+ # Converts and validates a plain text scope
177
+ # to a valid plugin or group scope.
231
178
  #
232
- # @param [Array<String>] entries the user entries
233
- # @return [Hash] a hash with a Guard or a group scope
179
+ # @param [Array<String>] entries the text scope
180
+ # @return [Hash, Array<String>] the plugin or group scope, the unknown entries
234
181
  #
235
- def extract_scopes(entries)
236
- scopes = { }
182
+ def self.convert_scope(entries)
183
+ scopes = { }
184
+ unknown = []
237
185
 
238
- entries.delete_if do |entry|
186
+ entries.each do |entry|
239
187
  if guard = ::Guard.guards(entry)
240
188
  scopes[:guard] ||= guard
241
- true
242
-
243
189
  elsif group = ::Guard.groups(entry)
244
190
  scopes[:group] ||= group
245
- true
246
-
247
191
  else
248
- false
192
+ unknown << entry
249
193
  end
250
194
  end
251
195
 
252
- scopes
196
+ [scopes, unknown]
253
197
  end
254
-
255
- # Find the action for the given input entry.
256
- # Any action found will be removed from the entries.
257
- #
258
- # @param [Array<String>] entries the user entries
259
- # @return [Symbol] a Guard action
260
- #
261
- def extract_action(entries)
262
- action = nil
263
-
264
- entries.delete_if do |entry|
265
- if command = ACTIONS.detect { |k, list| list.include?(entry) }
266
- action ||= command.first
267
- true
268
- else
269
- false
270
- end
271
- end
272
-
273
- action
274
- end
275
-
276
198
  end
277
199
  end