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.
@@ -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 }"\