guard 1.8.3 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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