guard 2.9.0 → 2.9.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2438b30b1e270d58f79c7f2ae9b8e2dce82fd6ad
4
- data.tar.gz: 50e9fa2441d8eefc8aa4adb62405765086eaeda3
3
+ metadata.gz: d5b1ccadd885e53f1a8063e6f6d78038874115e7
4
+ data.tar.gz: ed83d150185583201d3d6a76ebadc54172c94573
5
5
  SHA512:
6
- metadata.gz: d87720ec63302aaeeaacd21db3b76bc76768649af94ead543aba9cba5422f3a934db2e3d6172f7e5f3ec5d8a0ac3d7d3a7d4ba275c16adc52acdf51b2f510f4b
7
- data.tar.gz: 09427674cac325111ae666ec7043e559c5fd4ec7f96125d9ac929e6858aa962ca40a021541b3de6c67f8eb1e49b29c4a5aede1f98daa42617c73e257381833dd
6
+ metadata.gz: 30542f519409c98e17fe922651fb5d461ed771803b46833fd918ae74576bc0e2b5aefe23ec60686d55408fe1bcba687fa587a2f569ae29ede158d915dbbfd0d7
7
+ data.tar.gz: 8e352a2a2b4ec695b4ed2dd029bb5a1b46e38fcd308c2dddbcfcc007395b29498c7e0bbc28caec0c579482f1e3bd7326526f04a413f4a3ea972465cc7770383f
@@ -8,11 +8,13 @@ require "guard/internals/debugging"
8
8
  require "guard/internals/traps"
9
9
  require "guard/internals/helpers"
10
10
 
11
- require "guard/metadata"
12
- require "guard/options"
11
+ require "guard/internals/queue"
12
+ require "guard/internals/state"
13
13
 
14
+ require "guard/options"
14
15
  require "guard/commander"
15
16
  require "guard/dsl"
17
+ require "guard/group"
16
18
  require "guard/interactor"
17
19
  require "guard/notifier"
18
20
  require "guard/plugin_util"
@@ -20,6 +22,7 @@ require "guard/runner"
20
22
  require "guard/sheller"
21
23
  require "guard/ui"
22
24
  require "guard/watcher"
25
+ require "guard/guardfile/evaluator"
23
26
 
24
27
  # Guard is the main module for all Guard related modules and classes.
25
28
  # Also Guard plugins should use this namespace.
@@ -30,6 +33,9 @@ module Guard
30
33
  class << self
31
34
  attr_reader :listener
32
35
 
36
+ # @private api
37
+ attr_reader :queue
38
+
33
39
  include Internals::Helpers
34
40
 
35
41
  # Initializes the Guard singleton:
@@ -46,103 +52,37 @@ module Guard
46
52
  # @option options [String] guardfile the path to the Guardfile
47
53
  #
48
54
  # @return [Guard] the Guard singleton
49
- #
50
-
51
- # TODO: this method has too many instance variables
52
- # and some are mock and leak between tests,
53
- # so ideally there should be a guard "instance"
54
- # object that can be created anew between tests
55
- def setup(opts = {})
56
- # NOTE: must be set before anything calls Guard.options
57
- reset_options(opts)
58
-
59
- # NOTE: must be set before anything calls Guard::UI.debug
60
- ::Guard::Internals::Debugging.start if options[:debug]
55
+ def setup(cmdline_options = {})
56
+ init(cmdline_options)
61
57
 
62
- @queue = Queue.new
63
- self.watchdirs = Array(options[:watchdir])
58
+ @queue = Internals::Queue.new(Guard)
64
59
 
65
- ::Guard::UI.reset_and_clear
60
+ UI.reset_and_clear
66
61
 
67
- _reset_all
68
- evaluate_guardfile
69
- setup_scope
62
+ _evaluate(state.session.evaluator_options)
70
63
 
71
- @listener = _setup_listener
64
+ # NOTE: this should be *after* evaluate so :directories can work
65
+ @listener = Listen.send(*state.session.listener_args, &_listener_callback)
72
66
 
73
- ::Guard::Notifier.connect(notify: options[:notify])
67
+ Notifier.connect(state.session.notify_options)
74
68
 
75
69
  traps = Internals::Traps
76
70
  traps.handle("USR1") { async_queue_add([:guard_pause, :paused]) }
77
71
  traps.handle("USR2") { async_queue_add([:guard_pause, :unpaused]) }
78
72
 
79
- @interactor = ::Guard::Interactor.new(options[:no_interactions])
73
+ @interactor = Interactor.new(state.session.interactor_name == :sleep)
80
74
  traps.handle("INT") { @interactor.handle_interrupt }
81
75
 
82
76
  self
83
77
  end
84
78
 
85
- attr_reader :interactor
86
-
87
- # Used only by tests (for all I know...)
88
- def clear_options
89
- @options = nil
90
- end
91
-
92
- # Initializes the groups array with the default group(s).
93
- #
94
- # @see DEFAULT_GROUPS
95
- #
96
- def reset_groups
97
- @groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
98
- end
99
-
100
- # Initializes the plugins array to an empty array.
101
- #
102
- # @see Guard.plugins
103
- #
104
- def reset_plugins
105
- @plugins = []
106
- end
107
-
108
- attr_reader :watchdirs
109
-
110
- # Stores the scopes defined by the user via the `--group` / `-g` option (to
111
- # run only a specific group) or the `--plugin` / `-P` option (to run only a
112
- # specific plugin).
113
- #
114
- # @see CLI#start
115
- # @see Dsl#scope
116
- #
117
- def setup_scope(scope = {})
118
- # TODO: there should be a special Scope class instead
119
- scope = _prepare_scope(scope)
120
-
121
- ::Guard.scope = {
122
- groups: scope[:groups].map { |item| ::Guard.add_group(item) },
123
- plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
124
- }
79
+ def init(cmdline_options)
80
+ @state = Internals::State.new(cmdline_options)
125
81
  end
126
82
 
127
- # Evaluates the Guardfile content. It displays an error message if no
128
- # Guard plugins are instantiated after the Guardfile evaluation.
129
- #
130
- # @see Guard::Guardfile::Evaluator#evaluate_guardfile
131
- #
132
- def evaluate_guardfile
133
- evaluator = Guard::Guardfile::Evaluator.new(options)
134
- evaluator.evaluate_guardfile
83
+ attr_reader :state
135
84
 
136
- # FIXME: temporary hack while due to upcoming refactorings
137
- options[:guardfile] = evaluator.guardfile_path
138
-
139
- msg = "No plugins found in Guardfile, please add at least one."
140
- ::Guard::UI.error msg if _pluginless_guardfile?
141
- end
142
-
143
- # @private api
144
- # used solely for match_guardfile?
145
- attr_reader :guardfile_path
85
+ attr_reader :interactor
146
86
 
147
87
  # Asynchronously trigger changes
148
88
  #
@@ -160,60 +100,17 @@ module Guard
160
100
  Thread.new { interactor.background }
161
101
  end
162
102
 
163
- def pending_changes?
164
- ! @queue.empty?
165
- end
166
-
167
- def watchdirs=(dirs)
168
- dirs = [Dir.pwd] if dirs.empty?
169
- @watchdirs = dirs.map { |dir| File.expand_path dir }
170
- end
171
-
172
103
  private
173
104
 
174
- # Initializes the listener and registers a callback for changes.
175
- #
176
- def _setup_listener
177
- if options[:listen_on]
178
- Listen.on(options[:listen_on], &_listener_callback)
179
- else
180
- listener_options = {}
181
- [:latency, :force_polling, :wait_for_delay].each do |option|
182
- listener_options[option] = options[option] if options[option]
183
- end
184
- listen_args = watchdirs + [listener_options]
185
- Listen.to(*listen_args, &_listener_callback)
186
- end
187
- end
188
-
189
- # Process the change queue, running tasks within the main Guard thread
190
- def _process_queue
191
- actions, changes = [], { modified: [], added: [], removed: [] }
192
-
193
- while pending_changes?
194
- if (item = @queue.pop).first.is_a?(Symbol)
195
- actions << item
196
- else
197
- item.each { |key, value| changes[key] += value }
198
- end
199
- end
200
-
201
- _run_actions(actions)
202
- return if changes.values.all?(&:empty?)
203
- Runner.new.run_on_changes(*changes.values)
204
- end
205
-
206
- # TODO: Guard::Watch or Guard::Scope should provide this
207
- def _scoped_watchers
208
- watchers = []
209
- Runner.new.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
210
- watchers
211
- end
212
-
213
105
  # Check if any of the changes are actually watched for
106
+ # TODO: why iterate twice? reuse this info when running tasks
214
107
  def _relevant_changes?(changes)
108
+ # TODO: no coverage!
215
109
  files = changes.values.flatten(1)
216
- watchers = _scoped_watchers
110
+ scope = Guard.state.scope
111
+ watchers = scope.grouped_plugins.map do |_group, plugins|
112
+ plugins.map(&:watchers).flatten
113
+ end.flatten
217
114
  watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
218
115
  end
219
116
 
@@ -221,19 +118,6 @@ module Guard
221
118
  paths.map { |path| _relative_pathname(path) }
222
119
  end
223
120
 
224
- def _run_actions(actions)
225
- actions.each do |action_args|
226
- args = action_args.dup
227
- namespaced_action = args.shift
228
- action = namespaced_action.to_s.sub(/^guard_/, "")
229
- if ::Guard.respond_to?(action)
230
- ::Guard.send(action, *args)
231
- else
232
- fail "Unknown action: #{action.inspect}"
233
- end
234
- end
235
- end
236
-
237
121
  def _listener_callback
238
122
  lambda do |modified, added, removed|
239
123
  relative_paths = {
@@ -246,12 +130,7 @@ module Guard
246
130
  end
247
131
  end
248
132
 
249
- def _reset_all
250
- reset_groups
251
- reset_plugins
252
- reset_scope
253
- end
254
-
133
+ # TODO: obsoleted? (move to Dsl?)
255
134
  def _pluginless_guardfile?
256
135
  # no Reevaluator means there was no Guardfile configured that could be
257
136
  # reevaluated, so we don't have a pluginless guardfile, because we don't
@@ -260,19 +139,23 @@ module Guard
260
139
  # But, if we have a Guardfile, we'll at least have the built-in
261
140
  # Reevaluator, so the following will work:
262
141
 
263
- # TODO: this is a workaround for tests
264
- return true if plugins.empty?
265
-
266
- plugins.map(&:name) == ["reevaluator"]
142
+ plugins = state.session.plugins.all
143
+ plugins.empty? || plugins.map(&:name) == ["reevaluator"]
267
144
  end
268
145
 
269
- def _reset_for_tests
270
- @options = nil
271
- @queue = nil
272
- @watchdirs = nil
273
- @listener = nil
274
- @interactor = nil
275
- @scope = nil
146
+ def _evaluate(options)
147
+ evaluator = Guardfile::Evaluator.new(options)
148
+ evaluator.evaluate
149
+ msg = "No plugins found in Guardfile, please add at least one."
150
+ UI.error msg if _pluginless_guardfile?
151
+
152
+ if evaluator.inline?
153
+ UI.info("Using inline Guardfile.")
154
+ elsif evaluator.custom?
155
+ UI.info("Using Guardfile at #{ evaluator.path }.")
156
+ end
157
+ rescue Guardfile::Evaluator::NoPluginsError => e
158
+ UI.error(e.message)
276
159
  end
277
160
  end
278
161
  end
@@ -2,9 +2,8 @@ require "thor"
2
2
 
3
3
  require "guard"
4
4
  require "guard/version"
5
- require "guard/dsl_describer"
6
- require "guard/guardfile/evaluator"
7
5
  require "guard/guardfile/generator"
6
+ require "guard/dsl_describer"
8
7
 
9
8
  module Guard
10
9
  # Facade for the Guard command line interface managed by
@@ -49,6 +48,7 @@ module Guard
49
48
  aliases: "-P",
50
49
  banner: "Run only the passed plugins"
51
50
 
51
+ # TODO: make it plural
52
52
  method_option :watchdir,
53
53
  type: :array,
54
54
  aliases: "-w",
@@ -105,7 +105,7 @@ module Guard
105
105
  #
106
106
  def start
107
107
  _verify_bundler_presence unless options[:no_bundler_warning]
108
- ::Guard.start(options)
108
+ _start(options)
109
109
  end
110
110
 
111
111
  desc "list", "Lists Guard plugins that can be used with init"
@@ -116,7 +116,8 @@ module Guard
116
116
  # @see Guard::DslDescriber.list
117
117
  #
118
118
  def list
119
- ::Guard::DslDescriber.new(options).list
119
+ _require_guardfile(options) # just to show which plugins are actually used
120
+ DslDescriber.new.list
120
121
  end
121
122
 
122
123
  desc "notifiers", "Lists notifiers and its options"
@@ -126,8 +127,8 @@ module Guard
126
127
  # @see Guard::DslDescriber.notifiers
127
128
  #
128
129
  def notifiers
129
- ::Guard.reset_options(options)
130
- ::Guard::DslDescriber.new(options).notifiers
130
+ _require_guardfile(options)
131
+ DslDescriber.new.notifiers
131
132
  end
132
133
 
133
134
  desc "version", "Show the Guard version"
@@ -138,7 +139,7 @@ module Guard
138
139
  # @see Guard::VERSION
139
140
  #
140
141
  def version
141
- $stdout.puts "Guard version #{ ::Guard::VERSION }"
142
+ $stdout.puts "Guard version #{ VERSION }"
142
143
  end
143
144
 
144
145
  desc "init [GUARDS]", "Generates a Guardfile at the current directory"\
@@ -164,19 +165,23 @@ module Guard
164
165
  #
165
166
  def init(*plugin_names)
166
167
  _verify_bundler_presence unless options[:no_bundler_warning]
168
+ bare = options[:bare]
167
169
 
168
- ::Guard.reset_options(options) # Since UI.deprecated uses config
170
+ generator = Guardfile::Generator.new
171
+ Guard.init(options)
172
+ session = Guard.state.session
169
173
 
170
- generator = Guardfile::Generator.new(abort_on_existence: options[:bare])
171
- generator.create_guardfile
174
+ begin
175
+ Guardfile::Evaluator.new(session.evaluator_options).evaluate
176
+ rescue Guardfile::Evaluator::NoGuardfileError
177
+ generator.create_guardfile
178
+ end
172
179
 
173
- # Note: this reset "hack" will be fixed after refactoring
174
- ::Guard.reset_plugins
180
+ return if bare
175
181
 
176
182
  # Evaluate because it might have existed and creating was skipped
177
- ::Guard::Guardfile::Evaluator.new(Guard.options).evaluate_guardfile
178
-
179
- return if options[:bare]
183
+ # FIXME: still, I don't know why this is needed
184
+ Guardfile::Evaluator.new(session.evaluator_options).evaluate
180
185
 
181
186
  if plugin_names.empty?
182
187
  generator.initialize_all_templates
@@ -196,7 +201,9 @@ module Guard
196
201
  # @see Guard::DslDescriber.show
197
202
  #
198
203
  def show
199
- ::Guard::DslDescriber.new(options).show
204
+ _require_guardfile(options)
205
+ # TODO: use Metadata class
206
+ DslDescriber.new.show
200
207
  end
201
208
 
202
209
  private
@@ -204,11 +211,12 @@ module Guard
204
211
  # Verifies if Guard is run with `bundle exec` and
205
212
  # shows a hint to do so if not.
206
213
  #
214
+ # TODO: move this elsewhere!!! (because of complex specs)
207
215
  def _verify_bundler_presence
208
216
  return unless File.exist?("Gemfile")
209
217
  return if ENV["BUNDLE_GEMFILE"] || ENV["RUBYGEMS_GEMDEPS"]
210
218
 
211
- ::Guard::UI.info <<EOF
219
+ UI.info <<EOF
212
220
 
213
221
  Guard here! It looks like your project has a Gemfile, yet you are running
214
222
  `guard` outside of Bundler. If this is your intent, feel free to ignore this
@@ -217,5 +225,29 @@ dependencies are loaded correctly.
217
225
  (You can run `guard` with --no-bundler-warning to get rid of this message.)
218
226
  EOF
219
227
  end
228
+
229
+ def _require_guardfile(options)
230
+ Guard.init(options) # to setup metadata
231
+ session = Guard.state.session
232
+ Guardfile::Evaluator.new(session.evaluator_options).evaluate
233
+ rescue Dsl::Error,
234
+ Guardfile::Evaluator::NoPluginsError,
235
+ Guardfile::Evaluator::NoGuardfileError,
236
+ Guardfile::Evaluator::NoCustomGuardfile => e
237
+ # catch to throw message instead of call stack
238
+ UI.error(e.message)
239
+ abort
240
+ end
241
+
242
+ def _start(options)
243
+ Guard.start(options)
244
+ rescue Dsl::Error,
245
+ Guardfile::Evaluator::NoPluginsError,
246
+ Guardfile::Evaluator::NoGuardfileError,
247
+ Guardfile::Evaluator::NoCustomGuardfile => e
248
+ # catch to throw message instead of call stack
249
+ UI.error(e.message)
250
+ abort
251
+ end
220
252
  end
221
253
  end
@@ -3,6 +3,8 @@ require "listen"
3
3
  require "guard/notifier"
4
4
  require "guard/interactor"
5
5
  require "guard/runner"
6
+ require "guard/reevaluator"
7
+ require "guard/dsl_describer"
6
8
  require "guard"
7
9
 
8
10
  module Guard
@@ -28,16 +30,16 @@ module Guard
28
30
  #
29
31
  def start(options = {})
30
32
  setup(options)
31
- ::Guard::UI.debug "Guard starts all plugins"
32
- Guard::Runner.new.run(:start)
33
+ UI.debug "Guard starts all plugins"
34
+ Runner.new.run(:start)
33
35
  listener.start
34
36
 
35
- watched = ::Guard.watchdirs.join("', '")
36
- ::Guard::UI.info "Guard is now watching at '#{ watched }'"
37
+ watched = Guard.state.session.watchdirs.join("', '")
38
+ UI.info "Guard is now watching at '#{ watched }'"
37
39
 
38
40
  begin
39
41
  while interactor.foreground != :exit
40
- _process_queue while pending_changes?
42
+ Guard.queue.process while Guard.queue.pending?
41
43
  end
42
44
  rescue Interrupt
43
45
  end
@@ -45,14 +47,13 @@ module Guard
45
47
  stop
46
48
  end
47
49
 
48
- # TODO: refactor (left to avoid breaking too many specs)
49
50
  def stop
50
51
  listener.stop
51
52
  interactor.background
52
- ::Guard::UI.debug "Guard stops all plugins"
53
- Guard::Runner.new.run(:stop)
54
- ::Guard::Notifier.disconnect
55
- ::Guard::UI.info "Bye bye...", reset: true
53
+ UI.debug "Guard stops all plugins"
54
+ Runner.new.run(:stop)
55
+ Notifier.disconnect
56
+ UI.info "Bye bye...", reset: true
56
57
  end
57
58
 
58
59
  # Reload Guardfile and all Guard plugins currently enabled.
@@ -63,13 +64,13 @@ module Guard
63
64
  #
64
65
  def reload(scopes = {})
65
66
  # TODO: guard reevaluator should probably handle all this
66
- ::Guard::UI.clear(force: true)
67
- ::Guard::UI.action_with_scopes("Reload", scopes)
67
+ UI.clear(force: true)
68
+ UI.action_with_scopes("Reload", scopes)
68
69
 
69
70
  if scopes.empty?
70
- Guard::Guardfile::Evaluator.new(Guard.options).reevaluate_guardfile
71
+ Reevaluator.new.reevaluate
71
72
  else
72
- Guard::Runner.new.run(:reload, scopes)
73
+ Runner.new.run(:reload, scopes)
73
74
  end
74
75
  end
75
76
 
@@ -78,8 +79,8 @@ module Guard
78
79
  # @param [Hash] scopes hash with a Guard plugin or a group scope
79
80
  #
80
81
  def run_all(scopes = {})
81
- ::Guard::UI.clear(force: true)
82
- ::Guard::UI.action_with_scopes("Run", scopes)
82
+ UI.clear(force: true)
83
+ UI.action_with_scopes("Run", scopes)
83
84
  Guard::Runner.new.run(:run_all, scopes)
84
85
  end
85
86
 
@@ -97,7 +98,7 @@ module Guard
97
98
  end
98
99
 
99
100
  def show
100
- ::Guard::DslDescriber.new(::Guard.options).show
101
+ DslDescriber.new.show
101
102
  end
102
103
  end
103
104
  extend Commander