guard 1.8.3 → 2.0.0.pre

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -10
  3. data/README.md +54 -33
  4. data/lib/guard.rb +133 -483
  5. data/lib/guard/cli.rb +78 -82
  6. data/lib/guard/commander.rb +121 -0
  7. data/lib/guard/commands/all.rb +1 -1
  8. data/lib/guard/commands/reload.rb +1 -1
  9. data/lib/guard/deprecated_methods.rb +59 -0
  10. data/lib/guard/deprecator.rb +107 -0
  11. data/lib/guard/dsl.rb +143 -329
  12. data/lib/guard/dsl_describer.rb +101 -57
  13. data/lib/guard/group.rb +27 -8
  14. data/lib/guard/guard.rb +25 -150
  15. data/lib/guard/guardfile.rb +35 -85
  16. data/lib/guard/guardfile/evaluator.rb +245 -0
  17. data/lib/guard/guardfile/generator.rb +89 -0
  18. data/lib/guard/interactor.rb +147 -163
  19. data/lib/guard/notifier.rb +83 -137
  20. data/lib/guard/notifiers/base.rb +220 -0
  21. data/lib/guard/notifiers/emacs.rb +39 -37
  22. data/lib/guard/notifiers/file_notifier.rb +29 -25
  23. data/lib/guard/notifiers/gntp.rb +68 -75
  24. data/lib/guard/notifiers/growl.rb +49 -52
  25. data/lib/guard/notifiers/growl_notify.rb +51 -56
  26. data/lib/guard/notifiers/libnotify.rb +41 -48
  27. data/lib/guard/notifiers/notifysend.rb +58 -38
  28. data/lib/guard/notifiers/rb_notifu.rb +54 -54
  29. data/lib/guard/notifiers/terminal_notifier.rb +48 -36
  30. data/lib/guard/notifiers/terminal_title.rb +23 -19
  31. data/lib/guard/notifiers/tmux.rb +110 -93
  32. data/lib/guard/options.rb +21 -0
  33. data/lib/guard/plugin.rb +66 -0
  34. data/lib/guard/plugin/base.rb +178 -0
  35. data/lib/guard/plugin/hooker.rb +123 -0
  36. data/lib/guard/plugin_util.rb +158 -0
  37. data/lib/guard/rake_task.rb +47 -0
  38. data/lib/guard/runner.rb +62 -82
  39. data/lib/guard/setuper.rb +248 -0
  40. data/lib/guard/ui.rb +24 -80
  41. data/lib/guard/ui/colors.rb +60 -0
  42. data/lib/guard/version.rb +1 -2
  43. data/lib/guard/watcher.rb +30 -30
  44. data/man/guard.1 +4 -4
  45. data/man/guard.1.html +6 -4
  46. metadata +25 -11
  47. data/lib/guard/hook.rb +0 -120
@@ -0,0 +1,21 @@
1
+ require 'ostruct'
2
+
3
+ module Guard
4
+
5
+ # A class that holds options. Can be instantiated with default options.
6
+ #
7
+ class Options < OpenStruct
8
+
9
+ # Initializes an Guard::Options object. `default_opts` is merged into
10
+ # `opts`.
11
+ #
12
+ # @param [Hash] opts the options
13
+ # @param [Hash] default_opts the default options
14
+ #
15
+ def initialize(opts = {}, default_opts = {})
16
+ super(default_opts.dup.merge(opts.dup))
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,66 @@
1
+ require 'guard/plugin/base'
2
+
3
+ module Guard
4
+
5
+ # Base class from which every Guard plugin implementation must inherit.
6
+ #
7
+ # Guard will trigger the {#start}, {#stop}, {#reload}, {#run_all} and
8
+ # {#run_on_changes} ({#run_on_additions}, {#run_on_modifications} and
9
+ # {#run_on_removals}) task methods depending on user interaction and file
10
+ # modification.
11
+ #
12
+ # {#run_on_changes} could be implemented to handle all the changes task case
13
+ # (additions, modifications, removals) in once, or each task can be
14
+ # implemented separately with a specific behavior.
15
+ #
16
+ # In each of these Guard task methods you have to implement some work when
17
+ # you want to support this kind of task. The return value of each Guard task
18
+ # method is not evaluated by Guard, but it'll be passed to the "_end" hook
19
+ # for further evaluation. You can throw `:task_has_failed` to indicate that
20
+ # your Guard plugin method was not successful, and successive Guard plugin
21
+ # tasks will be aborted when the group has set the `:halt_on_fail` option.
22
+ #
23
+ # @see Guard::Base
24
+ # @see Guard::Hooker
25
+ # @see Guard::Group
26
+ #
27
+ # @example Throw :task_has_failed
28
+ #
29
+ # def run_all
30
+ # if !runner.run(['all'])
31
+ # throw :task_has_failed
32
+ # end
33
+ # end
34
+ #
35
+ # Each Guard plugin should provide a template Guardfile located within the Gem
36
+ # at `lib/guard/guard-name/templates/Guardfile`.
37
+ #
38
+ # Watchers for a Guard plugin should return a file path or an array of files
39
+ # paths to Guard, but if your Guard plugin wants to allow any return value
40
+ # from a watcher, you can set the `any_return` option to true.
41
+ #
42
+ # If one of those methods raises an exception other than `:task_has_failed`,
43
+ # the `Guard::GuardName` instance will be removed from the active Guard
44
+ # plugins.
45
+ #
46
+ class Plugin
47
+ include Base
48
+
49
+ # Initializes a Guard plugin.
50
+ # Don't do any work here, especially as Guard plugins get initialized even
51
+ # if they are not in an active group!
52
+ #
53
+ # @param [Hash] options the Guard plugin options
54
+ # @option options [Array<Guard::Watcher>] watchers the Guard plugin file
55
+ # watchers
56
+ # @option options [Symbol] group the group this Guard plugin belongs to
57
+ # @option options [Boolean] any_return allow any object to be returned from
58
+ # a watcher
59
+ #
60
+ def initialize(options = {})
61
+ _set_instance_variables_from_options(options)
62
+ _register_callbacks
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,178 @@
1
+ module Guard
2
+
3
+ class Plugin
4
+
5
+ # Colection of shared methods between `Guard::Guard` (deprecated)
6
+ # and `Guard::Plugin`.
7
+ #
8
+ module Base
9
+ require 'guard/ui'
10
+ require 'guard/plugin/hooker'
11
+
12
+ include ::Guard::Plugin::Hooker
13
+
14
+ attr_accessor :group, :watchers, :callbacks, :options
15
+
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ end
19
+
20
+ module ClassMethods
21
+ # Returns the non-namespaced class name of the plugin
22
+ #
23
+ #
24
+ # @example Non-namespaced class name for Guard::RSpec
25
+ # Guard::RSpec.non_namespaced_classname
26
+ # #=> "RSpec"
27
+ #
28
+ # @return [String]
29
+ #
30
+ def non_namespaced_classname
31
+ self.to_s.sub('Guard::', '')
32
+ end
33
+
34
+ # Returns the non-namespaced name of the plugin
35
+ #
36
+ #
37
+ # @example Non-namespaced name for Guard::RSpec
38
+ # Guard::RSpec.non_namespaced_name
39
+ # #=> "rspec"
40
+ #
41
+ # @return [String]
42
+ #
43
+ def non_namespaced_name
44
+ non_namespaced_classname.downcase
45
+ end
46
+
47
+ # Specify the source for the Guardfile template.
48
+ # Each Guard plugin can redefine this method to add its own logic.
49
+ #
50
+ # @param [String] plugin_location the plugin location
51
+ #
52
+ def template(plugin_location)
53
+ File.read("#{ plugin_location }/lib/guard/#{ non_namespaced_name }/templates/Guardfile")
54
+ end
55
+ end
56
+
57
+ # Called once when Guard starts. Please override initialize method to
58
+ # init stuff.
59
+ #
60
+ # @raise [:task_has_failed] when start has failed
61
+ # @return [Object] the task result
62
+ #
63
+ # @!method start
64
+
65
+ # Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard
66
+ # quits).
67
+ #
68
+ # @raise [:task_has_failed] when stop has failed
69
+ # @return [Object] the task result
70
+ #
71
+ # @!method stop
72
+
73
+ # Called when `reload|r|z + enter` is pressed.
74
+ # This method should be mainly used for "reload" (really!) actions like
75
+ # reloading passenger/spork/bundler/...
76
+ #
77
+ # @raise [:task_has_failed] when reload has failed
78
+ # @return [Object] the task result
79
+ #
80
+ # @!method reload
81
+
82
+ # Called when just `enter` is pressed
83
+ # This method should be principally used for long action like running all
84
+ # specs/tests/...
85
+ #
86
+ # @raise [:task_has_failed] when run_all has failed
87
+ # @return [Object] the task result
88
+ #
89
+ # @!method run_all
90
+
91
+ # Default behaviour on file(s) changes that the Guard plugin watches.
92
+ #
93
+ # @param [Array<String>] paths the changes files or paths
94
+ # @raise [:task_has_failed] when run_on_changes has failed
95
+ # @return [Object] the task result
96
+ #
97
+ # @!method run_on_changes(paths)
98
+
99
+ # Called on file(s) additions that the Guard plugin watches.
100
+ #
101
+ # @param [Array<String>] paths the changes files or paths
102
+ # @raise [:task_has_failed] when run_on_additions has failed
103
+ # @return [Object] the task result
104
+ #
105
+ # @!method run_on_additions(paths)
106
+
107
+ # Called on file(s) modifications that the Guard plugin watches.
108
+ #
109
+ # @param [Array<String>] paths the changes files or paths
110
+ # @raise [:task_has_failed] when run_on_modifications has failed
111
+ # @return [Object] the task result
112
+ #
113
+ # @!method run_on_modifications(paths)
114
+
115
+ # Called on file(s) removals that the Guard plugin watches.
116
+ #
117
+ # @param [Array<String>] paths the changes files or paths
118
+ # @raise [:task_has_failed] when run_on_removals has failed
119
+ # @return [Object] the task result
120
+ #
121
+ # @!method run_on_removals(paths)
122
+
123
+ # Returns the plugin's name (without "guard-").
124
+ #
125
+ # @example Name for Guard::RSpec
126
+ # > Guard::RSpec.new.name
127
+ # => "rspec"
128
+ #
129
+ # @return [String]
130
+ #
131
+ def name
132
+ @name ||= self.class.non_namespaced_name
133
+ end
134
+
135
+ # Returns the plugin's class name without the Guard:: namespace.
136
+ #
137
+ # @example Title for Guard::RSpec
138
+ # > Guard::RSpec.new.title
139
+ # => "RSpec"
140
+ #
141
+ # @return [String]
142
+ #
143
+ def title
144
+ @title ||= self.class.non_namespaced_classname
145
+ end
146
+
147
+ # String representation of the plugin.
148
+ #
149
+ # @example String representation of an instance of the Guard::RSpec plugin
150
+ # Guard::RSpec.new.title
151
+ # #=> "#<Guard::RSpec @name=rspec @group=#<Guard::Group @name=default @options={}> @watchers=[] @callbacks=[] @options={all_after_pass: true}>"
152
+ #
153
+ # @return [String] the string representation
154
+ #
155
+ def to_s
156
+ "#<#{self.class} @name=#{name} @group=#{group} @watchers=#{watchers} @callbacks=#{callbacks} @options=#{options}>"
157
+ end
158
+
159
+ private
160
+
161
+ # Sets the @group, @watchers, @callbacks and @options variables from the
162
+ # given options hash.
163
+ #
164
+ # @param [Hash] options the Guard plugin options
165
+ #
166
+ # @see Guard::Plugin.initialize
167
+ #
168
+ def _set_instance_variables_from_options(options)
169
+ group_name = options.delete(:group) { :default }
170
+ @group = ::Guard.add_group(group_name)
171
+ @watchers = options.delete(:watchers) { [] }
172
+ @callbacks = options.delete(:callbacks) { [] }
173
+ @options = options
174
+ end
175
+
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,123 @@
1
+ module Guard
2
+
3
+ class Plugin
4
+
5
+ # Guard has a hook mechanism that allows you to insert callbacks for
6
+ # individual Guard plugins.
7
+ # By default, each of the Guard plugin instance methods has a "_begin" and
8
+ # an "_end" hook.
9
+ # For example, the Guard::Plugin#start method has a :start_begin hook that
10
+ # is runs immediately before Guard::Plugin#start, and a :start_end hook
11
+ # that runs immediately after Guard::Plugin#start.
12
+ #
13
+ # Read more about [hooks and callbacks on the
14
+ # wiki](https://github.com/guard/guard/wiki/Hooks-and-callbacks).
15
+ #
16
+ module Hooker
17
+
18
+ require 'guard/ui'
19
+
20
+ # Get all callbacks registered for all Guard plugins present in the
21
+ # Guardfile.
22
+ #
23
+ def self.callbacks
24
+ @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
25
+ end
26
+
27
+ # Add a callback.
28
+ #
29
+ # @param [Block] listener the listener to notify
30
+ # @param [Guard::Plugin] guard_plugin the Guard plugin to add the callback
31
+ # @param [Array<Symbol>] events the events to register
32
+ #
33
+ def self.add_callback(listener, guard_plugin, events)
34
+ _events = events.is_a?(Array) ? events : [events]
35
+ _events.each do |event|
36
+ callbacks[[guard_plugin, event]] << listener
37
+ end
38
+ end
39
+
40
+ # Checks if a callback has been registered.
41
+ #
42
+ # @param [Block] listener the listener to notify
43
+ # @param [Guard::Plugin] guard_plugin the Guard plugin to add the callback
44
+ # @param [Symbol] event the event to look for
45
+ #
46
+ def self.has_callback?(listener, guard_plugin, event)
47
+ callbacks[[guard_plugin, event]].include?(listener)
48
+ end
49
+
50
+ # Notify a callback.
51
+ #
52
+ # @param [Guard::Plugin] guard_plugin the Guard plugin to add the callback
53
+ # @param [Symbol] event the event to trigger
54
+ # @param [Array] args the arguments for the listener
55
+ #
56
+ def self.notify(guard_plugin, event, *args)
57
+ callbacks[[guard_plugin, event]].each do |listener|
58
+ listener.call(guard_plugin, event, *args)
59
+ end
60
+ end
61
+
62
+ # Reset all callbacks.
63
+ #
64
+ def self.reset_callbacks!
65
+ @callbacks = nil
66
+ end
67
+
68
+ # When event is a Symbol, {#hook} will generate a hook name
69
+ # by concatenating the method name from where {#hook} is called
70
+ # with the given Symbol.
71
+ #
72
+ # @example Add a hook with a Symbol
73
+ #
74
+ # def run_all
75
+ # hook :foo
76
+ # end
77
+ #
78
+ # Here, when {Guard::Plugin::Base#run_all} is called, {#hook} will notify
79
+ # callbacks registered for the "run_all_foo" event.
80
+ #
81
+ # When event is a String, {#hook} will directly turn the String
82
+ # into a Symbol.
83
+ #
84
+ # @example Add a hook with a String
85
+ #
86
+ # def run_all
87
+ # hook "foo_bar"
88
+ # end
89
+ #
90
+ # When {Guard::Plugin::Base#run_all} is called, {#hook} will notify
91
+ # callbacks registered for the "foo_bar" event.
92
+ #
93
+ # @param [Symbol, String] event the name of the Guard event
94
+ # @param [Array] args the parameters are passed as is to the callbacks
95
+ # registered for the given event.
96
+ #
97
+ def hook(event, *args)
98
+ hook_name = if event.is_a? Symbol
99
+ calling_method = caller[0][/`([^']*)'/, 1]
100
+ "#{ calling_method }_#{ event }"
101
+ else
102
+ event
103
+ end
104
+
105
+ ::Guard::UI.debug "Hook :#{ hook_name } executed for #{ self }"
106
+
107
+ Hooker.notify(self, hook_name.to_sym, *args)
108
+ end
109
+
110
+ private
111
+
112
+ # Add all the Guard::Plugin's callbacks to the global @callbacks array
113
+ # that's used by Guard to know which callbacks to notify.
114
+ #
115
+ def _register_callbacks
116
+ callbacks.each do |callback|
117
+ self.class.add_callback(callback[:listener], self, callback[:events])
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,158 @@
1
+ require 'guard/ui'
2
+
3
+ module Guard
4
+
5
+ # This class contains useful methods to:
6
+ #
7
+ # * Fetch all the Guard plugins names;
8
+ # * Initialize a plugin, get its location;
9
+ # * Return its class name;
10
+ # * Add its template to the Guardfile.
11
+ #
12
+ class PluginUtil
13
+
14
+ attr_accessor :name
15
+
16
+ # Returns a list of Guard plugin Gem names installed locally.
17
+ #
18
+ # @return [Array<String>] a list of Guard plugin gem names
19
+ #
20
+ def self.plugin_names
21
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
22
+ Gem::Specification.find_all.select do |x|
23
+ if x.name =~ /^guard-/
24
+ true
25
+ elsif x.name != 'guard'
26
+ guard_plugin_path = File.join(x.full_gem_path, "lib/guard/#{ x.name }.rb")
27
+ File.exists?( guard_plugin_path )
28
+ end
29
+ end
30
+ else
31
+ Gem.source_index.find_name(/^guard-/)
32
+ end.map { |x| x.name.sub(/^guard-/, '') }.uniq
33
+ end
34
+
35
+ # Initializes a new `Guard::PluginUtil` object.
36
+ #
37
+ # @param [String] name the name of the Guard plugin
38
+ #
39
+ def initialize(name)
40
+ @name = name.to_s.sub(/^guard-/, '')
41
+ end
42
+
43
+ # Initializes a new `Guard::Plugin` with the given `options` hash. This
44
+ # methods handles plugins that inherit from the deprecated `Guard::Guard`
45
+ # class as well as plugins that inherit from `Guard::Plugin`.
46
+ #
47
+ # @see Guard::Plugin
48
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to upgrade for Guard 2.0
49
+ #
50
+ # @return [Guard::Plugin] the initialized plugin
51
+ # @return [Guard::Guard] the initialized plugin. This return type is
52
+ # deprecated and the plugin's maintainer should update it to be
53
+ # compatible with Guard 2.0. For more information on how to upgrade for
54
+ # Guard 2.0, please head over to: https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0
55
+ #
56
+ def initialize_plugin(options)
57
+ if plugin_class.superclass == ::Guard::Guard
58
+ plugin_class.new(options.delete(:watchers), options)
59
+ else
60
+ plugin_class.new(options)
61
+ end
62
+ end
63
+
64
+ # Locates a path to a Guard plugin gem.
65
+ #
66
+ # @return [String] the full path to the plugin gem
67
+ #
68
+ def plugin_location
69
+ @plugin_location ||= begin
70
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
71
+ Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
72
+ else
73
+ Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
74
+ end
75
+ end
76
+ rescue
77
+ ::Guard::UI.error "Could not find 'guard-#{ name }' gem path."
78
+ end
79
+
80
+ # Tries to load the Guard plugin main class. This transforms the supplied
81
+ # plugin name into a class name:
82
+ #
83
+ # * `guardname` will become `Guard::Guardname`
84
+ # * `dashed-guard-name` will become `Guard::DashedGuardName`
85
+ # * `underscore_guard_name` will become `Guard::UnderscoreGuardName`
86
+ #
87
+ # When no class is found with the strict case sensitive rules, another
88
+ # try is made to locate the class without matching case:
89
+ #
90
+ # * `rspec` will find a class `Guard::RSpec`
91
+ #
92
+ # @option options [Boolean] fail_gracefully whether error messages should not be printed
93
+ # @return [Class, nil] the loaded class
94
+ #
95
+ def plugin_class(options = {})
96
+ options = { fail_gracefully: false }.merge(options)
97
+
98
+ try_require = false
99
+ begin
100
+ require "guard/#{ name.downcase }" if try_require
101
+
102
+ @plugin_class ||= ::Guard.const_get(_plugin_constant)
103
+ rescue TypeError
104
+ if try_require
105
+ ::Guard::UI.error "Could not find class Guard::#{ _constant_name }"
106
+ else
107
+ try_require = true
108
+ retry
109
+ end
110
+ rescue LoadError => loadError
111
+ unless options[:fail_gracefully]
112
+ ::Guard::UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ _constant_name }"
113
+ ::Guard::UI.error loadError.to_s
114
+ end
115
+ end
116
+ end
117
+
118
+ # Adds a plugin's template to the Guardfile.
119
+ #
120
+ def add_to_guardfile
121
+ if ::Guard.evaluator.guardfile_include?(name)
122
+ ::Guard::UI.info "Guardfile already includes #{ name } guard"
123
+ else
124
+ content = File.read('Guardfile')
125
+ File.open('Guardfile', 'wb') do |f|
126
+ f.puts(content)
127
+ f.puts('')
128
+ f.puts(plugin_class.template(plugin_location))
129
+ end
130
+
131
+ ::Guard::UI.info "#{ name } guard added to Guardfile, feel free to edit it"
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ # Returns the constant for the current plugin.
138
+ #
139
+ # @example Returns the constant for a plugin
140
+ # > Guard::PluginUtil.new('rspec').send(:_plugin_constant)
141
+ # => Guard::RSpec
142
+ #
143
+ def _plugin_constant
144
+ @_plugin_constant ||= ::Guard.constants.find { |c| c.to_s.downcase == _constant_name.downcase }
145
+ end
146
+
147
+ # Guesses the most probable name for the current plugin based on its name.
148
+ #
149
+ # @example Returns the most probable name for a plugin
150
+ # > Guard::PluginUtil.new('rspec').send(:_constant_name)
151
+ # => "Rspec"
152
+ #
153
+ def _constant_name
154
+ @_constant_name ||= name.gsub(/\/(.?)/) { "::#{ $1.upcase }" }.gsub(/(?:^|[_-])(.)/) { $1.upcase }
155
+ end
156
+
157
+ end
158
+ end