guard 1.4.0 → 2.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -677
- data/LICENSE +4 -2
- data/README.md +91 -753
- data/bin/_guard-core +11 -0
- data/bin/guard +108 -3
- data/lib/guard/aruba_adapter.rb +59 -0
- data/lib/guard/cli/environments/bundler.rb +22 -0
- data/lib/guard/cli/environments/evaluate_only.rb +35 -0
- data/lib/guard/cli/environments/valid.rb +69 -0
- data/lib/guard/cli.rb +129 -128
- data/lib/guard/commander.rb +104 -0
- data/lib/guard/commands/all.rb +37 -0
- data/lib/guard/commands/change.rb +31 -0
- data/lib/guard/commands/notification.rb +26 -0
- data/lib/guard/commands/pause.rb +29 -0
- data/lib/guard/commands/reload.rb +36 -0
- data/lib/guard/commands/scope.rb +38 -0
- data/lib/guard/commands/show.rb +24 -0
- data/lib/guard/config.rb +18 -0
- data/lib/guard/deprecated/dsl.rb +45 -0
- data/lib/guard/deprecated/evaluator.rb +39 -0
- data/lib/guard/deprecated/guard.rb +328 -0
- data/lib/guard/deprecated/guardfile.rb +84 -0
- data/lib/guard/deprecated/watcher.rb +27 -0
- data/lib/guard/dsl.rb +332 -363
- data/lib/guard/dsl_describer.rb +132 -122
- data/lib/guard/dsl_reader.rb +51 -0
- data/lib/guard/group.rb +34 -14
- data/lib/guard/guardfile/evaluator.rb +232 -0
- data/lib/guard/guardfile/generator.rb +128 -0
- data/lib/guard/guardfile.rb +24 -60
- data/lib/guard/interactor.rb +31 -255
- data/lib/guard/internals/debugging.rb +68 -0
- data/lib/guard/internals/groups.rb +40 -0
- data/lib/guard/internals/helpers.rb +13 -0
- data/lib/guard/internals/plugins.rb +53 -0
- data/lib/guard/internals/queue.rb +51 -0
- data/lib/guard/internals/scope.rb +121 -0
- data/lib/guard/internals/session.rb +180 -0
- data/lib/guard/internals/state.rb +25 -0
- data/lib/guard/internals/tracing.rb +33 -0
- data/lib/guard/internals/traps.rb +10 -0
- data/lib/guard/jobs/base.rb +21 -0
- data/lib/guard/jobs/pry_wrapper.rb +336 -0
- data/lib/guard/jobs/sleep.rb +26 -0
- data/lib/guard/notifier.rb +46 -212
- data/lib/guard/options.rb +22 -0
- data/lib/guard/plugin.rb +303 -0
- data/lib/guard/plugin_util.rb +191 -0
- data/lib/guard/rake_task.rb +42 -0
- data/lib/guard/runner.rb +80 -140
- data/lib/guard/templates/Guardfile +14 -0
- data/lib/guard/terminal.rb +13 -0
- data/lib/guard/ui/colors.rb +56 -0
- data/lib/guard/ui/config.rb +70 -0
- data/lib/guard/ui/logger.rb +30 -0
- data/lib/guard/ui.rb +163 -128
- data/lib/guard/version.rb +1 -2
- data/lib/guard/watcher/pattern/deprecated_regexp.rb +45 -0
- data/lib/guard/watcher/pattern/match_result.rb +18 -0
- data/lib/guard/watcher/pattern/matcher.rb +33 -0
- data/lib/guard/watcher/pattern/pathname_path.rb +15 -0
- data/lib/guard/watcher/pattern/simple_path.rb +23 -0
- data/lib/guard/watcher/pattern.rb +24 -0
- data/lib/guard/watcher.rb +52 -95
- data/lib/guard.rb +108 -376
- data/lib/tasks/releaser.rb +116 -0
- data/man/guard.1 +12 -9
- data/man/guard.1.html +18 -12
- metadata +148 -77
- data/images/guard.png +0 -0
- data/lib/guard/guard.rb +0 -156
- data/lib/guard/hook.rb +0 -120
- data/lib/guard/interactors/coolline.rb +0 -64
- data/lib/guard/interactors/helpers/completion.rb +0 -32
- data/lib/guard/interactors/helpers/terminal.rb +0 -46
- data/lib/guard/interactors/readline.rb +0 -94
- data/lib/guard/interactors/simple.rb +0 -19
- data/lib/guard/notifiers/emacs.rb +0 -69
- data/lib/guard/notifiers/gntp.rb +0 -118
- data/lib/guard/notifiers/growl.rb +0 -99
- data/lib/guard/notifiers/growl_notify.rb +0 -92
- data/lib/guard/notifiers/libnotify.rb +0 -96
- data/lib/guard/notifiers/notifysend.rb +0 -84
- data/lib/guard/notifiers/rb_notifu.rb +0 -102
- data/lib/guard/notifiers/terminal_notifier.rb +0 -66
- data/lib/guard/notifiers/tmux.rb +0 -69
- data/lib/guard/version.rbc +0 -130
data/lib/guard.rb
CHANGED
@@ -1,437 +1,169 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "thread"
|
2
|
+
require "listen"
|
3
|
+
|
4
|
+
require "guard/config"
|
5
|
+
require "guard/deprecated/guard" unless Guard::Config.new.strict?
|
6
|
+
require "guard/internals/helpers"
|
7
|
+
|
8
|
+
require "guard/internals/debugging"
|
9
|
+
require "guard/internals/traps"
|
10
|
+
require "guard/internals/queue"
|
11
|
+
|
12
|
+
# TODO: remove this class altogether
|
13
|
+
require "guard/interactor"
|
3
14
|
|
4
15
|
# Guard is the main module for all Guard related modules and classes.
|
5
16
|
# Also Guard plugins should use this namespace.
|
6
|
-
#
|
7
17
|
module Guard
|
18
|
+
Deprecated::Guard.add_deprecated(self) unless Config.new.strict?
|
8
19
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
require 'guard/runner'
|
15
|
-
require 'guard/ui'
|
16
|
-
require 'guard/watcher'
|
17
|
-
|
18
|
-
# The Guardfile template for `guard init`
|
19
|
-
GUARDFILE_TEMPLATE = File.expand_path('../guard/templates/Guardfile', __FILE__)
|
20
|
+
class << self
|
21
|
+
attr_reader :state
|
22
|
+
attr_reader :queue
|
23
|
+
attr_reader :listener
|
24
|
+
attr_reader :interactor
|
20
25
|
|
21
|
-
|
22
|
-
HOME_TEMPLATES = File.expand_path('~/.guard/templates')
|
26
|
+
# @private api
|
23
27
|
|
24
|
-
|
25
|
-
attr_accessor :options, :interactor, :runner, :listener, :lock
|
28
|
+
include Internals::Helpers
|
26
29
|
|
27
|
-
#
|
30
|
+
# Initializes the Guard singleton:
|
28
31
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
+
# * Initialize the internal Guard state;
|
33
|
+
# * Create the interactor
|
34
|
+
# * Select and initialize the file change listener.
|
32
35
|
#
|
33
36
|
# @option options [Boolean] clear if auto clear the UI should be done
|
34
37
|
# @option options [Boolean] notify if system notifications should be shown
|
35
38
|
# @option options [Boolean] debug if debug output should be shown
|
36
39
|
# @option options [Array<String>] group the list of groups to start
|
37
|
-
# @option options [String] watchdir the
|
40
|
+
# @option options [Array<String>] watchdir the directories to watch
|
38
41
|
# @option options [String] guardfile the path to the Guardfile
|
39
|
-
# @deprecated @option options [Boolean] watch_all_modifications watches all file modifications if true
|
40
|
-
# @deprecated @option options [Boolean] no_vendor ignore vendored dependencies
|
41
42
|
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
@watchdir = (options[:watchdir] && File.expand_path(options[:watchdir])) || Dir.pwd
|
46
|
-
@runner = ::Guard::Runner.new
|
47
|
-
@allow_stop = Listen::Turnstile.new
|
43
|
+
# @return [Guard] the Guard singleton
|
44
|
+
def setup(cmdline_options = {})
|
45
|
+
init(cmdline_options)
|
48
46
|
|
49
|
-
::
|
50
|
-
deprecated_options_warning
|
47
|
+
@queue = Internals::Queue.new(Guard)
|
51
48
|
|
52
|
-
|
53
|
-
setup_guards
|
54
|
-
setup_listener
|
55
|
-
setup_signal_traps
|
56
|
-
|
57
|
-
debug_command_execution if @options[:debug]
|
58
|
-
|
59
|
-
::Guard::Dsl.evaluate_guardfile(options)
|
60
|
-
::Guard::UI.error 'No guards found in Guardfile, please add at least one.' if @guards.empty?
|
61
|
-
|
62
|
-
runner.deprecation_warning if @options[:show_deprecations]
|
63
|
-
|
64
|
-
setup_notifier
|
65
|
-
setup_interactor
|
66
|
-
|
67
|
-
self
|
68
|
-
end
|
49
|
+
_evaluate(state.session.evaluator_options)
|
69
50
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
def setup_groups
|
75
|
-
@groups = [Group.new(:default)]
|
76
|
-
end
|
51
|
+
# NOTE: this should be *after* evaluate so :directories can work
|
52
|
+
# TODO: move listener setup to session?
|
53
|
+
@listener = Listen.send(*state.session.listener_args, &_listener_callback)
|
77
54
|
|
78
|
-
|
79
|
-
|
80
|
-
# @see Guard.guards
|
81
|
-
#
|
82
|
-
def setup_guards
|
83
|
-
@guards = []
|
84
|
-
end
|
55
|
+
ignores = state.session.guardfile_ignore
|
56
|
+
@listener.ignore(ignores) unless ignores.empty?
|
85
57
|
|
86
|
-
|
87
|
-
|
88
|
-
# Currently two signals are caught:
|
89
|
-
# - `USR1` which pauses listening to changes.
|
90
|
-
# - `USR2` which resumes listening to changes.
|
91
|
-
#
|
92
|
-
def setup_signal_traps
|
93
|
-
unless defined?(JRUBY_VERSION)
|
94
|
-
if Signal.list.keys.include?('USR1')
|
95
|
-
Signal.trap('USR1') { ::Guard.pause unless @listener.paused? }
|
96
|
-
end
|
97
|
-
|
98
|
-
if Signal.list.keys.include?('USR2')
|
99
|
-
Signal.trap('USR2') { ::Guard.pause if @listener.paused? }
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
58
|
+
ignores = state.session.guardfile_ignore_bang
|
59
|
+
@listener.ignore!(ignores) unless ignores.empty?
|
103
60
|
|
104
|
-
|
105
|
-
#
|
106
|
-
def setup_listener
|
107
|
-
listener_callback = lambda do |modified, added, removed|
|
108
|
-
::Guard::Dsl.reevaluate_guardfile if ::Guard::Watcher.match_guardfile?(modified)
|
61
|
+
Notifier.connect(state.session.notify_options)
|
109
62
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
63
|
+
traps = Internals::Traps
|
64
|
+
traps.handle("USR1") { async_queue_add([:guard_pause, :paused]) }
|
65
|
+
traps.handle("USR2") { async_queue_add([:guard_pause, :unpaused]) }
|
114
66
|
|
115
|
-
|
116
|
-
|
117
|
-
listener_options[option.to_sym] = options[option] if options.key?(option)
|
118
|
-
end
|
67
|
+
@interactor = Interactor.new(state.session.interactor_name == :sleep)
|
68
|
+
traps.handle("INT") { @interactor.handle_interrupt }
|
119
69
|
|
120
|
-
|
70
|
+
self
|
121
71
|
end
|
122
72
|
|
123
|
-
|
124
|
-
|
125
|
-
def setup_notifier
|
126
|
-
options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? ::Guard::Notifier.turn_on : ::Guard::Notifier.turn_off
|
73
|
+
def init(cmdline_options)
|
74
|
+
@state = Internals::State.new(cmdline_options)
|
127
75
|
end
|
128
76
|
|
129
|
-
#
|
77
|
+
# Asynchronously trigger changes
|
130
78
|
#
|
131
|
-
|
132
|
-
unless options[:no_interactions]
|
133
|
-
@interactor = ::Guard::Interactor.fabricate
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Start Guard by evaluating the `Guardfile`, initializing declared Guard plugins
|
138
|
-
# and starting the available file change listener.
|
139
|
-
# Main method for Guard that is called from the CLI when Guard starts.
|
79
|
+
# Currently supported args:
|
140
80
|
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
# - Configure Notifiers
|
144
|
-
# - Initialize the declared Guard plugins
|
145
|
-
# - Start the available file change listener
|
81
|
+
# @example Old style hash:
|
82
|
+
# async_queue_add(modified: ['foo'], added: ['bar'], removed: [])
|
146
83
|
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
# @option options [Boolean] debug if debug output should be shown
|
150
|
-
# @option options [Array<String>] group the list of groups to start
|
151
|
-
# @option options [String] watchdir the director to watch
|
152
|
-
# @option options [String] guardfile the path to the Guardfile
|
84
|
+
# @example New style signals with args:
|
85
|
+
# async_queue_add([:guard_pause, :unpaused ])
|
153
86
|
#
|
154
|
-
def
|
155
|
-
|
156
|
-
::Guard::UI.info "Guard is now watching at '#{ @watchdir }'"
|
87
|
+
def async_queue_add(changes)
|
88
|
+
@queue << changes
|
157
89
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
listener.start(false)
|
163
|
-
|
164
|
-
@allow_stop.wait if @allow_stop
|
90
|
+
# Putting interactor in background puts guard into foreground
|
91
|
+
# so it can handle change notifications
|
92
|
+
Thread.new { interactor.background }
|
165
93
|
end
|
166
94
|
|
167
|
-
|
168
|
-
#
|
169
|
-
def stop
|
170
|
-
listener.stop
|
171
|
-
interactor.stop if interactor
|
172
|
-
runner.run(:stop)
|
173
|
-
::Guard::UI.info 'Bye bye...', :reset => true
|
95
|
+
private
|
174
96
|
|
175
|
-
|
97
|
+
# Check if any of the changes are actually watched for
|
98
|
+
# TODO: why iterate twice? reuse this info when running tasks
|
99
|
+
def _relevant_changes?(changes)
|
100
|
+
# TODO: no coverage!
|
101
|
+
files = changes.values.flatten(1)
|
102
|
+
scope = Guard.state.scope
|
103
|
+
watchers = scope.grouped_plugins.map do |_group, plugins|
|
104
|
+
plugins.map(&:watchers).flatten
|
105
|
+
end.flatten
|
106
|
+
watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
|
176
107
|
end
|
177
108
|
|
178
|
-
|
179
|
-
|
180
|
-
# @param [Hash] scopes hash with a Guard plugin or a group scope
|
181
|
-
#
|
182
|
-
def reload(scopes = {})
|
183
|
-
within_preserved_state do
|
184
|
-
::Guard::UI.clear(:force => true)
|
185
|
-
::Guard::UI.action_with_scopes('Reload', scopes)
|
186
|
-
::Guard::Dsl.reevaluate_guardfile if scopes.empty?
|
187
|
-
runner.run(:reload, scopes)
|
188
|
-
end
|
109
|
+
def _relative_pathnames(paths)
|
110
|
+
paths.map { |path| _relative_pathname(path) }
|
189
111
|
end
|
190
112
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
::Guard::UI.action_with_scopes('Run', scopes)
|
199
|
-
runner.run(:run_all, scopes)
|
200
|
-
end
|
201
|
-
end
|
113
|
+
def _listener_callback
|
114
|
+
lambda do |modified, added, removed|
|
115
|
+
relative_paths = {
|
116
|
+
modified: _relative_pathnames(modified),
|
117
|
+
added: _relative_pathnames(added),
|
118
|
+
removed: _relative_pathnames(removed)
|
119
|
+
}
|
202
120
|
|
203
|
-
|
204
|
-
#
|
205
|
-
def pause
|
206
|
-
if listener.paused?
|
207
|
-
::Guard::UI.info 'Un-paused files modification listening', :reset => true
|
208
|
-
listener.unpause
|
209
|
-
else
|
210
|
-
::Guard::UI.info 'Paused files modification listening', :reset => true
|
211
|
-
listener.pause
|
212
|
-
end
|
213
|
-
end
|
121
|
+
_guardfile_deprecated_check(relative_paths[:modified])
|
214
122
|
|
215
|
-
|
216
|
-
#
|
217
|
-
# @see Guard.groups
|
218
|
-
#
|
219
|
-
# @example Filter Guard plugins by String or Symbol
|
220
|
-
# Guard.guards('rspec')
|
221
|
-
# Guard.guards(:rspec)
|
222
|
-
#
|
223
|
-
# @example Filter Guard plugins by Regexp
|
224
|
-
# Guard.guards(/rsp.+/)
|
225
|
-
#
|
226
|
-
# @example Filter Guard plugins by Hash
|
227
|
-
# Guard.guards({ :name => 'rspec', :group => 'backend' })
|
228
|
-
#
|
229
|
-
# @param [String, Symbol, Regexp, Hash] filter the filter to apply to the Guard plugins
|
230
|
-
# @return [Array<Guard>] the filtered Guard plugins
|
231
|
-
#
|
232
|
-
def guards(filter = nil)
|
233
|
-
@guards ||= []
|
234
|
-
|
235
|
-
case filter
|
236
|
-
when String, Symbol
|
237
|
-
@guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
|
238
|
-
when Regexp
|
239
|
-
@guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter }
|
240
|
-
when Hash
|
241
|
-
filter.inject(@guards) do |matches, (k, v)|
|
242
|
-
if k.to_sym == :name
|
243
|
-
matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') }
|
244
|
-
else
|
245
|
-
matches.find_all { |guard| guard.send(k).to_sym == v.to_sym }
|
246
|
-
end
|
247
|
-
end
|
248
|
-
else
|
249
|
-
@guards
|
123
|
+
async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
|
250
124
|
end
|
251
125
|
end
|
252
126
|
|
253
|
-
#
|
254
|
-
|
255
|
-
|
256
|
-
#
|
257
|
-
# @example Filter groups by String or Symbol
|
258
|
-
# Guard.groups('backend')
|
259
|
-
# Guard.groups(:backend)
|
260
|
-
#
|
261
|
-
# @example Filter groups by Regexp
|
262
|
-
# Guard.groups(/(back|front)end/)
|
263
|
-
#
|
264
|
-
# @param [String, Symbol, Regexp] filter the filter to apply to the Groups
|
265
|
-
# @return [Array<Group>] the filtered groups
|
266
|
-
#
|
267
|
-
def groups(filter = nil)
|
268
|
-
case filter
|
269
|
-
when String, Symbol
|
270
|
-
@groups.find { |group| group.name == filter.to_sym }
|
271
|
-
when Regexp
|
272
|
-
@groups.find_all { |group| group.name.to_s =~ filter }
|
273
|
-
else
|
274
|
-
@groups
|
275
|
-
end
|
127
|
+
# TODO: obsoleted? (move to Dsl?)
|
128
|
+
def _pluginless_guardfile?
|
129
|
+
state.session.plugins.all.empty?
|
276
130
|
end
|
277
131
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
# @param [Array<Watcher>] watchers the list of declared watchers
|
282
|
-
# @param [Array<Hash>] callbacks the list of callbacks
|
283
|
-
# @param [Hash] options the plugin options (see the given Guard documentation)
|
284
|
-
# @return [Guard::Guard] the added Guard plugin
|
285
|
-
#
|
286
|
-
def add_guard(name, watchers = [], callbacks = [], options = {})
|
287
|
-
if name.to_sym == :ego
|
288
|
-
::Guard::UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
|
289
|
-
else
|
290
|
-
guard_class = get_guard_class(name)
|
291
|
-
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
|
292
|
-
guard = guard_class.new(watchers, options)
|
293
|
-
@guards << guard
|
294
|
-
guard
|
295
|
-
end
|
296
|
-
end
|
132
|
+
def _evaluate(options)
|
133
|
+
evaluator = Guardfile::Evaluator.new(options)
|
134
|
+
evaluator.evaluate
|
297
135
|
|
298
|
-
|
299
|
-
#
|
300
|
-
# @param [String] name the group name
|
301
|
-
# @option options [Boolean] halt_on_fail if a task execution
|
302
|
-
# should be halted for all Guard plugins in this group if one Guard throws `:task_has_failed`
|
303
|
-
# @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present)
|
304
|
-
#
|
305
|
-
def add_group(name, options = {})
|
306
|
-
group = groups(name)
|
307
|
-
if group.nil?
|
308
|
-
group = ::Guard::Group.new(name, options)
|
309
|
-
@groups << group
|
310
|
-
end
|
311
|
-
group
|
312
|
-
end
|
136
|
+
UI.reset_and_clear
|
313
137
|
|
314
|
-
|
315
|
-
|
316
|
-
# to avoid state inconsistency.
|
317
|
-
#
|
318
|
-
# @yield the block to run
|
319
|
-
#
|
320
|
-
def within_preserved_state
|
321
|
-
lock.synchronize do
|
322
|
-
begin
|
323
|
-
interactor.stop if interactor
|
324
|
-
@result = yield
|
325
|
-
rescue Interrupt
|
326
|
-
end
|
327
|
-
|
328
|
-
interactor.start if interactor
|
329
|
-
end
|
330
|
-
@result
|
331
|
-
end
|
138
|
+
msg = "No plugins found in Guardfile, please add at least one."
|
139
|
+
UI.error msg if _pluginless_guardfile?
|
332
140
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
# * `dashed-guard-name` will become `Guard::DashedGuardName`
|
338
|
-
# * `underscore_guard_name` will become `Guard::UnderscoreGuardName`
|
339
|
-
#
|
340
|
-
# When no class is found with the strict case sensitive rules, another
|
341
|
-
# try is made to locate the class without matching case:
|
342
|
-
#
|
343
|
-
# * `rspec` will find a class `Guard::RSpec`
|
344
|
-
#
|
345
|
-
# @param [String] name the name of the Guard
|
346
|
-
# @param [Boolean] fail_gracefully whether error messages should not be printed
|
347
|
-
# @return [Class, nil] the loaded class
|
348
|
-
#
|
349
|
-
def get_guard_class(name, fail_gracefully=false)
|
350
|
-
name = name.to_s
|
351
|
-
try_require = false
|
352
|
-
const_name = name.gsub(/\/(.?)/) { "::#{ $1.upcase }" }.gsub(/(?:^|[_-])(.)/) { $1.upcase }
|
353
|
-
begin
|
354
|
-
require "guard/#{ name.downcase }" if try_require
|
355
|
-
self.const_get(self.constants.find { |c| c.to_s == const_name } || self.constants.find { |c| c.to_s.downcase == const_name.downcase })
|
356
|
-
rescue TypeError
|
357
|
-
unless try_require
|
358
|
-
try_require = true
|
359
|
-
retry
|
360
|
-
else
|
361
|
-
::Guard::UI.error "Could not find class Guard::#{ const_name.capitalize }"
|
362
|
-
end
|
363
|
-
rescue LoadError => loadError
|
364
|
-
unless fail_gracefully
|
365
|
-
::Guard::UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
|
366
|
-
::Guard::UI.error loadError.to_s
|
367
|
-
end
|
141
|
+
if evaluator.inline?
|
142
|
+
UI.info("Using inline Guardfile.")
|
143
|
+
elsif evaluator.custom?
|
144
|
+
UI.info("Using Guardfile at #{ evaluator.path }.")
|
368
145
|
end
|
146
|
+
rescue Guardfile::Evaluator::NoPluginsError => e
|
147
|
+
UI.error(e.message)
|
369
148
|
end
|
370
149
|
|
371
|
-
#
|
372
|
-
#
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
if
|
378
|
-
Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
|
379
|
-
else
|
380
|
-
Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
|
381
|
-
end
|
382
|
-
rescue
|
383
|
-
::Guard::UI.error "Could not find 'guard-#{ name }' gem path."
|
384
|
-
end
|
150
|
+
# TODO: remove at some point
|
151
|
+
# TODO: not tested because collides with ongoing refactoring
|
152
|
+
def _guardfile_deprecated_check(modified)
|
153
|
+
modified.map!(&:to_s)
|
154
|
+
regexp = %r{^(?:.+/)?Guardfile$}
|
155
|
+
guardfiles = modified.select { |path| regexp.match(path) }
|
156
|
+
return if guardfiles.empty?
|
385
157
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
#
|
390
|
-
def guard_gem_names
|
391
|
-
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
392
|
-
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
|
393
|
-
else
|
394
|
-
Gem.source_index.find_name(/^guard-/)
|
395
|
-
end.map { |x| x.name.sub(/^guard-/, '') }
|
396
|
-
end
|
397
|
-
|
398
|
-
# Adds a command logger in debug mode. This wraps common command
|
399
|
-
# execution functions and logs the executed command before execution.
|
400
|
-
#
|
401
|
-
def debug_command_execution
|
402
|
-
Kernel.send(:alias_method, :original_system, :system)
|
403
|
-
Kernel.send(:define_method, :system) do |command, *args|
|
404
|
-
::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
|
405
|
-
original_system command, *args
|
158
|
+
guardfile = Pathname("Guardfile").realpath
|
159
|
+
real_guardfiles = guardfiles.detect do |path|
|
160
|
+
/^Guardfile$/.match(path) || Pathname(path).expand_path == guardfile
|
406
161
|
end
|
407
162
|
|
408
|
-
|
409
|
-
Kernel.send(:define_method, :'`') do |command|
|
410
|
-
::Guard::UI.debug "Command execution: #{ command }"
|
411
|
-
original_backtick command
|
412
|
-
end
|
413
|
-
end
|
163
|
+
return unless real_guardfiles
|
414
164
|
|
415
|
-
|
416
|
-
|
417
|
-
Starting with Guard v1.1 the 'watch_all_modifications' option is removed and is now always on.
|
418
|
-
EOS
|
419
|
-
|
420
|
-
# Deprecation message for the `no_vendor` start option
|
421
|
-
NO_VENDOR_DEPRECATION = <<-EOS.gsub(/^\s*/, '')
|
422
|
-
Starting with Guard v1.1 the 'no_vendor' option is removed because the monitoring
|
423
|
-
gems are now part of a new gem called Listen. (https://github.com/guard/listen)
|
424
|
-
|
425
|
-
You can specify a custom version of any monitoring gem directly in your Gemfile
|
426
|
-
if you want to overwrite Listen's default monitoring gems.
|
427
|
-
EOS
|
428
|
-
|
429
|
-
# Displays a warning for each deprecated options used.
|
430
|
-
#
|
431
|
-
def deprecated_options_warning
|
432
|
-
::Guard::UI.deprecation(WATCH_ALL_MODIFICATIONS_DEPRECATION) if options[:watch_all_modifications]
|
433
|
-
::Guard::UI.deprecation(NO_VENDOR_DEPRECATION) if options[:no_vendor]
|
165
|
+
UI.warning "Guardfile changed -- _guard-core will exit.\n"
|
166
|
+
exit 2 # nonzero to break any while loop
|
434
167
|
end
|
435
|
-
|
436
168
|
end
|
437
169
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# TODO: extract to gem?
|
2
|
+
|
3
|
+
class Releaser
|
4
|
+
def initialize(options = {})
|
5
|
+
@project_name = options.delete(:project_name) do
|
6
|
+
fail "project_name is needed!"
|
7
|
+
end
|
8
|
+
|
9
|
+
@gem_name = options.delete(:gem_name) do
|
10
|
+
fail "gem_name is needed!"
|
11
|
+
end
|
12
|
+
|
13
|
+
@github_repo = options.delete(:github_repo) do
|
14
|
+
fail "github_repo is needed!"
|
15
|
+
end
|
16
|
+
|
17
|
+
@version = options.delete(:version) do
|
18
|
+
fail "version is needed!"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def full
|
23
|
+
rubygems
|
24
|
+
github
|
25
|
+
end
|
26
|
+
|
27
|
+
def rubygems
|
28
|
+
input = nil
|
29
|
+
loop do
|
30
|
+
STDOUT.puts "Release #{@project_name} #{@version} to RubyGems? (y/n)"
|
31
|
+
input = STDIN.gets.chomp.downcase
|
32
|
+
break if %w(y n).include?(input)
|
33
|
+
end
|
34
|
+
|
35
|
+
exit if input == "n"
|
36
|
+
|
37
|
+
Rake::Task["release"].invoke
|
38
|
+
end
|
39
|
+
|
40
|
+
def github
|
41
|
+
tag_name = "v#{@version}"
|
42
|
+
|
43
|
+
require "gems"
|
44
|
+
|
45
|
+
_verify_released
|
46
|
+
_verify_tag_pushed
|
47
|
+
|
48
|
+
require "octokit"
|
49
|
+
gh_client = Octokit::Client.new(netrc: true)
|
50
|
+
|
51
|
+
gh_release = _detect_gh_release(gh_client, tag_name, true)
|
52
|
+
return unless gh_release
|
53
|
+
|
54
|
+
STDOUT.puts "Draft release for #{tag_name}:\n"
|
55
|
+
STDOUT.puts gh_release.body
|
56
|
+
STDOUT.puts "\n-------------------------\n\n"
|
57
|
+
|
58
|
+
_confirm_publish
|
59
|
+
|
60
|
+
return unless _update_release(gh_client, gh_release, tag_name)
|
61
|
+
|
62
|
+
gh_release = _detect_gh_release(gh_client, tag_name, false)
|
63
|
+
|
64
|
+
_success_summary(gh_release, tag_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def _verify_released
|
70
|
+
if @version != Gems.info(@gem_name)["version"]
|
71
|
+
STDOUT.puts "#{@project_name} #{@version} is not yet released."
|
72
|
+
STDOUT.puts "Please release it first with: rake release:gem"
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def _verify_tag_pushed
|
78
|
+
tags = `git ls-remote --tags origin`.split("\n")
|
79
|
+
return if tags.detect { |tag| tag =~ /v#{@version}$/ }
|
80
|
+
|
81
|
+
STDOUT.puts "The tag v#{@version} has not yet been pushed."
|
82
|
+
STDOUT.puts "Please push it first with: rake release:gem"
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
|
86
|
+
def _success_summary(gh_release, tag_name)
|
87
|
+
href = gh_release.rels[:html].href
|
88
|
+
STDOUT.puts "GitHub release #{tag_name} has been published!"
|
89
|
+
STDOUT.puts "\nPlease enjoy and spread the word!"
|
90
|
+
STDOUT.puts "Lack of inspiration? Here's a tweet you could improve:\n\n"
|
91
|
+
STDOUT.puts "Just released #{@project_name} #{@version}! #{href}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def _detect_gh_release(gh_client, tag_name, draft)
|
95
|
+
gh_releases = gh_client.releases(@github_repo)
|
96
|
+
gh_releases.detect { |r| r.tag_name == tag_name && r.draft == draft }
|
97
|
+
end
|
98
|
+
|
99
|
+
def _confirm_publish
|
100
|
+
input = nil
|
101
|
+
loop do
|
102
|
+
STDOUT.puts "Would you like to publish this GitHub release now? (y/n)"
|
103
|
+
input = STDIN.gets.chomp.downcase
|
104
|
+
break if %w(y n).include?(input)
|
105
|
+
end
|
106
|
+
|
107
|
+
exit if input == "n"
|
108
|
+
end
|
109
|
+
|
110
|
+
def _update_release(gh_client, gh_release, tag_name)
|
111
|
+
result = gh_client.update_release(gh_release.rels[:self].href, draft: false)
|
112
|
+
return true if result
|
113
|
+
STDOUT.puts "GitHub release #{tag_name} couldn't be published!"
|
114
|
+
false
|
115
|
+
end
|
116
|
+
end
|