guard 2.6.1 → 2.7.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.
- checksums.yaml +4 -4
- data/README.md +73 -58
- data/bin/guard +2 -2
- data/lib/guard.rb +64 -59
- data/lib/guard/cli.rb +66 -60
- data/lib/guard/cli.rb.orig +215 -0
- data/lib/guard/commander.rb +45 -69
- data/lib/guard/commands/all.rb +21 -19
- data/lib/guard/commands/change.rb +17 -22
- data/lib/guard/commands/notification.rb +15 -16
- data/lib/guard/commands/pause.rb +14 -15
- data/lib/guard/commands/reload.rb +19 -20
- data/lib/guard/commands/scope.rb +23 -19
- data/lib/guard/commands/show.rb +13 -16
- data/lib/guard/deprecated_methods.rb +6 -10
- data/lib/guard/deprecator.rb +52 -37
- data/lib/guard/dsl.rb +55 -33
- data/lib/guard/dsl_describer.rb +83 -31
- data/lib/guard/dsl_describer.rb.orig +184 -0
- data/lib/guard/group.rb +7 -6
- data/lib/guard/guard.rb +4 -4
- data/lib/guard/guard.rb.orig +42 -0
- data/lib/guard/guardfile.rb +12 -13
- data/lib/guard/guardfile/evaluator.rb +77 -55
- data/lib/guard/guardfile/evaluator.rb.orig +275 -0
- data/lib/guard/guardfile/generator.rb +25 -20
- data/lib/guard/interactor.rb +52 -293
- data/lib/guard/interactor.rb.orig +85 -0
- data/lib/guard/jobs/base.rb +21 -0
- data/lib/guard/jobs/pry_wrapper.rb +290 -0
- data/lib/guard/jobs/pry_wrapper.rb.orig +293 -0
- data/lib/guard/jobs/sleep.rb +25 -0
- data/lib/guard/notifier.rb +42 -39
- data/lib/guard/notifiers/base.rb +25 -24
- data/lib/guard/notifiers/emacs.rb +30 -24
- data/lib/guard/notifiers/file_notifier.rb +3 -7
- data/lib/guard/notifiers/gntp.rb +22 -22
- data/lib/guard/notifiers/growl.rb +16 -15
- data/lib/guard/notifiers/libnotify.rb +7 -10
- data/lib/guard/notifiers/notifysend.rb +15 -14
- data/lib/guard/notifiers/rb_notifu.rb +8 -10
- data/lib/guard/notifiers/terminal_notifier.rb +15 -11
- data/lib/guard/notifiers/terminal_title.rb +4 -8
- data/lib/guard/notifiers/tmux.rb +104 -71
- data/lib/guard/options.rb +1 -5
- data/lib/guard/plugin.rb +1 -3
- data/lib/guard/plugin/base.rb +12 -9
- data/lib/guard/plugin/hooker.rb +1 -5
- data/lib/guard/plugin_util.rb +46 -25
- data/lib/guard/plugin_util.rb.orig +178 -0
- data/lib/guard/rake_task.rb +4 -7
- data/lib/guard/reevaluator.rb +13 -0
- data/lib/guard/runner.rb +50 -78
- data/lib/guard/runner.rb.orig +200 -0
- data/lib/guard/setuper.rb +199 -130
- data/lib/guard/setuper.rb.orig +348 -0
- data/lib/guard/sheller.rb +107 -0
- data/lib/guard/tags +367 -0
- data/lib/guard/ui.rb +50 -38
- data/lib/guard/ui.rb.orig +254 -0
- data/lib/guard/ui/colors.rb +17 -21
- data/lib/guard/version.rb +1 -1
- data/lib/guard/version.rb.orig +3 -0
- data/lib/guard/watcher.rb +49 -62
- metadata +21 -4
- data/lib/guard/notifiers/growl_notify.rb +0 -93
| @@ -0,0 +1,200 @@ | |
| 1 | 
            +
            require "lumberjack"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "guard/ui"
         | 
| 4 | 
            +
            require "guard/watcher"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Guard
         | 
| 7 | 
            +
              # The runner is responsible for running all methods defined on each plugin.
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              class Runner
         | 
| 10 | 
            +
                # Runs a Guard-task on all registered plugins.
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                # @param [Symbol] task the task to run
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # @param [Hash] scopes either the Guard plugin or the group to run the task
         | 
| 15 | 
            +
                # on
         | 
| 16 | 
            +
                #
         | 
| 17 | 
            +
                # @see self.run_supervised_task
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                def run(task, scope = {})
         | 
| 20 | 
            +
                  Lumberjack.unit_of_work do
         | 
| 21 | 
            +
                    _scoped_plugins(scope) do |guard|
         | 
| 22 | 
            +
                      run_supervised_task(guard, task) if guard.respond_to?(task)
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                MODIFICATION_TASKS = [
         | 
| 28 | 
            +
                  :run_on_modifications, :run_on_changes, :run_on_change
         | 
| 29 | 
            +
                ]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                ADDITION_TASKS     = [:run_on_additions, :run_on_changes, :run_on_change]
         | 
| 32 | 
            +
                REMOVAL_TASKS      = [:run_on_removals, :run_on_changes, :run_on_deletion]
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                # Runs the appropriate tasks on all registered plugins
         | 
| 35 | 
            +
                # based on the passed changes.
         | 
| 36 | 
            +
                #
         | 
| 37 | 
            +
                # @param [Array<String>] modified the modified paths.
         | 
| 38 | 
            +
                # @param [Array<String>] added the added paths.
         | 
| 39 | 
            +
                # @param [Array<String>] removed the removed paths.
         | 
| 40 | 
            +
                #
         | 
| 41 | 
            +
                def run_on_changes(modified, added, removed)
         | 
| 42 | 
            +
                  types = {
         | 
| 43 | 
            +
                    MODIFICATION_TASKS => modified,
         | 
| 44 | 
            +
                    ADDITION_TASKS => added,
         | 
| 45 | 
            +
                    REMOVAL_TASKS => removed
         | 
| 46 | 
            +
                  }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  ::Guard::UI.clearable
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  _scoped_plugins do |guard|
         | 
| 51 | 
            +
                    ::Guard::UI.clear
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    types.each do |tasks, unmatched_paths|
         | 
| 54 | 
            +
                      paths = ::Guard::Watcher.match_files(guard, unmatched_paths)
         | 
| 55 | 
            +
                      next if paths.empty?
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      next unless (task = tasks.detect { |meth| guard.respond_to?(meth) })
         | 
| 58 | 
            +
                      run_supervised_task(guard, task, paths)
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # Run a Guard plugin task, but remove the Guard plugin when his work leads
         | 
| 64 | 
            +
                # to a system failure.
         | 
| 65 | 
            +
                #
         | 
| 66 | 
            +
                # When the Group has `:halt_on_fail` disabled, we've to catch
         | 
| 67 | 
            +
                # `:task_has_failed` here in order to avoid an uncaught throw error.
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # @param [Guard::Plugin] guard the Guard to execute
         | 
| 70 | 
            +
                # @param [Symbol] task the task to run
         | 
| 71 | 
            +
                # @param [Array] args the arguments for the task
         | 
| 72 | 
            +
                # @raise [:task_has_failed] when task has failed
         | 
| 73 | 
            +
                #
         | 
| 74 | 
            +
                def run_supervised_task(guard, task, *args)
         | 
| 75 | 
            +
                  catch self.class.stopping_symbol_for(guard) do
         | 
| 76 | 
            +
                    guard.hook("#{ task }_begin", *args)
         | 
| 77 | 
            +
                    begin
         | 
| 78 | 
            +
                      result = guard.send(task, *args)
         | 
| 79 | 
            +
                    rescue Interrupt
         | 
| 80 | 
            +
                      throw(:task_has_failed)
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                    guard.hook("#{ task }_end", result)
         | 
| 83 | 
            +
                    result
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                rescue ScriptError, StandardError, RuntimeError
         | 
| 86 | 
            +
                  ::Guard::UI.error("#{ guard.class.name } failed to achieve its"\
         | 
| 87 | 
            +
                                    " <#{ task }>, exception was:" \
         | 
| 88 | 
            +
                                    "\n#{ $!.class }: #{ $!.message }" \
         | 
| 89 | 
            +
                                    "\n#{ $!.backtrace.join("\n") }")
         | 
| 90 | 
            +
                  ::Guard.plugins.delete guard
         | 
| 91 | 
            +
                  ::Guard::UI.info("\n#{ guard.class.name } has just been fired")
         | 
| 92 | 
            +
                  $!
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                # Returns the symbol that has to be caught when running a supervised task.
         | 
| 96 | 
            +
                #
         | 
| 97 | 
            +
                # @note If a Guard group is being run and it has the `:halt_on_fail`
         | 
| 98 | 
            +
                #   option set, this method returns :no_catch as it will be caught at the
         | 
| 99 | 
            +
                #   group level.
         | 
| 100 | 
            +
                # @see ._scoped_plugins
         | 
| 101 | 
            +
                #
         | 
| 102 | 
            +
                # @param [Guard::Plugin] guard the Guard plugin to execute
         | 
| 103 | 
            +
                # @return [Symbol] the symbol to catch
         | 
| 104 | 
            +
                #
         | 
| 105 | 
            +
                def self.stopping_symbol_for(guard)
         | 
| 106 | 
            +
                  guard.group.options[:halt_on_fail] ? :no_catch : :task_has_failed
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                private
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                # Loop through all groups and run the given task for each Guard plugin.
         | 
| 112 | 
            +
                #
         | 
| 113 | 
            +
                # If no scope is supplied, the global Guard scope is taken into account.
         | 
| 114 | 
            +
                # If both a plugin and a group scope is given, then only the plugin scope
         | 
| 115 | 
            +
                # is used.
         | 
| 116 | 
            +
                #
         | 
| 117 | 
            +
                # Stop the task run for the all Guard plugins within a group if one Guard
         | 
| 118 | 
            +
                # throws `:task_has_failed`.
         | 
| 119 | 
            +
                #
         | 
| 120 | 
            +
                # @param [Hash] scopes hash with plugins or a groups scope
         | 
| 121 | 
            +
                # @yield the task to run
         | 
| 122 | 
            +
                #
         | 
| 123 | 
            +
                def _scoped_plugins(scopes = {})
         | 
| 124 | 
            +
                  if plugins = _current_plugins_scope(scopes)
         | 
| 125 | 
            +
                    plugins.each do |guard|
         | 
| 126 | 
            +
                      yield(guard)
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
                  else
         | 
| 129 | 
            +
                    _current_groups_scope(scopes).each do |group|
         | 
| 130 | 
            +
                      current_plugin = nil
         | 
| 131 | 
            +
                      block_return = catch :task_has_failed do
         | 
| 132 | 
            +
                        ::Guard.plugins(group: group.name).each do |guard|
         | 
| 133 | 
            +
                          current_plugin = guard
         | 
| 134 | 
            +
                          yield(guard)
         | 
| 135 | 
            +
                        end
         | 
| 136 | 
            +
                      end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                      next unless block_return.nil?
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                      ::Guard::UI.info "#{ current_plugin.class.name } has failed,"\
         | 
| 141 | 
            +
                        " other group's plugins execution has been halted."
         | 
| 142 | 
            +
                    end
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                # Returns the current plugins scope.
         | 
| 147 | 
            +
                # Local plugins scope wins over global plugins scope.
         | 
| 148 | 
            +
                # If no plugins scope is found, then NO plugins are returned.
         | 
| 149 | 
            +
                #
         | 
| 150 | 
            +
                # @param [Hash] scopes hash with a local plugins or a groups scope
         | 
| 151 | 
            +
                # @return [Array<Guard::Plugin>] the plugins to scope to
         | 
| 152 | 
            +
                #
         | 
| 153 | 
            +
                def _current_plugins_scope(scope)
         | 
| 154 | 
            +
                  if plugins = _find_non_empty_plugins_scope(scope)
         | 
| 155 | 
            +
                    Array(plugins).map do |plugin|
         | 
| 156 | 
            +
                      plugin.is_a?(Symbol) ? ::Guard.plugin(plugin) : plugin
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                  else
         | 
| 159 | 
            +
                    nil
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                # Returns the current groups scope.
         | 
| 164 | 
            +
                # Local groups scope wins over global groups scope.
         | 
| 165 | 
            +
                # If no groups scope is found, then ALL groups are returned.
         | 
| 166 | 
            +
                #
         | 
| 167 | 
            +
                # @param [Hash] scopes hash with a local plugins or a groups scope
         | 
| 168 | 
            +
                # @return [Array<Guard::Group>] the groups to scope to
         | 
| 169 | 
            +
                #
         | 
| 170 | 
            +
                def _current_groups_scope(scope)
         | 
| 171 | 
            +
                  Array(_find_non_empty_groups_scope(scope)).map do |group|
         | 
| 172 | 
            +
                    group.is_a?(Symbol) ? ::Guard.group(group) : group
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                # Find the first non empty element in the given possibilities
         | 
| 177 | 
            +
                #
         | 
| 178 | 
            +
                def _find_non_empty_scope(type, local_scope, *additional_possibilities)
         | 
| 179 | 
            +
                  found = [
         | 
| 180 | 
            +
                    local_scope[:"#{type}s"],
         | 
| 181 | 
            +
                    local_scope[type.to_sym],
         | 
| 182 | 
            +
                    ::Guard.scope[:"#{type}s"],
         | 
| 183 | 
            +
                    additional_possibilities.flatten
         | 
| 184 | 
            +
                  ].compact.detect { |a| !Array(a).empty? }
         | 
| 185 | 
            +
                  found ? [::Guard.group(:common)] + Array(found) : found
         | 
| 186 | 
            +
                end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                # Find the first non empty plugins scope
         | 
| 189 | 
            +
                #
         | 
| 190 | 
            +
                def _find_non_empty_plugins_scope(scope)
         | 
| 191 | 
            +
                  _find_non_empty_scope(:plugin, scope)
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                # Find the first non empty groups scope
         | 
| 195 | 
            +
                #
         | 
| 196 | 
            +
                def _find_non_empty_groups_scope(scope)
         | 
| 197 | 
            +
                  _find_non_empty_scope(:group, scope, ::Guard.groups)
         | 
| 198 | 
            +
                end
         | 
| 199 | 
            +
              end
         | 
| 200 | 
            +
            end
         | 
    
        data/lib/guard/setuper.rb
    CHANGED
    
    | @@ -1,11 +1,10 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 3 | 
            -
            require  | 
| 1 | 
            +
            require "thread"
         | 
| 2 | 
            +
            require "listen"
         | 
| 3 | 
            +
            require "guard/options"
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Guard
         | 
| 6 | 
            -
             | 
| 6 | 
            +
              # Sets up initial variables and options
         | 
| 7 7 | 
             
              module Setuper
         | 
| 8 | 
            -
             | 
| 9 8 | 
             
                DEFAULT_OPTIONS = {
         | 
| 10 9 | 
             
                  clear: false,
         | 
| 11 10 | 
             
                  notify: true,
         | 
| @@ -22,12 +21,12 @@ module Guard | |
| 22 21 | 
             
                  wait_for_delay: nil,
         | 
| 23 22 | 
             
                  listen_on: nil
         | 
| 24 23 | 
             
                }
         | 
| 25 | 
            -
                DEFAULT_GROUPS = [:default]
         | 
| 24 | 
            +
                DEFAULT_GROUPS = [:default, :common]
         | 
| 26 25 |  | 
| 27 26 | 
             
                # Initializes the Guard singleton:
         | 
| 28 27 | 
             
                #
         | 
| 29 28 | 
             
                # * Initialize the internal Guard state;
         | 
| 30 | 
            -
                # * Create the interactor | 
| 29 | 
            +
                # * Create the interactor
         | 
| 31 30 | 
             
                # * Select and initialize the file change listener.
         | 
| 32 31 | 
             
                #
         | 
| 33 32 | 
             
                # @option options [Boolean] clear if auto clear the UI should be done
         | 
| @@ -39,59 +38,33 @@ module Guard | |
| 39 38 | 
             
                #
         | 
| 40 39 | 
             
                # @return [Guard] the Guard singleton
         | 
| 41 40 | 
             
                #
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # TODO: this method has too many instance variables
         | 
| 43 | 
            +
                # and some are mock and leak between tests,
         | 
| 44 | 
            +
                # so ideally there should be a guard "instance"
         | 
| 45 | 
            +
                # object that can be created anew between tests
         | 
| 42 46 | 
             
                def setup(opts = {})
         | 
| 43 | 
            -
                   | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
                  @ | 
| 47 | 
            -
                  @ | 
| 48 | 
            -
                  @ | 
| 49 | 
            -
             | 
| 50 | 
            -
                  if options[:watchdir]
         | 
| 51 | 
            -
                    # Ensure we have an array
         | 
| 52 | 
            -
                    @watchdirs = Array(options[:watchdir]).map { |dir| File.expand_path dir }
         | 
| 53 | 
            -
                  end
         | 
| 47 | 
            +
                  reset_options(opts)
         | 
| 48 | 
            +
                  reset_evaluator(opts)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  @queue = Queue.new
         | 
| 51 | 
            +
                  @runner = ::Guard::Runner.new
         | 
| 52 | 
            +
                  @watchdirs = _setup_watchdirs
         | 
| 54 53 |  | 
| 55 54 | 
             
                  ::Guard::UI.clear(force: true)
         | 
| 55 | 
            +
             | 
| 56 56 | 
             
                  _setup_debug if options[:debug]
         | 
| 57 | 
            -
                  _setup_listener
         | 
| 57 | 
            +
                  @listener = _setup_listener
         | 
| 58 58 | 
             
                  _setup_signal_traps
         | 
| 59 59 |  | 
| 60 | 
            -
                   | 
| 61 | 
            -
                   | 
| 62 | 
            -
                  reset_scope
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                  evaluate_guardfile
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                  setup_scope(groups: options[:group], plugins: options[:plugin])
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                  _setup_notifier
         | 
| 69 | 
            -
             | 
| 60 | 
            +
                  _load_guardfile
         | 
| 61 | 
            +
                  @interactor = _setup_interactor
         | 
| 70 62 | 
             
                  self
         | 
| 71 63 | 
             
                end
         | 
| 72 64 |  | 
| 73 | 
            -
                 | 
| 74 | 
            -
                #
         | 
| 75 | 
            -
                def options
         | 
| 76 | 
            -
                  @options ||= ::Guard::Options.new(@opts, DEFAULT_OPTIONS)
         | 
| 77 | 
            -
                end
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                # Lazy initializer for Guardfile evaluator
         | 
| 80 | 
            -
                #
         | 
| 81 | 
            -
                def evaluator
         | 
| 82 | 
            -
                  @evaluator ||= ::Guard::Guardfile::Evaluator.new(@opts || {})
         | 
| 83 | 
            -
                end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                # Lazy initializer the interactor unless the user has specified not to.
         | 
| 86 | 
            -
                #
         | 
| 87 | 
            -
                def interactor
         | 
| 88 | 
            -
                  return if options[:no_interactions] || !::Guard::Interactor.enabled
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                  @interactor ||= ::Guard::Interactor.new
         | 
| 91 | 
            -
                end
         | 
| 65 | 
            +
                attr_reader :options, :evaluator, :interactor
         | 
| 92 66 |  | 
| 93 | 
            -
                #  | 
| 94 | 
            -
                #
         | 
| 67 | 
            +
                # Used only by tests (for all I know...)
         | 
| 95 68 | 
             
                def clear_options
         | 
| 96 69 | 
             
                  @options = nil
         | 
| 97 70 | 
             
                end
         | 
| @@ -117,25 +90,48 @@ module Guard | |
| 117 90 | 
             
                # @see Guard.setup_scope
         | 
| 118 91 | 
             
                #
         | 
| 119 92 | 
             
                def reset_scope
         | 
| 120 | 
            -
                   | 
| 93 | 
            +
                  # calls Guard.scope=() to set the instance variable directly, as opposed
         | 
| 94 | 
            +
                  # to Guard.scope()
         | 
| 95 | 
            +
                  ::Guard.scope = { groups: [], plugins: [] }
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                # Used to merge CLI options with Setuper defaults
         | 
| 99 | 
            +
                def reset_options(new_options)
         | 
| 100 | 
            +
                  @options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                # TODO: code smell - too many reset_* methods
         | 
| 104 | 
            +
                def reset_evaluator(new_options)
         | 
| 105 | 
            +
                  @evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def save_scope
         | 
| 109 | 
            +
                  # This actually replaces scope from command line,
         | 
| 110 | 
            +
                  # so scope set by 'scope' Pry command will be reset
         | 
| 111 | 
            +
                  @saved_scope = _prepare_scope(::Guard.scope)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def restore_scope
         | 
| 115 | 
            +
                  ::Guard.setup_scope(@saved_scope)
         | 
| 121 116 | 
             
                end
         | 
| 122 117 |  | 
| 123 118 | 
             
                attr_reader :watchdirs
         | 
| 124 119 |  | 
| 125 | 
            -
                # Stores the scopes defined by the user via the `--group` / `-g` option (to | 
| 126 | 
            -
                # only a specific group) or the `--plugin` / `-P` option (to run only a
         | 
| 120 | 
            +
                # Stores the scopes defined by the user via the `--group` / `-g` option (to
         | 
| 121 | 
            +
                # run only a specific group) or the `--plugin` / `-P` option (to run only a
         | 
| 127 122 | 
             
                # specific plugin).
         | 
| 128 123 | 
             
                #
         | 
| 129 124 | 
             
                # @see CLI#start
         | 
| 130 125 | 
             
                # @see Dsl#scope
         | 
| 131 126 | 
             
                #
         | 
| 132 | 
            -
                def setup_scope( | 
| 133 | 
            -
                   | 
| 134 | 
            -
             | 
| 135 | 
            -
                   | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 127 | 
            +
                def setup_scope(scope = {})
         | 
| 128 | 
            +
                  # TODO: there should be a special Scope class instead
         | 
| 129 | 
            +
                  scope = _prepare_scope(scope)
         | 
| 130 | 
            +
                  { groups: :add_group, plugins: :plugin }.each do |type, meth|
         | 
| 131 | 
            +
                    next unless scope[type].any?
         | 
| 132 | 
            +
                    ::Guard.scope[type] = scope[type].map do |item|
         | 
| 133 | 
            +
                      ::Guard.send(meth, item)
         | 
| 134 | 
            +
                    end
         | 
| 139 135 | 
             
                  end
         | 
| 140 136 | 
             
                end
         | 
| 141 137 |  | 
| @@ -146,17 +142,40 @@ module Guard | |
| 146 142 | 
             
                #
         | 
| 147 143 | 
             
                def evaluate_guardfile
         | 
| 148 144 | 
             
                  evaluator.evaluate_guardfile
         | 
| 149 | 
            -
                   | 
| 145 | 
            +
                  msg = "No plugins found in Guardfile, please add at least one."
         | 
| 146 | 
            +
                  ::Guard::UI.error msg unless _non_builtin_plugins?
         | 
| 150 147 | 
             
                end
         | 
| 151 148 |  | 
| 152 | 
            -
                 | 
| 149 | 
            +
                # Asynchronously trigger changes
         | 
| 150 | 
            +
                #
         | 
| 151 | 
            +
                # Currently supported args:
         | 
| 152 | 
            +
                #
         | 
| 153 | 
            +
                #   old style hash: {modified: ['foo'], added: ['bar'], removed: []}
         | 
| 154 | 
            +
                #
         | 
| 155 | 
            +
                #   new style signals with args: [:guard_pause, :unpaused ]
         | 
| 156 | 
            +
                #
         | 
| 157 | 
            +
                def async_queue_add(changes)
         | 
| 158 | 
            +
                  @queue << changes
         | 
| 153 159 |  | 
| 154 | 
            -
             | 
| 155 | 
            -
                   | 
| 156 | 
            -
                   | 
| 157 | 
            -
                  @interactor = nil
         | 
| 160 | 
            +
                  # Putting interactor in background puts guard into foreground
         | 
| 161 | 
            +
                  # so it can handle change notifications
         | 
| 162 | 
            +
                  Thread.new { interactor.background }
         | 
| 158 163 | 
             
                end
         | 
| 159 164 |  | 
| 165 | 
            +
                def pending_changes?
         | 
| 166 | 
            +
                  ! @queue.empty?
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                def add_builtin_plugins(guardfile)
         | 
| 170 | 
            +
                  return unless guardfile
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  pattern = _relative_pathname(guardfile).to_s
         | 
| 173 | 
            +
                  watcher = ::Guard::Watcher.new(pattern)
         | 
| 174 | 
            +
                  ::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                private
         | 
| 178 | 
            +
             | 
| 160 179 | 
             
                # Sets up various debug behaviors:
         | 
| 161 180 | 
             
                #
         | 
| 162 181 | 
             
                # * Abort threads on exception;
         | 
| @@ -175,15 +194,31 @@ module Guard | |
| 175 194 | 
             
                #
         | 
| 176 195 | 
             
                def _setup_listener
         | 
| 177 196 | 
             
                  if options[:listen_on]
         | 
| 178 | 
            -
                     | 
| 197 | 
            +
                    Listen.on(options[:listen_on], &_listener_callback)
         | 
| 179 198 | 
             
                  else
         | 
| 180 199 | 
             
                    listener_options = {}
         | 
| 181 200 | 
             
                    [:latency, :force_polling, :wait_for_delay].each do |option|
         | 
| 182 201 | 
             
                      listener_options[option] = options[option] if options[option]
         | 
| 183 202 | 
             
                    end
         | 
| 184 203 | 
             
                    listen_args = watchdirs + [listener_options]
         | 
| 185 | 
            -
                     | 
| 204 | 
            +
                    Listen.to(*listen_args, &_listener_callback)
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                # Process the change queue, running tasks within the main Guard thread
         | 
| 209 | 
            +
                def _process_queue
         | 
| 210 | 
            +
                  actions, changes = [], { modified: [], added: [], removed: [] }
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  while pending_changes?
         | 
| 213 | 
            +
                    if (item = @queue.pop).first.is_a?(Symbol)
         | 
| 214 | 
            +
                      actions << item
         | 
| 215 | 
            +
                    else
         | 
| 216 | 
            +
                      item.each { |key, value| changes[key] += value }
         | 
| 217 | 
            +
                    end
         | 
| 186 218 | 
             
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  _run_actions(actions)
         | 
| 221 | 
            +
                  runner.run_on_changes(*changes.values)
         | 
| 187 222 | 
             
                end
         | 
| 188 223 |  | 
| 189 224 | 
             
                # Sets up traps to catch signals used to control Guard.
         | 
| @@ -194,39 +229,24 @@ module Guard | |
| 194 229 | 
             
                # - 'INT' which is delegated to Pry if active, otherwise stops Guard.
         | 
| 195 230 | 
             
                #
         | 
| 196 231 | 
             
                def _setup_signal_traps
         | 
| 197 | 
            -
                   | 
| 198 | 
            -
                    if Signal.list.keys.include?('USR1')
         | 
| 199 | 
            -
                      Signal.trap('USR1') do
         | 
| 200 | 
            -
                        unless listener.paused?
         | 
| 201 | 
            -
                          Thread.new { within_preserved_state { ::Guard.pause } }
         | 
| 202 | 
            -
                        end
         | 
| 203 | 
            -
                      end
         | 
| 204 | 
            -
                    end
         | 
| 232 | 
            +
                  return if defined?(JRUBY_VERSION)
         | 
| 205 233 |  | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
                          Thread.new { within_preserved_state { ::Guard.pause } }
         | 
| 210 | 
            -
                        end
         | 
| 211 | 
            -
                      end
         | 
| 212 | 
            -
                    end
         | 
| 234 | 
            +
                  if Signal.list.keys.include?("USR1")
         | 
| 235 | 
            +
                    Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
         | 
| 236 | 
            +
                  end
         | 
| 213 237 |  | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
                        if interactor && interactor.thread
         | 
| 217 | 
            -
                          interactor.thread.raise(Interrupt)
         | 
| 218 | 
            -
                        else
         | 
| 219 | 
            -
                          ::Guard.stop
         | 
| 220 | 
            -
                        end
         | 
| 221 | 
            -
                      end
         | 
| 222 | 
            -
                    end
         | 
| 238 | 
            +
                  if Signal.list.keys.include?("USR2")
         | 
| 239 | 
            +
                    Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
         | 
| 223 240 | 
             
                  end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                  return unless Signal.list.keys.include?("INT")
         | 
| 243 | 
            +
                  Signal.trap("INT") { interactor.handle_interrupt }
         | 
| 224 244 | 
             
                end
         | 
| 225 245 |  | 
| 226 246 | 
             
                # Enables or disables the notifier based on user's configurations.
         | 
| 227 247 | 
             
                #
         | 
| 228 248 | 
             
                def _setup_notifier
         | 
| 229 | 
            -
                  if options[:notify] && ENV[ | 
| 249 | 
            +
                  if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
         | 
| 230 250 | 
             
                    ::Guard::Notifier.turn_on
         | 
| 231 251 | 
             
                  else
         | 
| 232 252 | 
             
                    ::Guard::Notifier.turn_off
         | 
| @@ -239,7 +259,7 @@ module Guard | |
| 239 259 | 
             
                def _debug_command_execution
         | 
| 240 260 | 
             
                  Kernel.send(:alias_method, :original_system, :system)
         | 
| 241 261 | 
             
                  Kernel.send(:define_method, :system) do |command, *args|
         | 
| 242 | 
            -
                    ::Guard::UI.debug "Command execution: #{ command } #{ args.join( | 
| 262 | 
            +
                    ::Guard::UI.debug "Command execution: #{ command } #{ args.join(" ") }"
         | 
| 243 263 | 
             
                    Kernel.send :original_system, command, *args
         | 
| 244 264 | 
             
                  end
         | 
| 245 265 |  | 
| @@ -250,53 +270,102 @@ module Guard | |
| 250 270 | 
             
                  end
         | 
| 251 271 | 
             
                end
         | 
| 252 272 |  | 
| 273 | 
            +
                # TODO: Guard::Watch or Guard::Scope should provide this
         | 
| 274 | 
            +
                def _scoped_watchers
         | 
| 275 | 
            +
                  watchers = []
         | 
| 276 | 
            +
                  runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
         | 
| 277 | 
            +
                  watchers
         | 
| 278 | 
            +
                end
         | 
| 279 | 
            +
             | 
| 253 280 | 
             
                # Check if any of the changes are actually watched for
         | 
| 254 | 
            -
                #
         | 
| 255 | 
            -
                # NOTE: this is called from the listen thread - be careful to not
         | 
| 256 | 
            -
                # modify any state
         | 
| 257 | 
            -
                #
         | 
| 258 | 
            -
                # TODO: move this to watcher class?
         | 
| 259 | 
            -
                #
         | 
| 260 281 | 
             
                def _relevant_changes?(changes)
         | 
| 261 | 
            -
                   | 
| 262 | 
            -
                   | 
| 282 | 
            +
                  files = changes.values.flatten(1)
         | 
| 283 | 
            +
                  watchers = _scoped_watchers
         | 
| 284 | 
            +
                  watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
         | 
| 285 | 
            +
                end
         | 
| 263 286 |  | 
| 264 | 
            -
             | 
| 265 | 
            -
                   | 
| 266 | 
            -
                   | 
| 267 | 
            -
             | 
| 268 | 
            -
                   | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 273 | 
            -
             | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 277 | 
            -
             | 
| 278 | 
            -
             | 
| 279 | 
            -
             | 
| 280 | 
            -
             | 
| 287 | 
            +
                def _relative_pathname(path)
         | 
| 288 | 
            +
                  full_path = Pathname(path)
         | 
| 289 | 
            +
                  full_path.relative_path_from(Pathname.pwd)
         | 
| 290 | 
            +
                rescue ArgumentError
         | 
| 291 | 
            +
                  full_path
         | 
| 292 | 
            +
                end
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                def _relative_pathnames(paths)
         | 
| 295 | 
            +
                  paths.map { |path| _relative_pathname(path) }
         | 
| 296 | 
            +
                end
         | 
| 297 | 
            +
             | 
| 298 | 
            +
                def _run_actions(actions)
         | 
| 299 | 
            +
                  actions.each do |action_args|
         | 
| 300 | 
            +
                    args = action_args.dup
         | 
| 301 | 
            +
                    namespaced_action = args.shift
         | 
| 302 | 
            +
                    action = namespaced_action.to_s.sub(/^guard_/, "")
         | 
| 303 | 
            +
                    if ::Guard.respond_to?(action)
         | 
| 304 | 
            +
                      ::Guard.send(action, *args)
         | 
| 305 | 
            +
                    else
         | 
| 306 | 
            +
                      fail "Unknown action: #{action.inspect}"
         | 
| 281 307 | 
             
                    end
         | 
| 282 308 | 
             
                  end
         | 
| 283 309 | 
             
                end
         | 
| 284 310 |  | 
| 311 | 
            +
                def _setup_watchdirs
         | 
| 312 | 
            +
                  dirs = Array(options[:watchdir])
         | 
| 313 | 
            +
                  dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
         | 
| 314 | 
            +
                end
         | 
| 315 | 
            +
             | 
| 285 316 | 
             
                def _listener_callback
         | 
| 286 317 | 
             
                  lambda do |modified, added, removed|
         | 
| 287 | 
            -
                     | 
| 288 | 
            -
             | 
| 289 | 
            -
             | 
| 290 | 
            -
             | 
| 291 | 
            -
                     | 
| 292 | 
            -
                    _relative_paths(all_changes)
         | 
| 318 | 
            +
                    relative_paths = {
         | 
| 319 | 
            +
                      modified: _relative_pathnames(modified),
         | 
| 320 | 
            +
                      added: _relative_pathnames(added),
         | 
| 321 | 
            +
                      removed: _relative_pathnames(removed)
         | 
| 322 | 
            +
                    }
         | 
| 293 323 |  | 
| 294 | 
            -
                    if _relevant_changes?( | 
| 295 | 
            -
                      within_preserved_state do
         | 
| 296 | 
            -
                        runner.run_on_changes(*all_changes.values)
         | 
| 297 | 
            -
                      end
         | 
| 298 | 
            -
                    end
         | 
| 324 | 
            +
                    async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
         | 
| 299 325 | 
             
                  end
         | 
| 300 326 | 
             
                end
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                def _reset_all
         | 
| 329 | 
            +
                  reset_groups
         | 
| 330 | 
            +
                  reset_plugins
         | 
| 331 | 
            +
                  reset_scope
         | 
| 332 | 
            +
                end
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                def _setup_interactor
         | 
| 335 | 
            +
                  ::Guard::Interactor.new(options[:no_interactions])
         | 
| 336 | 
            +
                end
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                def _load_guardfile
         | 
| 339 | 
            +
                  _reset_all
         | 
| 340 | 
            +
                  evaluate_guardfile
         | 
| 341 | 
            +
                  setup_scope
         | 
| 342 | 
            +
                  _setup_notifier
         | 
| 343 | 
            +
                end
         | 
| 344 | 
            +
             | 
| 345 | 
            +
                def _prepare_scope(scope)
         | 
| 346 | 
            +
                  plugins = Array(options[:plugin])
         | 
| 347 | 
            +
                  plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
         | 
| 348 | 
            +
             | 
| 349 | 
            +
                  groups = Array(options[:group])
         | 
| 350 | 
            +
                  groups = Array(scope[:groups] || scope[:group]) if groups.empty?
         | 
| 351 | 
            +
             | 
| 352 | 
            +
                  { plugins: plugins, groups: groups }
         | 
| 353 | 
            +
                end
         | 
| 354 | 
            +
             | 
| 355 | 
            +
                def _non_builtin_plugins?
         | 
| 356 | 
            +
                  plugins.map(&:name) != ["reevaluator"]
         | 
| 357 | 
            +
                end
         | 
| 358 | 
            +
             | 
| 359 | 
            +
                def _reset_for_tests
         | 
| 360 | 
            +
                  @options = nil
         | 
| 361 | 
            +
                  @queue = nil
         | 
| 362 | 
            +
                  @runner = nil
         | 
| 363 | 
            +
                  @evaluator = nil
         | 
| 364 | 
            +
                  @watchdirs = nil
         | 
| 365 | 
            +
                  @watchdirs = nil
         | 
| 366 | 
            +
                  @listener = nil
         | 
| 367 | 
            +
                  @interactor = nil
         | 
| 368 | 
            +
                  ::Guard.scope = nil
         | 
| 369 | 
            +
                end
         | 
| 301 370 | 
             
              end
         | 
| 302 371 | 
             
            end
         |