guard 2.9.0 → 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -68,10 +68,12 @@ module Guard
68
68
  scopes = { plugins: [], groups: [] }
69
69
  unknown = []
70
70
 
71
+ session = Guard.state.session
72
+
71
73
  entries.each do |entry|
72
- if plugin = ::Guard.plugin(entry)
74
+ if plugin = session.plugins.all(entry).first
73
75
  scopes[:plugins] << plugin
74
- elsif group = ::Guard.group(entry)
76
+ elsif group = session.groups.all(entry).first
75
77
  scopes[:groups] << group
76
78
  else
77
79
  unknown << entry
@@ -0,0 +1,40 @@
1
+ require "guard/group"
2
+
3
+ module Guard
4
+ # @private api
5
+ module Internals
6
+ class Groups
7
+ DEFAULT_GROUPS = [:common, :default]
8
+
9
+ def initialize
10
+ @groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
11
+ end
12
+
13
+ def all(filter = nil)
14
+ return @groups if filter.nil?
15
+ matcher = matcher_for(filter)
16
+ @groups.select { |group| matcher.call(group) }
17
+ end
18
+
19
+ def add(name, options = {})
20
+ all(name).first || Group.new(name, options).tap do |group|
21
+ fail if name == :specs && options.empty?
22
+ @groups << group
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def matcher_for(filter)
29
+ case filter
30
+ when String, Symbol
31
+ lambda { |group| group.name == filter.to_sym }
32
+ when Regexp
33
+ lambda { |group| group.name.to_s =~ filter }
34
+ else
35
+ fail "Invalid filter: #{filter.inspect}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,51 @@
1
+ require "guard/plugin"
2
+
3
+ module Guard
4
+ # @private api
5
+ module Internals
6
+ class Plugins
7
+ def initialize
8
+ @plugins = []
9
+ end
10
+
11
+ def all(filter = nil)
12
+ return @plugins if filter.nil?
13
+ matcher = matcher_for(filter)
14
+ @plugins.select { |plugin| matcher.call(plugin) }
15
+ end
16
+
17
+ def remove(plugin)
18
+ @plugins.delete(plugin)
19
+ end
20
+
21
+ # TODO: should it allow duplicates? (probably yes because of different
22
+ # configs or groups)
23
+ def add(name, options)
24
+ @plugins << PluginUtil.new(name).initialize_plugin(options)
25
+ end
26
+
27
+ private
28
+
29
+ def matcher_for(filter)
30
+ case filter
31
+ when String, Symbol
32
+ shortname = filter.to_s.downcase.gsub("-", "")
33
+ lambda { |plugin| plugin.name == shortname }
34
+ when Regexp
35
+ lambda { |plugin| plugin.name =~ filter }
36
+ when Hash
37
+ lambda do |plugin|
38
+ filter.all? do |k, v|
39
+ case k
40
+ when :name
41
+ plugin.name == v.to_s.downcase.gsub("-", "")
42
+ when :group
43
+ plugin.group.name == v.to_sym
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ module Guard
2
+ module Internals
3
+ class Queue
4
+ def initialize(commander)
5
+ @commander = commander
6
+ @queue = ::Queue.new
7
+ end
8
+
9
+ # Process the change queue, running tasks within the main Guard thread
10
+ def process
11
+ actions, changes = [], { modified: [], added: [], removed: [] }
12
+
13
+ while pending?
14
+ if (item = @queue.pop).first.is_a?(Symbol)
15
+ actions << item
16
+ else
17
+ item.each { |key, value| changes[key] += value }
18
+ end
19
+ end
20
+
21
+ _run_actions(actions)
22
+ return if changes.values.all?(&:empty?)
23
+ Runner.new.run_on_changes(*changes.values)
24
+ end
25
+
26
+ def pending?
27
+ ! @queue.empty?
28
+ end
29
+
30
+ def <<(changes)
31
+ @queue << changes
32
+ end
33
+
34
+ private
35
+
36
+ def _run_actions(actions)
37
+ actions.each do |action_args|
38
+ args = action_args.dup
39
+ namespaced_action = args.shift
40
+ action = namespaced_action.to_s.sub(/^guard_/, "")
41
+ if @commander.respond_to?(action)
42
+ @commander.send(action, *args)
43
+ else
44
+ fail "Unknown action: #{action.inspect}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,110 @@
1
+ module Guard
2
+ # @private api
3
+ module Internals
4
+ class Scope
5
+ def initialize
6
+ @interactor_plugin_scope = []
7
+ @interactor_group_scope = []
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ plugins: _build_scope(:plugin),
13
+ groups: _build_scope(:group)
14
+ }.dup.freeze
15
+ end
16
+
17
+ # TODO: refactor
18
+ def grouped_plugins(scope = { plugins: [], groups: [] })
19
+ items = nil
20
+ plugins = _find_non_empty_scope(:plugins, scope)
21
+ if plugins
22
+ items = Array(plugins).map { |plugin| _instantiate(:plugin, plugin) }
23
+ end
24
+
25
+ unless items
26
+ # TODO: no coverage here!!
27
+ found = _find_non_empty_scope(:groups, scope)
28
+ found ||= Guard.state.session.groups.all
29
+ groups = Array(found).map { |group| _instantiate(:group, group) }
30
+ if groups.any? { |g| g.name == :common }
31
+ items = groups
32
+ else
33
+ items = ([_instantiate(:group, :common)] + Array(found)).compact
34
+ end
35
+ end
36
+
37
+ items.map do |plugin_or_group|
38
+ group = nil
39
+ plugins = [plugin_or_group]
40
+ if plugin_or_group.is_a?(Group)
41
+ # TODO: no coverage here!
42
+ group = plugin_or_group
43
+ plugins = Guard.state.session.plugins.all(group: group.name)
44
+ end
45
+ [group, plugins]
46
+ end
47
+ end
48
+
49
+ def from_interactor(scope)
50
+ @interactor_plugin_scope = Array(scope[:plugins])
51
+ @interactor_group_scope = Array(scope[:groups])
52
+ end
53
+
54
+ def titles(scope = nil)
55
+ hash = scope || to_hash
56
+ return hash[:plugins].map(&:title) unless hash[:plugins].empty?
57
+ return hash[:groups].map(&:title) unless hash[:groups].empty?
58
+ ["all"]
59
+ end
60
+
61
+ private
62
+
63
+ # TODO: move to session
64
+ def _scope_names(new_scope, name)
65
+ items = Array(new_scope[:"#{name}s"] || new_scope[name]) if items.empty?
66
+
67
+ # Convert objects to names
68
+ items.map { |p| p.respond_to?(:name) ? p.name : p }
69
+ end
70
+
71
+ # TODO: let the Plugins and Groups classes handle this?
72
+ # TODO: why even instantiate?? just to check if it exists?
73
+ def _build_scope(type)
74
+ # TODO: get cmdline passed to initialize above?
75
+ cmdline = Array(Guard.state.session.send("cmdline_#{type}s"))
76
+ guardfile = Guard.state.session.send(:"guardfile_#{type}_scope")
77
+ interactor = instance_variable_get(:"@interactor_#{type}_scope")
78
+
79
+ # TODO: session should decide whether to use cmdline or guardfile -
80
+ # since it has access to both variables
81
+ items = [interactor, cmdline, guardfile].detect do |source|
82
+ !source.empty?
83
+ end
84
+
85
+ # TODO: should already be instantiated
86
+ Array(items).map do |name|
87
+ (type == :group ? _groups : _plugins).all(name).first
88
+ end
89
+ end
90
+
91
+ def _instantiate(meth, obj)
92
+ # TODO: no coverage
93
+ return obj unless obj.is_a?(Symbol) || obj.is_a?(String)
94
+ Guard.state.session.send("#{meth}s".to_sym).all(obj).first
95
+ end
96
+
97
+ def _find_non_empty_scope(type, local_scope)
98
+ [Array(local_scope[type]), to_hash[type]].map(&:compact).detect(&:any?)
99
+ end
100
+
101
+ def _groups
102
+ Guard.state.session.groups
103
+ end
104
+
105
+ def _plugins
106
+ Guard.state.session.plugins
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,135 @@
1
+ require "guard/internals/plugins"
2
+ require "guard/internals/groups"
3
+
4
+ require "guard/options"
5
+
6
+ module Guard
7
+ # @private api
8
+ module Internals
9
+ # TODO: split into a commandline class and session (plugins, groups)
10
+ # TODO: swap session and metadata
11
+ # This class contains variables set during
12
+ # evaluation of the guardfile (and are reset
13
+ # before reevaluation)
14
+ class Session
15
+ attr_reader :options
16
+ attr_reader :plugins
17
+ attr_reader :groups
18
+
19
+ DEFAULT_OPTIONS = {
20
+ clear: false,
21
+ debug: false,
22
+ no_bundler_warning: false,
23
+
24
+ # User defined scopes
25
+ group: [],
26
+ plugin: [],
27
+
28
+ # Notifier
29
+ notify: true,
30
+
31
+ # Interactor
32
+ no_interactions: false,
33
+
34
+ # Guardfile options:
35
+ # guardfile_contents
36
+ guardfile: nil,
37
+
38
+ # Listener options
39
+ # TODO: rename to watchdirs?
40
+ watchdir: Dir.pwd,
41
+ latency: nil,
42
+ force_polling: false,
43
+ wait_for_delay: nil,
44
+ listen_on: nil
45
+ }
46
+
47
+ def cmdline_groups
48
+ @cmdline_groups.dup.freeze
49
+ end
50
+
51
+ def cmdline_plugins
52
+ @cmdline_plugins.dup.freeze
53
+ end
54
+
55
+ def initialize(new_options)
56
+ @options = Options.new(new_options, DEFAULT_OPTIONS)
57
+
58
+ @plugins = Internals::Plugins.new
59
+ @groups = Internals::Groups.new
60
+
61
+ @cmdline_groups = @options[:group]
62
+ @cmdline_plugins = @options[:plugin]
63
+
64
+ @clear = @options[:clear]
65
+ @debug = @options[:debug]
66
+ @watchdirs = Array(@options[:watchdir])
67
+ @notify = @options[:notify]
68
+ @interactor_name = @options[:no_interactions] ? :sleep : :pry_wrapper
69
+
70
+ @guardfile_plugin_scope = []
71
+ @guardfile_group_scope = []
72
+ end
73
+
74
+ def guardfile_scope(scope)
75
+ opts = scope.dup
76
+ @guardfile_plugin_scope = Array(opts.delete(:plugins))
77
+ @guardfile_group_scope = Array(opts.delete(:groups))
78
+ fail "Unknown options: #{opts.inspect}" unless opts.empty?
79
+ end
80
+
81
+ attr_reader :guardfile_group_scope
82
+ attr_reader :guardfile_plugin_scope
83
+
84
+ # TODO: not tested
85
+ def clear?
86
+ @clear
87
+ end
88
+
89
+ def debug?
90
+ @debug
91
+ end
92
+
93
+ def watchdirs
94
+ @watchdirs_from_guardfile ||= nil
95
+ @watchdirs_from_guardfile || @watchdirs
96
+ end
97
+
98
+ # set by Dsl with :directories() command
99
+ def watchdirs=(dirs)
100
+ dirs = [Dir.pwd] if dirs.empty?
101
+ @watchdirs_from_guardfile = dirs.map { |dir| File.expand_path dir }
102
+ end
103
+
104
+ def listener_args
105
+ if options[:listen_on]
106
+ [:on, options[:listen_on]]
107
+ else
108
+ listener_options = {}
109
+ [:latency, :force_polling, :wait_for_delay].each do |option|
110
+ listener_options[option] = options[option] if options[option]
111
+ end
112
+ expanded_watchdirs = watchdirs.map { |dir| File.expand_path dir }
113
+ [:to, *expanded_watchdirs, listener_options]
114
+ end
115
+ end
116
+
117
+ def evaluator_options
118
+ opts = { guardfile: options[:guardfile] }
119
+ # TODO: deprecate :guardfile_contents
120
+ if options[:guardfile_contents]
121
+ opts[:contents] = options[:guardfile_contents]
122
+ end
123
+ opts
124
+ end
125
+
126
+ def notify_options
127
+ { notify: @options[:notify] }
128
+ end
129
+
130
+ def interactor_name
131
+ @interactor_name
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,33 @@
1
+ require "guard/group"
2
+
3
+ require "guard/plugin_util"
4
+ require "guard/internals/session"
5
+ require "guard/internals/scope"
6
+
7
+ module Guard
8
+ module Internals
9
+ class State
10
+ # Minimal setup for non-interactive commands (list, init, show, etc.)
11
+ def initialize(cmdline_opts)
12
+ # NOTE: this is reset during reevaluation
13
+ @session = Session.new(cmdline_opts)
14
+
15
+ # NOTE: this should persist across reevaluate() calls
16
+ @scope = Scope.new
17
+
18
+ # NOTE: must be set before anything calls Guard::UI.debug
19
+ Debugging.start if session.debug?
20
+ end
21
+
22
+ attr_reader :scope
23
+ attr_reader :session
24
+
25
+ # @private api
26
+ # used to clear instance variables during reevaluation
27
+ def reset_session
28
+ options = @session.options.dup
29
+ @session = Session.new(options)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -14,22 +14,22 @@ module Guard
14
14
  class TerminalSettings
15
15
  def initialize
16
16
  @settings = nil
17
- @works = ::Guard::Sheller.run("hash", "stty") || false
17
+ @works = Sheller.run("hash", "stty") || false
18
18
  end
19
19
 
20
20
  def restore
21
21
  return unless configurable? && @settings
22
- ::Guard::Sheller.run("stty #{ @setting } 2>#{IO::NULL}")
22
+ Sheller.run("stty #{ @setting } 2>#{IO::NULL}")
23
23
  end
24
24
 
25
25
  def save
26
26
  return unless configurable?
27
- @settings = ::Guard::Sheller.stdout("stty -g 2>#{IO::NULL}").chomp
27
+ @settings = Sheller.stdout("stty -g 2>#{IO::NULL}").chomp
28
28
  end
29
29
 
30
30
  def echo
31
31
  return unless configurable?
32
- ::Guard::Sheller.run("stty echo 2>#{IO::NULL}")
32
+ Sheller.run("stty echo 2>#{IO::NULL}")
33
33
  end
34
34
 
35
35
  def configurable?
@@ -69,17 +69,15 @@ module Guard
69
69
  end
70
70
 
71
71
  def foreground
72
- ::Guard::UI.debug "Start interactor"
72
+ UI.debug "Start interactor"
73
73
  @terminal_settings.save
74
74
 
75
- _start_pry
76
- @thread.join
77
- thread = @thread
75
+ _switch_to_pry
78
76
  # TODO: rename :stopped to continue
79
- thread.nil? ? :stopped : :exit
77
+ _killed? ? :stopped : :exit
80
78
  ensure
81
- ::Guard::UI.reset_line
82
- ::Guard::UI.debug "Interactor was stopped or killed"
79
+ UI.reset_line
80
+ UI.debug "Interactor was stopped or killed"
83
81
  @terminal_settings.restore
84
82
  end
85
83
 
@@ -97,13 +95,24 @@ module Guard
97
95
 
98
96
  attr_reader :thread
99
97
 
100
- def _start_pry
98
+ def _switch_to_pry
99
+ th = nil
101
100
  @mutex.synchronize do
102
101
  unless @thread
103
102
  @thread = Thread.new { Pry.start }
104
103
  @thread.join(0.5) # give pry a chance to start
104
+ th = @thread
105
105
  end
106
106
  end
107
+ # check for nill, because it might've been killed between the mutex and
108
+ # now
109
+ th.join unless th.nil?
110
+ end
111
+
112
+ def _killed?
113
+ th = nil
114
+ @mutex.synchronize { th = @thread }
115
+ th.nil?
107
116
  end
108
117
 
109
118
  def _kill_pry
@@ -123,13 +132,13 @@ module Guard
123
132
 
124
133
  _add_hooks(options)
125
134
 
126
- ::Guard::Commands::All.import
127
- ::Guard::Commands::Change.import
128
- ::Guard::Commands::Notification.import
129
- ::Guard::Commands::Pause.import
130
- ::Guard::Commands::Reload.import
131
- ::Guard::Commands::Show.import
132
- ::Guard::Commands::Scope.import
135
+ Commands::All.import
136
+ Commands::Change.import
137
+ Commands::Notification.import
138
+ Commands::Pause.import
139
+ Commands::Reload.import
140
+ Commands::Show.import
141
+ Commands::Scope.import
133
142
 
134
143
  _setup_commands
135
144
  _configure_prompt
@@ -215,7 +224,7 @@ module Guard
215
224
  # `rspec` is created that runs `all rspec`.
216
225
  #
217
226
  def _create_guard_commands
218
- ::Guard.plugins.each do |guard_plugin|
227
+ Guard.state.session.plugins.all.each do |guard_plugin|
219
228
  cmd = "Run all #{ guard_plugin.title }"
220
229
  Pry.commands.create_command guard_plugin.name, cmd do
221
230
  group "Guard"
@@ -233,7 +242,7 @@ module Guard
233
242
  # `frontend` is created that runs `all frontend`.
234
243
  #
235
244
  def _create_group_commands
236
- ::Guard.groups.each do |group|
245
+ Guard.state.session.groups.all.each do |group|
237
246
  next if group.name == :default
238
247
 
239
248
  cmd = "Run all #{ group.title }"
@@ -258,16 +267,8 @@ module Guard
258
267
  # prompt.
259
268
  #
260
269
  def _scope_for_prompt
261
- scope_name = [:plugins, :groups].detect do |name|
262
- ! ::Guard.scope[name].empty?
263
- end
264
- scope_name ? "#{_join_scope_for_prompt(scope_name)} " : ""
265
- end
266
-
267
- # Joins the scope corresponding to the given scope name with commas.
268
- #
269
- def _join_scope_for_prompt(scope_name)
270
- ::Guard.scope[scope_name].map(&:title).join(",")
270
+ titles = Guard.state.scope.titles.join(",")
271
+ titles == "all" ? "" : titles + " "
271
272
  end
272
273
 
273
274
  # Returns a proc that will return itself a string ending with the given
@@ -276,7 +277,7 @@ module Guard
276
277
  def _prompt(ending_char)
277
278
  proc do |target_self, nest_level, pry|
278
279
  history = pry.input_array.size
279
- process = ::Guard.listener.paused? ? "pause" : "guard"
280
+ process = Guard.listener.paused? ? "pause" : "guard"
280
281
  level = ":#{ nest_level }" unless nest_level.zero?
281
282
 
282
283
  "[#{ history }] #{ _scope_for_prompt }#{ process }"\