diff --git a/.travis.yml b/.travis.yml index ea7380f..0bc846d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ rvm: branches: only: - master - - hook - - stdin - - jruby_travis_ctime_bug + - guard_dependencies +env: + - GUARD_SLEEP=1 notifications: recipients: - thibaud@thibaud.me diff --git a/CHANGELOG.md b/CHANGELOG.md index e358b89..6849eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ ## Master +### Bugs fixes: + - Pull request [#137](https://github.com/guard/guard/pull/137): Fix interacting with tools like ruby-debug. ([@hron][] & [@netzpirat][]) +- Pull request [#138](https://github.com/guard/guard/pull/138): Fixed comments in example scaffold to reference interactions. ([@rmm5t][] & [@netzpirat][]) + +### New feature: + +- Issue [#97](https://github.com/guard/guard/issues/97): Guard dependencies. Task execution can now be halted if a Guard throws `:task_has_failed` and `Guard::Dsl#group` options include `:halt_on_fail => true`. ([@rymai][]) +- Issue [#121](https://github.com/guard/guard/issues/121): `Guard.guards` and `Guard.groups` are now smart accessors. Filters can be passed to find a specific Guard/group or several Guards/groups that match (see YARDoc). ([@rymai][] & [@ches][]) +- New `Guard::Group` class to store groups defined in Guardfile (with `Guard::Dsl#group`). ([@rymai][]) + +### Improvement: + +- Full YARD documentation. ([@netzpirat][] & a little of [@rymai][]) ## 0.7.0 - September 14, 2011 @@ -9,7 +22,7 @@ ### Major Changes - Posix Signals handlers (`Ctrl-C`, `Ctrl-\` and `Ctrl-Z`) are no more supported and replaced by `$stdin.gets`. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][]) -- JRuby & Rubinius support (beta). ([@thibaudgg][] and [@netzpirat][]) +- JRuby & Rubinius support (beta). ([@thibaudgg][] & [@netzpirat][]) ### New feature: @@ -272,6 +285,7 @@ [@niklas]: https://github.com/niklas [@oliamb]: https://github.com/oliamb [@pcreux]: https://github.com/pcreux +[@rmm5t]: https://github.com/rmm5t [@rymai]: https://github.com/rymai [@stereobooster]: https://github.com/stereobooster [@stouset]: https://github.com/stouset diff --git a/lib/guard.rb b/lib/guard.rb index 740c115..7d12926 100644 --- a/lib/guard.rb +++ b/lib/guard.rb @@ -6,6 +6,7 @@ module Guard autoload :UI, 'guard/ui' autoload :Dsl, 'guard/dsl' autoload :DslDescriber, 'guard/dsl_describer' + autoload :Group, 'guard/group' autoload :Interactor, 'guard/interactor' autoload :Listener, 'guard/listener' autoload :Watcher, 'guard/watcher' @@ -13,11 +14,10 @@ module Guard autoload :Hook, 'guard/hook' class << self - attr_accessor :options, :guards, :groups, :interactor, :listener + attr_accessor :options, :interactor, :listener # Initialize the Guard singleton. # - # @param [Hash] options the Guard options. # @option options [Boolean] clear if auto clear the UI should be done # @option options [Boolean] notify if system notifications should be shown # @option options [Boolean] debug if debug output should be shown @@ -28,7 +28,7 @@ module Guard def setup(options = {}) @options = options @guards = [] - @groups = [:default] + @groups = [Group.new(:default)] @interactor = Interactor.new @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options) @@ -41,10 +41,57 @@ module Guard self end + # Smart accessor for retrieving a specific guard or several guards at once. + # + # @param [String, Symbol] filter return the guard with the given name, or nil if not found + # @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found + # @param [Hash] filter returns all guards matching the given Hash. + # Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found + # @param [NilClass] filter returns all guards + # + # @see Guard.groups + # + def guards(filter = nil) + case filter + when String, Symbol + @guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') } + when Regexp + @guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter } + when Hash + filter.inject(@guards) do |matches, (k, v)| + if k.to_sym == :name + matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') } + else + matches.find_all { |guard| guard.send(k).to_sym == v.to_sym } + end + end + else + @guards + end + end + + # Smart accessor for retrieving a specific group or several groups at once. + # + # @param [NilClass] filter returns all groups + # @param [String, Symbol] filter return the group with the given name, or nil if not found + # @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found + # + # @see Guard.guards + # + def groups(filter = nil) + case filter + when String, Symbol + @groups.find { |group| group.name == filter.to_sym } + when Regexp + @groups.find_all { |group| group.name.to_s =~ filter } + else + @groups + end + end + # Start Guard by evaluate the `Guardfile`, initialize the declared Guards # and start the available file change listener. # - # @param [Hash] options the Guard options. # @option options [Boolean] clear if auto clear the UI should be done # @option options [Boolean] notify if system notifications should be shown # @option options [Boolean] debug if debug output should be shown @@ -63,7 +110,8 @@ module Guard end UI.info "Guard is now watching at '#{ listener.directory }'" - guards.each { |guard| supervised_task(guard, :start) } + + execute_supervised_task_for_all_guards(:start) interactor.start listener.start @@ -74,7 +122,7 @@ module Guard def stop UI.info 'Bye bye...', :reset => true listener.stop - guards.each { |guard| supervised_task(guard, :stop) } + execute_supervised_task_for_all_guards(:stop) abort end @@ -82,7 +130,7 @@ module Guard # def reload run do - guards.each { |guard| supervised_task(guard, :reload) } + execute_supervised_task_for_all_guards(:reload) end end @@ -90,7 +138,7 @@ module Guard # def run_all run do - guards.each { |guard| supervised_task(guard, :run_all) } + execute_supervised_task_for_all_guards(:run_all) end end @@ -107,29 +155,11 @@ module Guard end end - # Trigger `run_on_change` on all Guards currently enabled and + # Trigger `run_on_change` on all Guards currently enabled. # - def run_on_change(files) + def run_on_change(paths) run do - guards.each do |guard| - paths = Watcher.match_files(guard, files) - if @watch_all_modifications - unless paths.empty? - UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}" - supervised_task(guard, :run_on_change, paths.select {|f| !f.start_with?('!') }) - deletions = paths.collect { |f| f.slice(1..-1) if f.start_with?('!') }.compact - unless deletions.empty? - UI.debug "#{guard.class.name}#run_on_deletion with #{deletions.inspect}" - supervised_task(guard, :run_on_deletion, deletions) - end - end - else - unless paths.empty? - UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}" - supervised_task(guard, :run_on_change, paths) - end - end - end + execute_supervised_task_for_all_guards(:run_on_change, paths) end end @@ -150,6 +180,41 @@ module Guard listener.unlock end + # Loop through all groups and execute the given task for each Guard in it, + # but halt the task execution for the all Guards within a group if one Guard + # throws `:task_has_failed` and the group has its `:halt_on_fail` option to `true`. + # + # @param [Symbol] task the task to run + # @param [Array] files the list of files to pass to the task + # + def execute_supervised_task_for_all_guards(task, files = nil) + groups.each do |group| + catch group.options[:halt_on_fail] == true ? :task_has_failed : :no_catch do + guards(:group => group.name).each do |guard| + if task == :run_on_change + paths = Watcher.match_files(guard, files) + unless paths.empty? + if @watch_all_modifications + UI.debug "#{guard.class.name}##{task} with #{paths.inspect}" + supervised_task(guard, task, paths.select {|f| !f.start_with?('!') }) + deletions = paths.collect { |f| f.slice(1..-1) if f.start_with?('!') }.compact + unless deletions.empty? + UI.debug "#{guard.class.name}#run_on_deletion with #{deletions.inspect}" + supervised_task(guard, :run_on_deletion, deletions) + end + else + UI.debug "#{guard.class.name}##{task} with #{paths.inspect}" + supervised_task(guard, task, paths) + end + end + else + supervised_task(guard, task) + end + end + end + end + end + # Let a Guard execute its task, but fire it # if his work leads to a system failure. # @@ -179,7 +244,7 @@ module Guard # @param [String] name the Guard name # @param [Array] watchers the list of declared watchers # @param [Array] callbacks the list of callbacks - # @param [Hash] options the Guard options + # @param [Hash] options the Guard options (see the given Guard documentation) # def add_guard(name, watchers = [], callbacks = [], options = {}) if name.to_sym == :ego @@ -194,9 +259,17 @@ module Guard # Add a Guard group. # # @param [String] name the group name + # @option options [Boolean] halt_on_fail if a task execution + # should be halted for all Guards in this group if one Guard throws `:task_has_failed` + # @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present) # - def add_group(name) - @groups << name.to_sym unless name.nil? + def add_group(name, options = {}) + group = groups(name) + if group.nil? + group = Group.new(name, options) + @groups << group + end + group end # Tries to load the Guard main class. diff --git a/lib/guard/dsl.rb b/lib/guard/dsl.rb index adc2099..ecb6dbc 100644 --- a/lib/guard/dsl.rb +++ b/lib/guard/dsl.rb @@ -81,7 +81,6 @@ module Guard # Evaluate the DSL methods in the `Guardfile`. # - # @param [Hash] options the Guard options # @option options [Array] groups the groups to evaluate # @option options [String] guardfile the path to a valid Guardfile # @option options [String] guardfile_contents a string representing the content of a valid Guardfile @@ -102,6 +101,7 @@ module Guard # def reevaluate_guardfile ::Guard.guards.clear + ::Guard.groups.clear @@options.delete(:guardfile_contents) Dsl.evaluate_guardfile(@@options) msg = 'Guardfile has been re-evaluated.' @@ -265,16 +265,19 @@ module Guard # end # # @param [Symbol, String] name the group's name called from the CLI + # @param [Hash] options the options accepted by the group # @yield a block where you can declare several guards # + # @see Guard.add_group # @see Dsl#guard # @see Guard::DslDescriber # - def group(name) + def group(name, options = {}) @groups = @@options[:group] || [] name = name.to_sym if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name)) + ::Guard.add_group(name.to_s.downcase, options) @current_group = name yield if block_given? @@ -299,6 +302,8 @@ module Guard # @param [Hash] options the options accepted by the Guard # @yield a block where you can declare several watch patterns and actions # + # @see Guard.add_guard + # @see Dsl#group # @see Dsl#watch # @see Guard::DslDescriber # @@ -309,7 +314,7 @@ module Guard yield if block_given? options.update(:group => (@current_group || :default)) - ::Guard.add_guard(name.to_s.downcase.to_sym, @watchers, @callbacks, options) + ::Guard.add_guard(name.to_s.downcase, @watchers, @callbacks, options) end # Define a pattern to be watched in order to run actions on file modification. @@ -327,6 +332,9 @@ module Guard # @yieldparam [MatchData] m matches of the pattern # @yieldreturn a directory, a filename, an array of directories / filenames, or nothing (can be an arbitrary command) # + # @see Guard::Watcher + # @see Dsl#guard + # def watch(pattern, &action) @watchers << ::Guard::Watcher.new(pattern, action) end @@ -337,6 +345,8 @@ module Guard # @param [Array] args the callback arguments # @yield a block with listeners # + # @see Guard::Hook + # def callback(*args, &listener) listener, events = args.size > 1 ? args : [listener, args[0]] @callbacks << { :events => events, :listener => listener } @@ -349,6 +359,8 @@ module Guard # # @param [Array] paths the list of paths to ignore # + # @see Guard::Listener + # def ignore_paths(*paths) UI.info "Ignoring paths: #{ paths.join(', ') }" ::Guard.listener.ignore_paths.push(*paths) diff --git a/lib/guard/dsl_describer.rb b/lib/guard/dsl_describer.rb index b049fc6..b5d7a77 100644 --- a/lib/guard/dsl_describer.rb +++ b/lib/guard/dsl_describer.rb @@ -31,7 +31,7 @@ module Guard # @param [String] name the group's name called from the CLI # @yield a block where you can declare several guards # - # @see Guard::Dsl + # @see Guard::Dsl#group # def group(name) @@guardfile_structure << { :group => name.to_sym, :guards => [] } @@ -42,13 +42,13 @@ module Guard @group = false end - # Declare a guard. + # Declares a Guard. # # @param [String] name the Guard name # @param [Hash] options the options accepted by the Guard # @yield a block where you can declare several watch patterns and actions # - # @see Guard::Dsl + # @see Guard::Dsl#guard # def guard(name, options = {}) node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first) diff --git a/lib/guard/group.rb b/lib/guard/group.rb new file mode 100644 index 0000000..c8239b1 --- /dev/null +++ b/lib/guard/group.rb @@ -0,0 +1,22 @@ +module Guard + + # A group of Guards. + # + class Group + + attr_accessor :name, :options + + # Initialize a Group. + # + # @param [String] name the name of the group + # @option options [Boolean] halt_on_fail if a task execution + # should be halted for all Guards in this group if one Guard throws `:task_has_failed` + # + def initialize(name, options = {}) + @name = name.to_sym + @options = options + end + + end + +end diff --git a/lib/guard/guard.rb b/lib/guard/guard.rb index 3774187..cfe92f4 100644 --- a/lib/guard/guard.rb +++ b/lib/guard/guard.rb @@ -16,10 +16,10 @@ module Guard # Initialize a Guard. # # @param [Array] watchers the Guard file watchers - # @param [Hash] options the custom Guard options. + # @param [Hash] options the custom Guard options # def initialize(watchers = [], options = {}) - @group = options.delete(:group) || :default + @group = options[:group] ? options.delete(:group).to_sym : :default @watchers, @options = watchers, options end @@ -89,4 +89,5 @@ module Guard end end + end diff --git a/lib/guard/interactor.rb b/lib/guard/interactor.rb index c6c56b2..c69dc5d 100644 --- a/lib/guard/interactor.rb +++ b/lib/guard/interactor.rb @@ -37,6 +37,7 @@ module Guard when 'stop', 'quit', 'exit', 's', 'q', 'e' ::Guard.stop when 'reload', 'r', 'z' + ::Guard::Dsl.reevaluate_guardfile ::Guard.reload when 'pause', 'p' ::Guard.pause diff --git a/lib/guard/listener.rb b/lib/guard/listener.rb index 83561e7..87eedf1 100644 --- a/lib/guard/listener.rb +++ b/lib/guard/listener.rb @@ -43,7 +43,6 @@ module Guard # Initialize the listener. # # @param [String] directory the root directory to listen to - # @param [Hash] options the listener options # @option options [Boolean] relativize_paths use only relative paths # @option options [Array] ignore_paths the paths to ignore by the listener # @@ -210,7 +209,6 @@ module Guard # Gets a list of files that are in the modified directories. # # @param [Array] dirs the list of directories - # @param [Hash] options the options # @option options [Symbol] all whether to include all files # def potentially_modified_files(dirs, options = {}) diff --git a/lib/guard/notifier.rb b/lib/guard/notifier.rb index 8f84ed1..1ee20e5 100644 --- a/lib/guard/notifier.rb +++ b/lib/guard/notifier.rb @@ -43,7 +43,6 @@ module Guard # @see .image_path # # @param [String] the message to show - # @param [Hash] options the notification options # @option options [Symbol, String] image the image symbol or path to an image # @option options [String] title the notification title # @@ -80,7 +79,7 @@ module Guard # @param [Symbol, String] the image to user # @param [Hash] options the growl options # - def self.notify_mac(title, message, image, options) + def self.notify_mac(title, message, image, options = {}) require_growl # need for guard-rspec formatter that is called out of guard scope default_options = { :title => title, :icon => image_path(image), :name => APPLICATION_NAME } @@ -104,7 +103,7 @@ module Guard # @param [Symbol, String] the image to user # @param [Hash] options the libnotify options # - def self.notify_linux(title, message, image, options) + def self.notify_linux(title, message, image, options = {}) require_libnotify # need for guard-rspec formatter that is called out of guard scope default_options = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true } Libnotify.show default_options.merge(options) if enabled? @@ -117,7 +116,7 @@ module Guard # @param [Symbol, String] the image to user # @param [Hash] options the notifu options # - def self.notify_windows(title, message, image, options) + def self.notify_windows(title, message, image, options = {}) require_rbnotifu # need for guard-rspec formatter that is called out of guard scope default_options = { :message => message, :title => title, :type => image_level(image), :time => 3 } Notifu.show default_options.merge(options) if enabled? diff --git a/lib/guard/ui.rb b/lib/guard/ui.rb index 51b2531..80ff7d4 100644 --- a/lib/guard/ui.rb +++ b/lib/guard/ui.rb @@ -10,7 +10,6 @@ module Guard # Show an info message. # # @param [String] message the message to show - # @param [Hash] options the options # @option options [Boolean] reset whether to clean the output before # def info(message, options = { }) @@ -23,7 +22,6 @@ module Guard # Show a red error message that is prefixed with ERROR. # # @param [String] message the message to show - # @param [Hash] options the options # @option options [Boolean] reset whether to clean the output before # def error(message, options = { }) @@ -36,7 +34,6 @@ module Guard # Show a red deprecation message that is prefixed with DEPRECATION. # # @param [String] message the message to show - # @param [Hash] options the options # @option options [Boolean] reset whether to clean the output before # def deprecation(message, options = { }) @@ -49,7 +46,6 @@ module Guard # Show a debug message that is prefixed with DEBUG and a timestamp. # # @param [String] message the message to show - # @param [Hash] options the options # @option options [Boolean] reset whether to clean the output before # def debug(message, options = { }) diff --git a/man/guard b/man/guard index 3260c45..00640fb 100644 --- a/man/guard +++ b/man/guard @@ -17,6 +17,91 @@ . .nf +NAME +NAME +NAME +NAME +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • guard
  • +
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +\fBguard\fR +. +.P +\fI!DOCTYPE html\fR +. +.P +. +.P +. +.IP "" 4 +. +.nf + +NAME +NAME +NAME +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • guard
  • +
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +\fBguard\fR +. +.P +\fI!DOCTYPE html\fR +. +.P +. +.P +. +.IP "" 4 +. +.nf + NAME NAME . @@ -48,7 +133,7 @@ \fBguard\fR . .P -\fI!DOCTYPE html\fR + +
  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +\fI<<<<<< HEAD\fR +. +.P +

    +

  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +======= +. +.P +. +.IP "" 4 +. +.nf + +
  • +
  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • +
  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • +
  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +. +.IP "" 4 +. +.nf +
  • September 2011
  • guard
  • diff --git a/man/guard.1 b/man/guard.1 index e54f8f3..715327a 100644 --- a/man/guard.1 +++ b/man/guard.1 @@ -41,7 +41,7 @@ Tells Guard to watch PATH instead of \fB\./\fR\. .P \fB\-G\fR, \fB\-\-guardfile\fR \fIFILE\fR Tells Guard to use FILE as its Guardfile instead of \fB\./Guardfile\fR or \fB~/\.Guardfile\fR\. . -.SS "init GUARD" +.SS "init [GUARD]" If no Guardfile is present in the current directory, creates an empty Guardfile\. . .P diff --git a/man/guard.1.html b/man/guard.1.html index f40a902..a4c9ce0 100644 --- a/man/guard.1.html +++ b/man/guard.1.html @@ -112,7 +112,7 @@

    -G, --guardfile FILE Tells Guard to use FILE as its Guardfile instead of ./Guardfile or ~/.Guardfile.

    -

    init GUARD

    +

    init [GUARD]

    If no Guardfile is present in the current directory, creates an empty Guardfile.

    diff --git a/man/guard.html b/man/guard.html index 604ffa2..94362f6 100644 --- a/man/guard.html +++ b/man/guard.html @@ -56,6 +56,8 @@ NAME NAME NAME + NAME + NAME
      @@ -71,10 +73,10 @@

      !DOCTYPE html - + guard - +

      + + + + + + + + + +

      +

      + + + + +