guard 1.4.0 → 2.18.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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -677
  3. data/LICENSE +4 -2
  4. data/README.md +91 -753
  5. data/bin/_guard-core +11 -0
  6. data/bin/guard +108 -3
  7. data/lib/guard/aruba_adapter.rb +59 -0
  8. data/lib/guard/cli/environments/bundler.rb +22 -0
  9. data/lib/guard/cli/environments/evaluate_only.rb +35 -0
  10. data/lib/guard/cli/environments/valid.rb +69 -0
  11. data/lib/guard/cli.rb +129 -128
  12. data/lib/guard/commander.rb +104 -0
  13. data/lib/guard/commands/all.rb +37 -0
  14. data/lib/guard/commands/change.rb +31 -0
  15. data/lib/guard/commands/notification.rb +26 -0
  16. data/lib/guard/commands/pause.rb +29 -0
  17. data/lib/guard/commands/reload.rb +36 -0
  18. data/lib/guard/commands/scope.rb +38 -0
  19. data/lib/guard/commands/show.rb +24 -0
  20. data/lib/guard/config.rb +18 -0
  21. data/lib/guard/deprecated/dsl.rb +45 -0
  22. data/lib/guard/deprecated/evaluator.rb +39 -0
  23. data/lib/guard/deprecated/guard.rb +328 -0
  24. data/lib/guard/deprecated/guardfile.rb +84 -0
  25. data/lib/guard/deprecated/watcher.rb +27 -0
  26. data/lib/guard/dsl.rb +332 -363
  27. data/lib/guard/dsl_describer.rb +132 -122
  28. data/lib/guard/dsl_reader.rb +51 -0
  29. data/lib/guard/group.rb +34 -14
  30. data/lib/guard/guardfile/evaluator.rb +232 -0
  31. data/lib/guard/guardfile/generator.rb +128 -0
  32. data/lib/guard/guardfile.rb +24 -60
  33. data/lib/guard/interactor.rb +31 -255
  34. data/lib/guard/internals/debugging.rb +68 -0
  35. data/lib/guard/internals/groups.rb +40 -0
  36. data/lib/guard/internals/helpers.rb +13 -0
  37. data/lib/guard/internals/plugins.rb +53 -0
  38. data/lib/guard/internals/queue.rb +51 -0
  39. data/lib/guard/internals/scope.rb +121 -0
  40. data/lib/guard/internals/session.rb +180 -0
  41. data/lib/guard/internals/state.rb +25 -0
  42. data/lib/guard/internals/tracing.rb +33 -0
  43. data/lib/guard/internals/traps.rb +10 -0
  44. data/lib/guard/jobs/base.rb +21 -0
  45. data/lib/guard/jobs/pry_wrapper.rb +336 -0
  46. data/lib/guard/jobs/sleep.rb +26 -0
  47. data/lib/guard/notifier.rb +46 -212
  48. data/lib/guard/options.rb +22 -0
  49. data/lib/guard/plugin.rb +303 -0
  50. data/lib/guard/plugin_util.rb +191 -0
  51. data/lib/guard/rake_task.rb +42 -0
  52. data/lib/guard/runner.rb +80 -140
  53. data/lib/guard/templates/Guardfile +14 -0
  54. data/lib/guard/terminal.rb +13 -0
  55. data/lib/guard/ui/colors.rb +56 -0
  56. data/lib/guard/ui/config.rb +70 -0
  57. data/lib/guard/ui/logger.rb +30 -0
  58. data/lib/guard/ui.rb +163 -128
  59. data/lib/guard/version.rb +1 -2
  60. data/lib/guard/watcher/pattern/deprecated_regexp.rb +45 -0
  61. data/lib/guard/watcher/pattern/match_result.rb +18 -0
  62. data/lib/guard/watcher/pattern/matcher.rb +33 -0
  63. data/lib/guard/watcher/pattern/pathname_path.rb +15 -0
  64. data/lib/guard/watcher/pattern/simple_path.rb +23 -0
  65. data/lib/guard/watcher/pattern.rb +24 -0
  66. data/lib/guard/watcher.rb +52 -95
  67. data/lib/guard.rb +108 -376
  68. data/lib/tasks/releaser.rb +116 -0
  69. data/man/guard.1 +12 -9
  70. data/man/guard.1.html +18 -12
  71. metadata +148 -77
  72. data/images/guard.png +0 -0
  73. data/lib/guard/guard.rb +0 -156
  74. data/lib/guard/hook.rb +0 -120
  75. data/lib/guard/interactors/coolline.rb +0 -64
  76. data/lib/guard/interactors/helpers/completion.rb +0 -32
  77. data/lib/guard/interactors/helpers/terminal.rb +0 -46
  78. data/lib/guard/interactors/readline.rb +0 -94
  79. data/lib/guard/interactors/simple.rb +0 -19
  80. data/lib/guard/notifiers/emacs.rb +0 -69
  81. data/lib/guard/notifiers/gntp.rb +0 -118
  82. data/lib/guard/notifiers/growl.rb +0 -99
  83. data/lib/guard/notifiers/growl_notify.rb +0 -92
  84. data/lib/guard/notifiers/libnotify.rb +0 -96
  85. data/lib/guard/notifiers/notifysend.rb +0 -84
  86. data/lib/guard/notifiers/rb_notifu.rb +0 -102
  87. data/lib/guard/notifiers/terminal_notifier.rb +0 -66
  88. data/lib/guard/notifiers/tmux.rb +0 -69
  89. data/lib/guard/version.rbc +0 -130
@@ -0,0 +1,53 @@
1
+ require "guard/plugin_util"
2
+ require "guard/group"
3
+ require "guard/plugin"
4
+
5
+ module Guard
6
+ # @private api
7
+ module Internals
8
+ class Plugins
9
+ def initialize
10
+ @plugins = []
11
+ end
12
+
13
+ def all(filter = nil)
14
+ return @plugins if filter.nil?
15
+ matcher = matcher_for(filter)
16
+ @plugins.select { |plugin| matcher.call(plugin) }
17
+ end
18
+
19
+ def remove(plugin)
20
+ @plugins.delete(plugin)
21
+ end
22
+
23
+ # TODO: should it allow duplicates? (probably yes because of different
24
+ # configs or groups)
25
+ def add(name, options)
26
+ @plugins << PluginUtil.new(name).initialize_plugin(options)
27
+ end
28
+
29
+ private
30
+
31
+ def matcher_for(filter)
32
+ case filter
33
+ when String, Symbol
34
+ shortname = filter.to_s.downcase.delete("-")
35
+ ->(plugin) { plugin.name == shortname }
36
+ when Regexp
37
+ ->(plugin) { plugin.name =~ filter }
38
+ when Hash
39
+ lambda do |plugin|
40
+ filter.all? do |k, v|
41
+ case k
42
+ when :name
43
+ plugin.name == v.to_s.downcase.delete("-")
44
+ when :group
45
+ plugin.group.name == v.to_sym
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,51 @@
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 = []
12
+ changes = { modified: [], added: [], removed: [] }
13
+
14
+ while pending?
15
+ if (item = @queue.pop).first.is_a?(Symbol)
16
+ actions << item
17
+ else
18
+ item.each { |key, value| changes[key] += value }
19
+ end
20
+ end
21
+
22
+ _run_actions(actions)
23
+ return if changes.values.all?(&:empty?)
24
+ Runner.new.run_on_changes(*changes.values)
25
+ end
26
+
27
+ def pending?
28
+ !@queue.empty?
29
+ end
30
+
31
+ def <<(changes)
32
+ @queue << changes
33
+ end
34
+
35
+ private
36
+
37
+ def _run_actions(actions)
38
+ actions.each do |action_args|
39
+ args = action_args.dup
40
+ namespaced_action = args.shift
41
+ action = namespaced_action.to_s.sub(/^guard_/, "")
42
+ if @commander.respond_to?(action)
43
+ @commander.send(action, *args)
44
+ else
45
+ fail "Unknown action: #{action.inspect}"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,121 @@
1
+ require "guard"
2
+
3
+ module Guard
4
+ # @private api
5
+ module Internals
6
+ class Scope
7
+ def initialize
8
+ @interactor_plugin_scope = []
9
+ @interactor_group_scope = []
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ plugins: _hashify_scope(:plugin),
15
+ groups: _hashify_scope(:group)
16
+ }.dup.freeze
17
+ end
18
+
19
+ # TODO: refactor
20
+ def grouped_plugins(scope = { plugins: [], groups: [] })
21
+ items = nil
22
+ plugins = _find_non_empty_scope(:plugins, scope)
23
+ if plugins
24
+ items = Array(plugins).map { |plugin| _instantiate(:plugin, plugin) }
25
+ end
26
+
27
+ unless items
28
+ # TODO: no coverage here!!
29
+ found = _find_non_empty_scope(:groups, scope)
30
+ found ||= Guard.state.session.groups.all
31
+ groups = Array(found).map { |group| _instantiate(:group, group) }
32
+ if groups.any? { |g| g.name == :common }
33
+ items = groups
34
+ else
35
+ items = ([_instantiate(:group, :common)] + Array(found)).compact
36
+ end
37
+ end
38
+
39
+ items.map do |plugin_or_group|
40
+ group = nil
41
+ plugins = [plugin_or_group]
42
+ if plugin_or_group.is_a?(Group)
43
+ # TODO: no coverage here!
44
+ group = plugin_or_group
45
+ plugins = Guard.state.session.plugins.all(group: group.name)
46
+ end
47
+ [group, plugins]
48
+ end
49
+ end
50
+
51
+ def from_interactor(scope)
52
+ @interactor_plugin_scope = Array(scope[:plugins])
53
+ @interactor_group_scope = Array(scope[:groups])
54
+ end
55
+
56
+ def titles(scope = nil)
57
+ hash = scope || to_hash
58
+ plugins = hash[:plugins]
59
+ groups = hash[:groups]
60
+ return plugins.map(&:title) unless plugins.nil? || plugins.empty?
61
+ return hash[:groups].map(&:title) unless groups.nil? || groups.empty?
62
+ ["all"]
63
+ end
64
+
65
+ private
66
+
67
+ # TODO: move to session
68
+ def _scope_names(new_scope, name)
69
+ items = Array(new_scope[:"#{name}s"] || new_scope[name]) if items.empty?
70
+
71
+ # Convert objects to names
72
+ items.map { |p| p.respond_to?(:name) ? p.name : p }
73
+ end
74
+
75
+ # TODO: let the Plugins and Groups classes handle this?
76
+ # TODO: why even instantiate?? just to check if it exists?
77
+ def _hashify_scope(type)
78
+ # TODO: get cmdline passed to initialize above?
79
+ cmdline = Array(Guard.state.session.send("cmdline_#{type}s"))
80
+ guardfile = Guard.state.session.send(:"guardfile_#{type}_scope")
81
+ interactor = instance_variable_get(:"@interactor_#{type}_scope")
82
+
83
+ # TODO: session should decide whether to use cmdline or guardfile -
84
+ # since it has access to both variables
85
+ items = [interactor, cmdline, guardfile].detect do |source|
86
+ !source.empty?
87
+ end
88
+
89
+ # TODO: not tested when groups/plugins given don't exist
90
+
91
+ # TODO: should already be instantiated
92
+ Array(items).map do |obj|
93
+ if obj.respond_to?(:name)
94
+ obj
95
+ else
96
+ name = obj
97
+ (type == :group ? _groups : _plugins).all(name).first
98
+ end
99
+ end.compact
100
+ end
101
+
102
+ def _instantiate(meth, obj)
103
+ # TODO: no coverage
104
+ return obj unless obj.is_a?(Symbol) || obj.is_a?(String)
105
+ Guard.state.session.send("#{meth}s".to_sym).all(obj).first
106
+ end
107
+
108
+ def _find_non_empty_scope(type, local_scope)
109
+ [Array(local_scope[type]), to_hash[type]].map(&:compact).detect(&:any?)
110
+ end
111
+
112
+ def _groups
113
+ Guard.state.session.groups
114
+ end
115
+
116
+ def _plugins
117
+ Guard.state.session.plugins
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,180 @@
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
+ class Session
12
+ attr_reader :plugins
13
+ attr_reader :groups
14
+
15
+ DEFAULT_OPTIONS = {
16
+ clear: false,
17
+ debug: false,
18
+ no_bundler_warning: false,
19
+
20
+ # User defined scopes
21
+ group: [],
22
+ plugin: [],
23
+
24
+ # Notifier
25
+ notify: true,
26
+
27
+ # Interactor
28
+ no_interactions: false,
29
+
30
+ # Guardfile options:
31
+ # guardfile_contents
32
+ guardfile: nil,
33
+
34
+ # Listener options
35
+ # TODO: rename to watchdirs?
36
+ watchdir: Dir.pwd,
37
+ latency: nil,
38
+ force_polling: false,
39
+ wait_for_delay: nil,
40
+ listen_on: nil
41
+ }
42
+
43
+ def cmdline_groups
44
+ @cmdline_groups.dup.freeze
45
+ end
46
+
47
+ def cmdline_plugins
48
+ @cmdline_plugins.dup.freeze
49
+ end
50
+
51
+ def initialize(new_options)
52
+ @options = Options.new(new_options, DEFAULT_OPTIONS)
53
+
54
+ @plugins = Plugins.new
55
+ @groups = Groups.new
56
+
57
+ @cmdline_groups = @options[:group]
58
+ @cmdline_plugins = @options[:plugin]
59
+
60
+ @clear = @options[:clear]
61
+ @debug = @options[:debug]
62
+ @watchdirs = Array(@options[:watchdir])
63
+ @notify = @options[:notify]
64
+ @interactor_name = @options[:no_interactions] ? :sleep : :pry_wrapper
65
+
66
+ @guardfile_plugin_scope = []
67
+ @guardfile_group_scope = []
68
+ @guardfile_ignore = []
69
+ @guardfile_ignore_bang = []
70
+
71
+ @guardfile_notifier_options = {}
72
+ end
73
+
74
+ def guardfile_scope(scope)
75
+ opts = scope.dup
76
+
77
+ groups = Array(opts.delete(:groups))
78
+ group = Array(opts.delete(:group))
79
+ @guardfile_group_scope = Array(groups) + Array(group)
80
+
81
+ plugins = Array(opts.delete(:plugins))
82
+ plugin = Array(opts.delete(:plugin))
83
+ @guardfile_plugin_scope = Array(plugins) + Array(plugin)
84
+
85
+ fail "Unknown options: #{opts.inspect}" unless opts.empty?
86
+ end
87
+
88
+ # TODO: create a EvaluatorResult class?
89
+ attr_reader :guardfile_group_scope
90
+ attr_reader :guardfile_plugin_scope
91
+ attr_accessor :guardfile_ignore_bang
92
+
93
+ attr_reader :guardfile_ignore
94
+ def guardfile_ignore=(ignores)
95
+ @guardfile_ignore += Array(ignores).flatten
96
+ end
97
+
98
+ def clearing(on)
99
+ @clear = on
100
+ end
101
+
102
+ def clearing?
103
+ @clear
104
+ end
105
+
106
+ alias :clear? :clearing?
107
+
108
+ def debug?
109
+ @debug
110
+ end
111
+
112
+ def watchdirs
113
+ @watchdirs_from_guardfile ||= nil
114
+ @watchdirs_from_guardfile || @watchdirs
115
+ end
116
+
117
+ # set by Dsl with :directories() command
118
+ def watchdirs=(dirs)
119
+ dirs = [Dir.pwd] if dirs.empty?
120
+ @watchdirs_from_guardfile = dirs.map { |dir| File.expand_path dir }
121
+ end
122
+
123
+ def listener_args
124
+ if @options[:listen_on]
125
+ [:on, @options[:listen_on]]
126
+ else
127
+ listener_options = {}
128
+ [:latency, :force_polling, :wait_for_delay].each do |option|
129
+ listener_options[option] = @options[option] if @options[option]
130
+ end
131
+ expanded_watchdirs = watchdirs.map { |dir| File.expand_path dir }
132
+ [:to, *expanded_watchdirs, listener_options]
133
+ end
134
+ end
135
+
136
+ def evaluator_options
137
+ opts = { guardfile: @options[:guardfile] }
138
+ # TODO: deprecate :guardfile_contents
139
+ if @options[:guardfile_contents]
140
+ opts[:contents] = @options[:guardfile_contents]
141
+ end
142
+ opts
143
+ end
144
+
145
+ def notify_options
146
+ names = @guardfile_notifier_options.keys
147
+ return { notify: false } if names.include?(:off)
148
+
149
+ {
150
+ notify: @options[:notify],
151
+ notifiers: @guardfile_notifier_options
152
+ }
153
+ end
154
+
155
+ def guardfile_notification=(config)
156
+ @guardfile_notifier_options.merge!(config)
157
+ end
158
+
159
+ attr_reader :interactor_name
160
+
161
+ # TODO: call this from within action, not within interactor command
162
+ def convert_scope(entries)
163
+ scopes = { plugins: [], groups: [] }
164
+ unknown = []
165
+
166
+ entries.each do |entry|
167
+ if plugin = plugins.all(entry).first
168
+ scopes[:plugins] << plugin
169
+ elsif group = groups.all(entry).first
170
+ scopes[:groups] << group
171
+ else
172
+ unknown << entry
173
+ end
174
+ end
175
+
176
+ [scopes, unknown]
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,25 @@
1
+ require "guard/group"
2
+
3
+ require "guard/plugin_util"
4
+ require "guard/internals/session"
5
+ require "guard/internals/scope"
6
+ require "guard/runner"
7
+
8
+ module Guard
9
+ module Internals
10
+ class State
11
+ # Minimal setup for non-interactive commands (list, init, show, etc.)
12
+ def initialize(cmdline_opts)
13
+ @session = Session.new(cmdline_opts)
14
+
15
+ @scope = Scope.new
16
+
17
+ # NOTE: must be set before anything calls Guard::UI.debug
18
+ Debugging.start if session.debug?
19
+ end
20
+
21
+ attr_reader :scope
22
+ attr_reader :session
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ module Guard
2
+ module Internals
3
+ module Tracing
4
+ def self.trace(mod, meth)
5
+ meta = (class << mod; self; end)
6
+ original_meth = "original_#{meth}".to_sym
7
+
8
+ if mod.respond_to?(original_meth)
9
+ fail "ALREADY TRACED: #{mod}.#{meth}"
10
+ end
11
+
12
+ meta.send(:alias_method, original_meth, meth)
13
+ meta.send(:define_method, meth) do |*args, &block|
14
+ yield(*args) if block_given?
15
+ mod.send original_meth, *args, &block
16
+ end
17
+ end
18
+
19
+ def self.untrace(mod, meth)
20
+ meta = (class << mod; self; end)
21
+ original_meth = "original_#{meth}".to_sym
22
+
23
+ unless mod.respond_to?(original_meth)
24
+ fail "NOT TRACED: #{mod}.#{meth} (no method: #{original_meth})"
25
+ end
26
+
27
+ meta.send(:remove_method, meth)
28
+ meta.send(:alias_method, meth, original_meth)
29
+ meta.send(:undef_method, original_meth)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module Guard
2
+ module Internals
3
+ module Traps
4
+ def self.handle(signal, &block)
5
+ return unless Signal.list.key?(signal)
6
+ Signal.trap(signal, &block)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module Guard
2
+ module Jobs
3
+ class Base
4
+ def initialize(_options)
5
+ end
6
+
7
+ # @return [Symbol] :stopped once job is finished
8
+ # @return [Symbol] :exit to tell Guard to terminate
9
+ def foreground
10
+ end
11
+
12
+ def background
13
+ end
14
+
15
+ # Signal handler calls this, so avoid actually doing
16
+ # anything other than signaling threads
17
+ def handle_interrupt
18
+ end
19
+ end
20
+ end
21
+ end