changed watch deletions option to watch_all_modifiactions, Merge branch 'master' of git://github.com/guard/guard
Conflicts: lib/guard.rb lib/guard/cli.rb lib/guard/listener.rb
This commit is contained in:
commit
432d4a0991
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,9 +1,11 @@
|
|||||||
pkg/*
|
pkg/*
|
||||||
|
doc/*
|
||||||
*.gem
|
*.gem
|
||||||
*.rbc
|
*.rbc
|
||||||
.*.swp
|
.*.swp
|
||||||
*.bak
|
*.bak
|
||||||
.bundle
|
.bundle
|
||||||
|
.yardoc
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
|
||||||
## MAC OS
|
## MAC OS
|
||||||
@ -12,4 +14,4 @@ Gemfile.lock
|
|||||||
.com.apple.timemachine.supported
|
.com.apple.timemachine.supported
|
||||||
.fseventsd
|
.fseventsd
|
||||||
Desktop DB
|
Desktop DB
|
||||||
Desktop DF
|
Desktop DF
|
||||||
|
11
.yardopts
Normal file
11
.yardopts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
--title 'Guard Documentation'
|
||||||
|
--readme README.md
|
||||||
|
--markup markdown
|
||||||
|
--markup-provider kramdown
|
||||||
|
--private
|
||||||
|
--protected
|
||||||
|
--output-dir ./doc
|
||||||
|
lib/**/*.rb
|
||||||
|
-
|
||||||
|
CHANGELOG.md
|
||||||
|
LICENSE
|
6
Gemfile
6
Gemfile
@ -13,12 +13,10 @@ require 'rbconfig'
|
|||||||
if RbConfig::CONFIG['target_os'] =~ /darwin/i
|
if RbConfig::CONFIG['target_os'] =~ /darwin/i
|
||||||
gem 'rb-fsevent', '>= 0.4.0', :require => false
|
gem 'rb-fsevent', '>= 0.4.0', :require => false
|
||||||
gem 'growl', '~> 1.0.3', :require => false
|
gem 'growl', '~> 1.0.3', :require => false
|
||||||
end
|
elsif RbConfig::CONFIG['target_os'] =~ /linux/i
|
||||||
if RbConfig::CONFIG['target_os'] =~ /linux/i
|
|
||||||
gem 'rb-inotify', '>= 0.8.5', :require => false
|
gem 'rb-inotify', '>= 0.8.5', :require => false
|
||||||
gem 'libnotify', '~> 0.1.3', :require => false
|
gem 'libnotify', '~> 0.1.3', :require => false
|
||||||
end
|
elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||||
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
|
||||||
gem 'win32console', :require => false
|
gem 'win32console', :require => false
|
||||||
gem 'rb-fchange', '>= 0.0.2', :require => false
|
gem 'rb-fchange', '>= 0.0.2', :require => false
|
||||||
gem 'rb-notifu', '>= 0.0.4', :require => false
|
gem 'rb-notifu', '>= 0.0.4', :require => false
|
||||||
|
@ -14,11 +14,13 @@ Gem::Specification.new do |s|
|
|||||||
s.required_rubygems_version = '>= 1.3.6'
|
s.required_rubygems_version = '>= 1.3.6'
|
||||||
s.rubyforge_project = 'guard'
|
s.rubyforge_project = 'guard'
|
||||||
|
|
||||||
|
s.add_dependency 'thor', '~> 0.14.6'
|
||||||
|
|
||||||
s.add_development_dependency 'bundler'
|
s.add_development_dependency 'bundler'
|
||||||
s.add_development_dependency 'rspec', '~> 2.6.0'
|
s.add_development_dependency 'rspec', '~> 2.6.0'
|
||||||
s.add_development_dependency 'guard-rspec', '~> 0.3.1'
|
s.add_development_dependency 'guard-rspec', '~> 0.3.1'
|
||||||
|
s.add_development_dependency 'yard', '~> 0.7.2'
|
||||||
s.add_dependency 'thor', '~> 0.14.6'
|
s.add_development_dependency 'kramdown', '~> 0.13.3'
|
||||||
|
|
||||||
s.files = Dir.glob('{bin,images,lib}/**/*') + %w[CHANGELOG.md LICENSE man/guard.1 man/guard.1.html README.md]
|
s.files = Dir.glob('{bin,images,lib}/**/*') + %w[CHANGELOG.md LICENSE man/guard.1 man/guard.1.html README.md]
|
||||||
s.executable = 'guard'
|
s.executable = 'guard'
|
||||||
|
129
lib/guard.rb
129
lib/guard.rb
@ -1,3 +1,6 @@
|
|||||||
|
# Guard is the main module for all Guard related modules and classes.
|
||||||
|
# Also other Guard implementation should use this namespace.
|
||||||
|
#
|
||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
autoload :UI, 'guard/ui'
|
autoload :UI, 'guard/ui'
|
||||||
@ -12,7 +15,16 @@ module Guard
|
|||||||
class << self
|
class << self
|
||||||
attr_accessor :options, :guards, :groups, :interactor, :listener
|
attr_accessor :options, :guards, :groups, :interactor, :listener
|
||||||
|
|
||||||
# initialize this singleton
|
# 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
|
||||||
|
# @option options [Array<String>] group the list of groups to start
|
||||||
|
# @option options [String] watchdir the director to watch
|
||||||
|
# @option options [String] guardfile the path to the Guardfile
|
||||||
|
# @optuin options [Boolean] watch_all_modifications watches all file modifications if true
|
||||||
def setup(options = {})
|
def setup(options = {})
|
||||||
@options = options
|
@options = options
|
||||||
@guards = []
|
@guards = []
|
||||||
@ -20,8 +32,8 @@ module Guard
|
|||||||
@interactor = Interactor.new
|
@interactor = Interactor.new
|
||||||
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd,options)
|
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd,options)
|
||||||
|
|
||||||
@watch_deletions = options[:deletions]
|
@watch_all_modifications = options[:watch_all_modifications]
|
||||||
@options[:notify] && ENV["GUARD_NOTIFY"] != 'false' ? Notifier.turn_on : Notifier.turn_off
|
@options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
|
||||||
|
|
||||||
UI.clear if @options[:clear]
|
UI.clear if @options[:clear]
|
||||||
debug_command_execution if @options[:debug]
|
debug_command_execution if @options[:debug]
|
||||||
@ -29,6 +41,17 @@ module Guard
|
|||||||
self
|
self
|
||||||
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
|
||||||
|
# @option options [Array<String>] group the list of groups to start
|
||||||
|
# @option options [String] watchdir the director to watch
|
||||||
|
# @option options [String] guardfile the path to the Guardfile
|
||||||
|
#
|
||||||
def start(options = {})
|
def start(options = {})
|
||||||
setup(options)
|
setup(options)
|
||||||
|
|
||||||
@ -39,48 +62,58 @@ module Guard
|
|||||||
listener.changed_files += files if Watcher.match_files?(guards, files)
|
listener.changed_files += files if Watcher.match_files?(guards, files)
|
||||||
end
|
end
|
||||||
|
|
||||||
UI.info "Guard is now watching at '#{listener.directory}'"
|
UI.info "Guard is now watching at '#{ listener.directory }'"
|
||||||
guards.each { |guard| supervised_task(guard, :start) }
|
guards.each { |guard| supervised_task(guard, :start) }
|
||||||
|
|
||||||
interactor.start
|
interactor.start
|
||||||
listener.start
|
listener.start
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stop Guard listening to file changes
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
UI.info "Bye bye...", :reset => true
|
UI.info 'Bye bye...', :reset => true
|
||||||
listener.stop
|
listener.stop
|
||||||
guards.each { |guard| supervised_task(guard, :stop) }
|
guards.each { |guard| supervised_task(guard, :stop) }
|
||||||
abort
|
abort
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reload all Guards currently enabled.
|
||||||
|
#
|
||||||
def reload
|
def reload
|
||||||
run do
|
run do
|
||||||
guards.each { |guard| supervised_task(guard, :reload) }
|
guards.each { |guard| supervised_task(guard, :reload) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Trigger `run_all` on all Guards currently enabled.
|
||||||
|
#
|
||||||
def run_all
|
def run_all
|
||||||
run do
|
run do
|
||||||
guards.each { |guard| supervised_task(guard, :run_all) }
|
guards.each { |guard| supervised_task(guard, :run_all) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Pause Guard listening to file changes.
|
||||||
|
#
|
||||||
def pause
|
def pause
|
||||||
if listener.locked
|
if listener.locked
|
||||||
UI.info "Un-paused files modification listening", :reset => true
|
UI.info 'Un-paused files modification listening', :reset => true
|
||||||
listener.clear_changed_files
|
listener.clear_changed_files
|
||||||
listener.unlock
|
listener.unlock
|
||||||
else
|
else
|
||||||
UI.info "Paused files modification listening", :reset => true
|
UI.info 'Paused files modification listening', :reset => true
|
||||||
listener.lock
|
listener.lock
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Trigger `run_on_change` on all Guards currently enabled and
|
||||||
|
#
|
||||||
def run_on_change(files)
|
def run_on_change(files)
|
||||||
run do
|
run do
|
||||||
guards.each do |guard|
|
guards.each do |guard|
|
||||||
paths = Watcher.match_files(guard, files)
|
paths = Watcher.match_files(guard, files)
|
||||||
if @watch_deletions
|
if @watch_all_modifications
|
||||||
unless paths.empty?
|
unless paths.empty?
|
||||||
UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}"
|
UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}"
|
||||||
supervised_task(guard, :run_on_change, paths.select {|f| !f.start_with?('!') })
|
supervised_task(guard, :run_on_change, paths.select {|f| !f.start_with?('!') })
|
||||||
@ -100,6 +133,11 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Run a block where the listener and the interactor is
|
||||||
|
# blocked.
|
||||||
|
#
|
||||||
|
# @yield the block to run
|
||||||
|
#
|
||||||
def run
|
def run
|
||||||
listener.lock
|
listener.lock
|
||||||
interactor.lock
|
interactor.lock
|
||||||
@ -112,24 +150,40 @@ module Guard
|
|||||||
listener.unlock
|
listener.unlock
|
||||||
end
|
end
|
||||||
|
|
||||||
# Let a guard execute its task but
|
# Let a Guard execute its task, but fire it
|
||||||
# fire it if his work leads to a system failure
|
# if his work leads to a system failure.
|
||||||
|
#
|
||||||
|
# @param [Guard::Guard] the guard to execute
|
||||||
|
# @param [Symbol] task_to_supervise the task to run
|
||||||
|
# @param [Array] args the arguments for the task
|
||||||
|
# @return [Boolean, Exception] the result of the Guard
|
||||||
|
#
|
||||||
def supervised_task(guard, task_to_supervise, *args)
|
def supervised_task(guard, task_to_supervise, *args)
|
||||||
guard.hook("#{task_to_supervise}_begin", *args)
|
guard.hook("#{ task_to_supervise }_begin", *args)
|
||||||
result = guard.send(task_to_supervise, *args)
|
result = guard.send(task_to_supervise, *args)
|
||||||
guard.hook("#{task_to_supervise}_end", result)
|
guard.hook("#{ task_to_supervise }_end", result)
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
||||||
rescue Exception => ex
|
rescue Exception => ex
|
||||||
UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" +
|
UI.error("#{ guard.class.name } failed to achieve its <#{ task_to_supervise.to_s }>, exception was:" +
|
||||||
"\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
|
"\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
|
||||||
guards.delete guard
|
guards.delete guard
|
||||||
UI.info("\n#{guard.class.name} has just been fired")
|
UI.info("\n#{ guard.class.name } has just been fired")
|
||||||
return ex
|
|
||||||
|
ex
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add a Guard to use.
|
||||||
|
#
|
||||||
|
# @param [String] name the Guard name
|
||||||
|
# @param [Array<Watcher>] watchers the list of declared watchers
|
||||||
|
# @param [Array<Hash>] callbacks the list of callbacks
|
||||||
|
# @param [Hash] the Guard options
|
||||||
|
#
|
||||||
def add_guard(name, watchers = [], callbacks = [], options = {})
|
def add_guard(name, watchers = [], callbacks = [], options = {})
|
||||||
if name.to_sym == :ego
|
if name.to_sym == :ego
|
||||||
UI.deprecation("Guard::Ego is now part of Guard. You can remove it from your Guardfile.")
|
UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
|
||||||
else
|
else
|
||||||
guard_class = get_guard_class(name)
|
guard_class = get_guard_class(name)
|
||||||
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
|
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
|
||||||
@ -137,42 +191,58 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add a Guard group.
|
||||||
|
#
|
||||||
|
# @param [String] name the group name
|
||||||
|
#
|
||||||
def add_group(name)
|
def add_group(name)
|
||||||
@groups << name.to_sym unless name.nil?
|
@groups << name.to_sym unless name.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tries to load the Guard main class.
|
||||||
|
#
|
||||||
|
# @param [String] name the name of the Guard
|
||||||
|
# @return [Class, nil] the loaded class
|
||||||
|
#
|
||||||
def get_guard_class(name)
|
def get_guard_class(name)
|
||||||
name = name.to_s
|
name = name.to_s
|
||||||
try_require = false
|
try_require = false
|
||||||
const_name = name.downcase.gsub('-', '')
|
const_name = name.downcase.gsub('-', '')
|
||||||
begin
|
begin
|
||||||
require "guard/#{name.downcase}" if try_require
|
require "guard/#{ name.downcase }" if try_require
|
||||||
self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
|
self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
|
||||||
rescue TypeError
|
rescue TypeError
|
||||||
unless try_require
|
unless try_require
|
||||||
try_require = true
|
try_require = true
|
||||||
retry
|
retry
|
||||||
else
|
else
|
||||||
UI.error "Could not find class Guard::#{const_name.capitalize}"
|
UI.error "Could not find class Guard::#{ const_name.capitalize }"
|
||||||
end
|
end
|
||||||
rescue LoadError => loadError
|
rescue LoadError => loadError
|
||||||
UI.error "Could not load 'guard/#{name.downcase}' or find class Guard::#{const_name.capitalize}"
|
UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
|
||||||
UI.error loadError.to_s
|
UI.error loadError.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Locate a path to a Guard gem.
|
||||||
|
#
|
||||||
|
# @param [String] name the name of the Guard without the prefix `guard-`
|
||||||
|
# @return [String] the full path to the Guard gem
|
||||||
|
#
|
||||||
def locate_guard(name)
|
def locate_guard(name)
|
||||||
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
||||||
Gem::Specification.find_by_name("guard-#{name}").full_gem_path
|
Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
|
||||||
else
|
else
|
||||||
Gem.source_index.find_name("guard-#{name}").last.full_gem_path
|
Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
UI.error "Could not find 'guard-#{name}' gem path."
|
UI.error "Could not find 'guard-#{ name }' gem path."
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
|
||||||
# Returns a list of guard Gem names installed locally.
|
# Returns a list of guard Gem names installed locally.
|
||||||
|
#
|
||||||
|
# @return [Array<String>] a list of guard gem names
|
||||||
|
#
|
||||||
def guard_gem_names
|
def guard_gem_names
|
||||||
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
||||||
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
|
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
|
||||||
@ -181,16 +251,19 @@ module Guard
|
|||||||
end.map { |x| x.name.sub /^guard-/, '' }
|
end.map { |x| x.name.sub /^guard-/, '' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Adds a command logger in debug mode. This wraps common command
|
||||||
|
# execution functions and logs the executed command before execution.
|
||||||
|
#
|
||||||
def debug_command_execution
|
def debug_command_execution
|
||||||
Kernel.send(:alias_method, :original_system, :system)
|
Kernel.send(:alias_method, :original_system, :system)
|
||||||
Kernel.send(:define_method, :system) do |command, *args|
|
Kernel.send(:define_method, :system) do |command, *args|
|
||||||
::Guard::UI.debug "Command execution: #{command} #{args.join(' ')}"
|
::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
|
||||||
original_system command, *args
|
original_system command, *args
|
||||||
end
|
end
|
||||||
|
|
||||||
Kernel.send(:alias_method, :original_backtick, :"`")
|
Kernel.send(:alias_method, :original_backtick, :'`')
|
||||||
Kernel.send(:define_method, :"`") do |command|
|
Kernel.send(:define_method, :'`') do |command|
|
||||||
::Guard::UI.debug "Command execution: #{command}"
|
::Guard::UI.debug "Command execution: #{ command }"
|
||||||
original_backtick command
|
original_backtick command
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
186
lib/guard/cli.rb
186
lib/guard/cli.rb
@ -2,90 +2,178 @@ require 'thor'
|
|||||||
require 'guard/version'
|
require 'guard/version'
|
||||||
|
|
||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Guard command line interface managed by [Thor](https://github.com/wycats/thor).
|
||||||
|
# This is the main interface to Guard that is called by the Guard binary at `bin/guard`.
|
||||||
|
#
|
||||||
class CLI < Thor
|
class CLI < Thor
|
||||||
|
|
||||||
default_task :start
|
default_task :start
|
||||||
|
|
||||||
method_option :clear, :type => :boolean, :default => false, :aliases => '-c', :banner => "Auto clear shell before each change/run_all/reload"
|
desc 'start', 'Starts Guard'
|
||||||
method_option :notify, :type => :boolean, :default => true, :aliases => '-n', :banner => "Notifications feature (growl/libnotify)"
|
|
||||||
method_option :debug, :type => :boolean, :default => false, :aliases => '-d', :banner => "Print debug messages"
|
|
||||||
method_option :group, :type => :array, :default => [], :aliases => '-g', :banner => "Run only the passed groups"
|
|
||||||
method_option :watchdir, :type => :string, :aliases => '-w', :banner => "Specify the directory to watch"
|
|
||||||
method_option :guardfile, :type => :string, :aliases => '-G', :banner => "Specify a Guardfile"
|
|
||||||
method_option :watch_moves_deletions, :type => :boolean, :default => false, :aliases => '-D', :banner => "Watch for moved or deleted files"
|
|
||||||
|
|
||||||
desc "start", "Starts Guard"
|
method_option :clear,
|
||||||
|
:type => :boolean,
|
||||||
|
:default => false,
|
||||||
|
:aliases => '-c',
|
||||||
|
:banner => 'Auto clear shell before each change/run_all/reload'
|
||||||
|
|
||||||
|
method_option :notify,
|
||||||
|
:type => :boolean,
|
||||||
|
:default => true,
|
||||||
|
:aliases => '-n',
|
||||||
|
:banner => 'Notifications feature (growl/libnotify)'
|
||||||
|
|
||||||
|
method_option :debug,
|
||||||
|
:type => :boolean,
|
||||||
|
:default => false,
|
||||||
|
:aliases => '-d',
|
||||||
|
:banner => 'Print debug messages'
|
||||||
|
|
||||||
|
method_option :group,
|
||||||
|
:type => :array,
|
||||||
|
:default => [],
|
||||||
|
:aliases => '-g',
|
||||||
|
:banner => 'Run only the passed groups'
|
||||||
|
|
||||||
|
method_option :watchdir,
|
||||||
|
:type => :string,
|
||||||
|
:aliases => '-w',
|
||||||
|
:banner => 'Specify the directory to watch'
|
||||||
|
|
||||||
|
method_option :guardfile,
|
||||||
|
:type => :string,
|
||||||
|
:aliases => '-G',
|
||||||
|
:banner => 'Specify a Guardfile'
|
||||||
|
|
||||||
|
method_option :watch_all_modifications,
|
||||||
|
:type => :boolean,
|
||||||
|
:default => false,
|
||||||
|
:aliases => '-A',
|
||||||
|
:banner => "Watch for all file modifications including moves and deletions"
|
||||||
|
|
||||||
|
# Start Guard by initialize the defined Guards and watch the file system.
|
||||||
|
# This is the default task, so calling `guard` is the same as calling `guard start`.
|
||||||
|
#
|
||||||
|
# @see Guard.start
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
::Guard.start(options)
|
::Guard.start(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "list", "Lists guards that can be used with init"
|
desc 'list', 'Lists guards that can be used with init'
|
||||||
|
|
||||||
|
# List the Guards that are available for use in your system and marks
|
||||||
|
# those that are currently used in your `Guardfile`.
|
||||||
|
#
|
||||||
|
# @example guard list output
|
||||||
|
#
|
||||||
|
# Available guards:
|
||||||
|
# bundler *
|
||||||
|
# livereload
|
||||||
|
# ronn
|
||||||
|
# rspec *
|
||||||
|
# spork
|
||||||
|
#
|
||||||
|
# See also https://github.com/guard/guard/wiki/List-of-available-Guards
|
||||||
|
# * denotes ones already in your Guardfile
|
||||||
|
#
|
||||||
|
# @see Guard::DslDescriber
|
||||||
|
#
|
||||||
def list
|
def list
|
||||||
::Guard::DslDescriber.evaluate_guardfile(options)
|
Guard::DslDescriber.evaluate_guardfile(options)
|
||||||
installed = []
|
|
||||||
::Guard::DslDescriber.guardfile_structure.each do |group|
|
installed = Guard::DslDescriber.guardfile_structure.inject([]) do |installed, group|
|
||||||
group[:guards].each {|x| installed << x[:name]} if group[:guards]
|
group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
|
||||||
|
installed
|
||||||
end
|
end
|
||||||
|
|
||||||
::Guard::UI.info "Available guards:"
|
Guard::UI.info 'Available guards:'
|
||||||
::Guard::guard_gem_names.sort.each do |name|
|
|
||||||
if installed.include? name
|
Guard::guard_gem_names.sort.uniq.each do |name|
|
||||||
::Guard::UI.info " #{name} *"
|
Guard::UI.info " #{ name } #{ installed.include?(name) ? '*' : '' }"
|
||||||
else
|
|
||||||
::Guard::UI.info " #{name}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
::Guard::UI.info ' '
|
|
||||||
::Guard::UI.info "See also https://github.com/guard/guard/wiki/List-of-available-Guards"
|
Guard::UI.info ' '
|
||||||
::Guard::UI.info "* denotes ones already in your Guardfile"
|
Guard::UI.info 'See also https://github.com/guard/guard/wiki/List-of-available-Guards'
|
||||||
|
Guard::UI.info '* denotes ones already in your Guardfile'
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "version", "Prints Guard's version"
|
desc 'version', 'Show the Guard version'
|
||||||
def version
|
|
||||||
::Guard::UI.info "Guard version #{Guard::VERSION}"
|
|
||||||
end
|
|
||||||
map %w(-v --version) => :version
|
map %w(-v --version) => :version
|
||||||
|
|
||||||
desc "init [GUARD]", "Generates a Guardfile into the current working directory, or insert the given GUARD in an existing Guardfile"
|
# Shows the current version of Guard.
|
||||||
def init(guard_name = nil)
|
#
|
||||||
if !File.exist?("Guardfile")
|
# @see Guard::VERSION
|
||||||
puts "Writing new Guardfile to #{Dir.pwd}/Guardfile"
|
#
|
||||||
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
|
def version
|
||||||
elsif guard_name.nil?
|
Guard::UI.info "Guard version #{ Guard::VERSION }"
|
||||||
::Guard::UI.error "Guardfile already exists at #{Dir.pwd}/Guardfile"
|
end
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
||||||
|
desc 'init [GUARD]', 'Generates a Guardfile at the current working directory, or insert the given GUARD to an existing Guardfile'
|
||||||
|
|
||||||
|
# Appends the Guard template to the `Guardfile`, or creates an initial
|
||||||
|
# `Guardfile` when no Guard name is passed,
|
||||||
|
#
|
||||||
|
# @param [String] guard_name the name of the Guard to initialize
|
||||||
|
#
|
||||||
|
def init(guard_name = nil)
|
||||||
if guard_name
|
if guard_name
|
||||||
guard_class = ::Guard.get_guard_class(guard_name)
|
guard_class = ::Guard.get_guard_class(guard_name)
|
||||||
guard_class.init(guard_name)
|
guard_class.init(guard_name)
|
||||||
|
|
||||||
|
else
|
||||||
|
if File.exist?('Guardfile')
|
||||||
|
puts 'Writing new Guardfile to #{Dir.pwd}/Guardfile'
|
||||||
|
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
|
||||||
|
else
|
||||||
|
Guard::UI.error "Guardfile already exists at #{ Dir.pwd }/Guardfile"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "show", "Show all defined Guards and their options"
|
desc 'show', 'Show all defined Guards and their options'
|
||||||
def show
|
map %w(-T) => :show
|
||||||
::Guard::DslDescriber.evaluate_guardfile(options)
|
|
||||||
|
|
||||||
::Guard::DslDescriber.guardfile_structure.each do |group|
|
# Shows all Guards and their options that are defined in
|
||||||
if !group[:guards].empty?
|
# the `Guardfile`.
|
||||||
|
#
|
||||||
|
# @example guard show output
|
||||||
|
#
|
||||||
|
# (global):
|
||||||
|
# bundler
|
||||||
|
# coffeescript: input => "app/assets/javascripts", noop => true
|
||||||
|
# jasmine
|
||||||
|
# rspec: cli => "--fail-fast --format Fuubar
|
||||||
|
#
|
||||||
|
# @see Guard::DslDescriber
|
||||||
|
#
|
||||||
|
def show
|
||||||
|
Guard::DslDescriber.evaluate_guardfile(options)
|
||||||
|
|
||||||
|
Guard::DslDescriber.guardfile_structure.each do |group|
|
||||||
|
unless group[:guards].empty?
|
||||||
if group[:group]
|
if group[:group]
|
||||||
::Guard::UI.info "Group #{group[:group]}:"
|
Guard::UI.info "Group #{ group[:group] }:"
|
||||||
else
|
else
|
||||||
::Guard::UI.info "(global):"
|
Guard::UI.info '(global):'
|
||||||
end
|
end
|
||||||
|
|
||||||
group[:guards].each do |guard|
|
group[:guards].each do |guard|
|
||||||
line = " #{guard[:name]}"
|
line = " #{ guard[:name] }"
|
||||||
|
|
||||||
if !guard[:options].empty?
|
unless guard[:options].empty?
|
||||||
line += ": #{guard[:options].collect { |k, v| "#{k} => #{v.inspect}" }.join(", ")}"
|
line += ": #{ guard[:options].collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
|
||||||
end
|
end
|
||||||
::Guard::UI.info line
|
|
||||||
|
Guard::UI.info line
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
::Guard::UI.info ''
|
Guard::UI.info ''
|
||||||
end
|
end
|
||||||
map %w(-T) => :show
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
278
lib/guard/dsl.rb
278
lib/guard/dsl.rb
@ -1,62 +1,160 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# The DSL class provides the methods that are used in each `Guardfile` to describe
|
||||||
|
# the behaviour of Guard.
|
||||||
|
#
|
||||||
|
# The main keywords of the DSL are `guard` and `watch`, which are necessary to define
|
||||||
|
# which Guards are used a what file changes they are watching.
|
||||||
|
#
|
||||||
|
# Optionally you can group the Guards with the `group` keyword and ignore certain paths
|
||||||
|
# with the `ignore_paths` keyword.
|
||||||
|
#
|
||||||
|
# A more advanced DSL use is the `callback` keyword, that allows you to execute arbitrary
|
||||||
|
# code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change`
|
||||||
|
# guards' method. You can even insert more hooks inside these methods.
|
||||||
|
# Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
|
||||||
|
#
|
||||||
|
# The DSL will also evaluate normal Ruby code.
|
||||||
|
#
|
||||||
|
# There are two possible locations for the `Guardfile`:
|
||||||
|
# - The `Guardfile` in the current directory where Guard has been started
|
||||||
|
# - The `.Guardfile` in your home directory.
|
||||||
|
#
|
||||||
|
# In addition, if a user configuration `.guard.rb` in your home directory is found, it will
|
||||||
|
# be appended to the current project `Guardfile`.
|
||||||
|
#
|
||||||
|
# @example A sample of a complex Guardfile
|
||||||
|
# group 'frontend' do
|
||||||
|
# guard 'passenger', :ping => true do
|
||||||
|
# watch('config/application.rb')
|
||||||
|
# watch('config/environment.rb')
|
||||||
|
# watch(%r{^config/environments/.+\.rb})
|
||||||
|
# watch(%r{^config/initializers/.+\.rb})
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# guard 'livereload', :apply_js_live => false do
|
||||||
|
# watch(%r{^app/.+\.(erb|haml)})
|
||||||
|
# watch(%r{^app/helpers/.+\.rb})
|
||||||
|
# watch(%r{^public/javascripts/.+\.js})
|
||||||
|
# watch(%r{^public/stylesheets/.+\.css})
|
||||||
|
# watch(%r{^public/.+\.html})
|
||||||
|
# watch(%r{^config/locales/.+\.yml})
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# group 'backend' do
|
||||||
|
# # Reload the bundle when the Gemfile is modified
|
||||||
|
# guard 'bundler' do
|
||||||
|
# watch('Gemfile')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # for big project you can fine tune the "timeout" before Spork's launch is considered failed
|
||||||
|
# guard 'spork', :wait => 40 do
|
||||||
|
# watch('Gemfile')
|
||||||
|
# watch('config/application.rb')
|
||||||
|
# watch('config/environment.rb')
|
||||||
|
# watch(%r{^config/environments/.+\.rb})
|
||||||
|
# watch(%r{^config/initializers/.+\.rb})
|
||||||
|
# watch('spec/spec_helper.rb')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # use RSpec 2, from the system's gem and with some direct RSpec CLI options
|
||||||
|
# guard 'rspec', :version => 2, :cli => "--color --drb -f doc", :bundler => false do
|
||||||
|
# watch('spec/spec_helper.rb') { "spec" }
|
||||||
|
# watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
||||||
|
# watch('config/routes.rb') { "spec/routing" }
|
||||||
|
# watch(%r{^spec/support/(controllers|acceptance)_helpers\.rb}) { |m| "spec/#{m[1]}" }
|
||||||
|
# watch(%r{^spec/.+_spec\.rb})
|
||||||
|
#
|
||||||
|
# watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
||||||
|
#
|
||||||
|
# watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||||
|
# watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
class Dsl
|
class Dsl
|
||||||
class << self
|
class << self
|
||||||
|
|
||||||
@@options = nil
|
@@options = nil
|
||||||
|
|
||||||
|
# Evaluate the DSL methods in the `Guardfile`.
|
||||||
|
#
|
||||||
|
# @param [Hash] options the Guard options
|
||||||
|
# @option options [Array<Symbol,String>] 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
|
||||||
|
# @raise [ArgumentError] when options are not a Hash
|
||||||
|
#
|
||||||
def evaluate_guardfile(options = {})
|
def evaluate_guardfile(options = {})
|
||||||
options.is_a?(Hash) or raise ArgumentError.new("evaluate_guardfile not passed a Hash!")
|
raise ArgumentError.new('No option hash passed to evaluate_guardfile!') unless options.is_a?(Hash)
|
||||||
|
|
||||||
@@options = options.dup
|
@@options = options.dup
|
||||||
|
|
||||||
fetch_guardfile_contents
|
fetch_guardfile_contents
|
||||||
instance_eval_guardfile(guardfile_contents_with_user_config)
|
instance_eval_guardfile(guardfile_contents_with_user_config)
|
||||||
|
|
||||||
UI.error "No guards found in Guardfile, please add at least one." if !::Guard.guards.nil? && ::Guard.guards.empty?
|
UI.error 'No guards found in Guardfile, please add at least one.' if !::Guard.guards.nil? && ::Guard.guards.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reevaluate the Guardfile to update the current Guard configuration
|
||||||
|
# when the `Guardfile` has been changed after Guard is started.
|
||||||
|
#
|
||||||
def reevaluate_guardfile
|
def reevaluate_guardfile
|
||||||
::Guard.guards.clear
|
::Guard.guards.clear
|
||||||
@@options.delete(:guardfile_contents)
|
@@options.delete(:guardfile_contents)
|
||||||
Dsl.evaluate_guardfile(@@options)
|
Dsl.evaluate_guardfile(@@options)
|
||||||
msg = "Guardfile has been re-evaluated."
|
msg = 'Guardfile has been re-evaluated.'
|
||||||
UI.info(msg)
|
UI.info(msg)
|
||||||
Notifier.notify(msg)
|
Notifier.notify(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Evaluate the content of the `Guardfile`.
|
||||||
|
#
|
||||||
|
# @param [String] contents the content to evaluate.
|
||||||
|
#
|
||||||
def instance_eval_guardfile(contents)
|
def instance_eval_guardfile(contents)
|
||||||
begin
|
new.instance_eval(contents, @@options[:guardfile_path], 1)
|
||||||
new.instance_eval(contents, @@options[:guardfile_path], 1)
|
rescue
|
||||||
rescue
|
UI.error "Invalid Guardfile, original error is:\n#{ $! }"
|
||||||
UI.error "Invalid Guardfile, original error is:\n#{$!}"
|
exit 1
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if the current `Guardfile` contains a specific Guard.
|
||||||
|
#
|
||||||
|
# @param [String] guard_name the name of the Guard
|
||||||
|
# @return [Boolean] whether the Guard has been declared
|
||||||
|
#
|
||||||
def guardfile_include?(guard_name)
|
def guardfile_include?(guard_name)
|
||||||
guardfile_contents.match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/)
|
guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Read the current `Guardfile` content.
|
||||||
|
#
|
||||||
|
# @param [String] the path to the Guardfile
|
||||||
|
#
|
||||||
def read_guardfile(guardfile_path)
|
def read_guardfile(guardfile_path)
|
||||||
begin
|
@@options[:guardfile_path] = guardfile_path
|
||||||
@@options[:guardfile_path] = guardfile_path
|
@@options[:guardfile_contents] = File.read(guardfile_path)
|
||||||
@@options[:guardfile_contents] = File.read(guardfile_path)
|
rescue
|
||||||
rescue
|
UI.error("Error reading file #{ guardfile_path }")
|
||||||
UI.error("Error reading file #{guardfile_path}")
|
exit 1
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the content to evaluate and stores it into
|
||||||
|
# the options as :guardfile_contents.
|
||||||
|
#
|
||||||
def fetch_guardfile_contents
|
def fetch_guardfile_contents
|
||||||
# TODO: do we need .rc file interaction?
|
|
||||||
if @@options[:guardfile_contents]
|
if @@options[:guardfile_contents]
|
||||||
UI.info "Using inline Guardfile."
|
UI.info 'Using inline Guardfile.'
|
||||||
@@options[:guardfile_path] = 'Inline Guardfile'
|
@@options[:guardfile_path] = 'Inline Guardfile'
|
||||||
|
|
||||||
elsif @@options[:guardfile]
|
elsif @@options[:guardfile]
|
||||||
if File.exist?(@@options[:guardfile])
|
if File.exist?(@@options[:guardfile])
|
||||||
read_guardfile(@@options[:guardfile])
|
read_guardfile(@@options[:guardfile])
|
||||||
UI.info "Using Guardfile at #{@@options[:guardfile]}."
|
UI.info "Using Guardfile at #{ @@options[:guardfile] }."
|
||||||
else
|
else
|
||||||
UI.error "No Guardfile exists at #{@@options[:guardfile]}."
|
UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -64,84 +162,192 @@ module Guard
|
|||||||
if File.exist?(guardfile_default_path)
|
if File.exist?(guardfile_default_path)
|
||||||
read_guardfile(guardfile_default_path)
|
read_guardfile(guardfile_default_path)
|
||||||
else
|
else
|
||||||
UI.error "No Guardfile found, please create one with `guard init`."
|
UI.error 'No Guardfile found, please create one with `guard init`.'
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unless guardfile_contents_usable?
|
unless guardfile_contents_usable?
|
||||||
UI.error "The command file(#{@@options[:guardfile]}) seems to be empty."
|
UI.error "The command file(#{ @@options[:guardfile] }) seems to be empty."
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the content of the `Guardfile`.
|
||||||
|
#
|
||||||
|
# @return [String] the Guardfile content
|
||||||
|
#
|
||||||
def guardfile_contents
|
def guardfile_contents
|
||||||
@@options ? @@options[:guardfile_contents] : ""
|
@@options ? @@options[:guardfile_contents] : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the content of the `Guardfile` and the global
|
||||||
|
# user configuration file.
|
||||||
|
#
|
||||||
|
# @see #user_config_path
|
||||||
|
#
|
||||||
|
# @return [String] the Guardfile content
|
||||||
|
#
|
||||||
def guardfile_contents_with_user_config
|
def guardfile_contents_with_user_config
|
||||||
config = File.read(user_config_path) if File.exist?(user_config_path)
|
config = File.read(user_config_path) if File.exist?(user_config_path)
|
||||||
[guardfile_contents, config].join("\n")
|
[guardfile_contents, config].join("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the file path to the project `Guardfile`.
|
||||||
|
#
|
||||||
|
# @return [String] the path to the Guardfile
|
||||||
|
#
|
||||||
def guardfile_path
|
def guardfile_path
|
||||||
@@options ? @@options[:guardfile_path] : ""
|
@@options ? @@options[:guardfile_path] : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tests if the current `Guardfile` content is usable.
|
||||||
|
#
|
||||||
|
# @return [Boolean] if the Guardfile is usable
|
||||||
|
#
|
||||||
def guardfile_contents_usable?
|
def guardfile_contents_usable?
|
||||||
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # smallest guard-definition
|
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # smallest guard-definition
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets the default path of the `Guardfile`.
|
||||||
|
# This returns the `Guardfile` from the current directory when existing,
|
||||||
|
# or the global `Guardfile` at the home directory.
|
||||||
|
#
|
||||||
|
# @return [String] the path to the Guardfile
|
||||||
|
#
|
||||||
def guardfile_default_path
|
def guardfile_default_path
|
||||||
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
|
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# The path to the `Guardfile` that is located at
|
||||||
|
# the directory where Guard has been started from.
|
||||||
|
#
|
||||||
|
# @param [String] the path to the local Guardfile
|
||||||
|
#
|
||||||
def local_guardfile_path
|
def local_guardfile_path
|
||||||
File.join(Dir.pwd, "Guardfile")
|
File.join(Dir.pwd, 'Guardfile')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The path to the `.Guardfile` that is located at
|
||||||
|
# the users home directory.
|
||||||
|
#
|
||||||
|
# @param [String] the path to ~/.Guardfile
|
||||||
|
#
|
||||||
def home_guardfile_path
|
def home_guardfile_path
|
||||||
File.expand_path(File.join("~", ".Guardfile"))
|
File.expand_path(File.join('~', '.Guardfile'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The path to the user configuration `.guard.rb`
|
||||||
|
# that is located at the users home directory.
|
||||||
|
#
|
||||||
|
# @param [String] the path to ~/.guard.rb
|
||||||
|
#
|
||||||
def user_config_path
|
def user_config_path
|
||||||
File.expand_path(File.join("~", ".guard.rb"))
|
File.expand_path(File.join('~', '.guard.rb'))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def group(name, &guard_definition)
|
# Declares a group of guards to be run with `guard start --group group_name`.
|
||||||
|
#
|
||||||
|
# @example Declare two groups of Guards
|
||||||
|
# group 'backend' do
|
||||||
|
# guard 'spork'
|
||||||
|
# guard 'rspec'
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# group 'frontend' do
|
||||||
|
# guard 'passenger'
|
||||||
|
# guard 'livereload'
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @param [Symbol, String] name the group's name called from the CLI
|
||||||
|
# @yield a block where you can declare several guards
|
||||||
|
#
|
||||||
|
# @see Dsl#guard
|
||||||
|
# @see Guard::DslDescriber
|
||||||
|
#
|
||||||
|
def group(name)
|
||||||
@groups = @@options[:group] || []
|
@groups = @@options[:group] || []
|
||||||
name = name.to_sym
|
name = name.to_sym
|
||||||
|
|
||||||
if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name))
|
if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
|
||||||
@current_group = name
|
@current_group = name
|
||||||
guard_definition.call
|
|
||||||
|
yield if block_given?
|
||||||
|
|
||||||
@current_group = nil
|
@current_group = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def guard(name, options = {}, &watch_and_callback_definition)
|
# Declare a guard to be used when running `guard start`.
|
||||||
|
#
|
||||||
|
# The name parameter is usually the name of the gem without
|
||||||
|
# the 'guard-' prefix.
|
||||||
|
#
|
||||||
|
# The available options are different for each Guard implementation.
|
||||||
|
#
|
||||||
|
# @example Declare a Guard
|
||||||
|
# guard 'rspec' do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @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 Dsl#watch
|
||||||
|
# @see Guard::DslDescriber
|
||||||
|
#
|
||||||
|
def guard(name, options = {})
|
||||||
@watchers = []
|
@watchers = []
|
||||||
@callbacks = []
|
@callbacks = []
|
||||||
watch_and_callback_definition.call if watch_and_callback_definition
|
|
||||||
|
yield if block_given?
|
||||||
|
|
||||||
options.update(:group => (@current_group || :default))
|
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.to_sym, @watchers, @callbacks, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Define a pattern to be watched in order to run actions on file modification.
|
||||||
|
#
|
||||||
|
# @example Declare watchers for a Guard
|
||||||
|
# guard 'rspec' do
|
||||||
|
# watch('spec/spec_helper.rb')
|
||||||
|
# watch(%r{^.+_spec.rb})
|
||||||
|
# watch(%r{^app/controllers/(.+).rb}) { |m| 'spec/acceptance/#{m[1]}s_spec.rb' }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @param [String, Regexp] pattern the pattern to be watched by the guard
|
||||||
|
# @yield a block to be run when the pattern is matched
|
||||||
|
# @yieldparam [MatchData] m matches of the pattern
|
||||||
|
# @yieldreturn a directory, a filename, an array of directories / filenames, or nothing (can be an arbitrary command)
|
||||||
|
#
|
||||||
def watch(pattern, &action)
|
def watch(pattern, &action)
|
||||||
@watchers << ::Guard::Watcher.new(pattern, action)
|
@watchers << ::Guard::Watcher.new(pattern, action)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Define a callback to execute arbitary code before or after any of
|
||||||
|
# the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method.
|
||||||
|
#
|
||||||
|
# @param [Array] args the callback arguments
|
||||||
|
# @yield a block with listeners
|
||||||
|
#
|
||||||
def callback(*args, &listener)
|
def callback(*args, &listener)
|
||||||
listener, events = args.size > 1 ? args : [listener, args[0]]
|
listener, events = args.size > 1 ? args : [listener, args[0]]
|
||||||
@callbacks << { :events => events, :listener => listener }
|
@callbacks << { :events => events, :listener => listener }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Ignore certain paths globally.
|
||||||
|
#
|
||||||
|
# @example Ignore some paths
|
||||||
|
# ignore_paths .git, .svn
|
||||||
|
#
|
||||||
|
# @param [Array] paths the list of paths to ignore
|
||||||
|
#
|
||||||
def ignore_paths(*paths)
|
def ignore_paths(*paths)
|
||||||
UI.info "Ignoring paths: #{paths.join(', ')}"
|
UI.info "Ignoring paths: #{ paths.join(', ') }"
|
||||||
::Guard.listener.ignore_paths.push(*paths)
|
::Guard.listener.ignore_paths.push(*paths)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,28 +1,60 @@
|
|||||||
require 'guard/dsl'
|
require 'guard/dsl'
|
||||||
|
|
||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# The DslDescriber overrides methods to create an internal structure
|
||||||
|
# of the Guardfile that is used in some inspection utility methods
|
||||||
|
# like the CLI commands `show` and `list`.
|
||||||
|
#
|
||||||
|
# @see Guard::DSL
|
||||||
|
# @see Guard::CLI
|
||||||
|
#
|
||||||
class DslDescriber < Dsl
|
class DslDescriber < Dsl
|
||||||
|
|
||||||
@@guardfile_structure = [ { :guards => [] } ]
|
@@guardfile_structure = [ { :guards => [] } ]
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
||||||
|
# Get the Guardfile structure.
|
||||||
|
#
|
||||||
|
# @return [Array<Hash>] the structure
|
||||||
|
#
|
||||||
def guardfile_structure
|
def guardfile_structure
|
||||||
@@guardfile_structure
|
@@guardfile_structure
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def group(name, &guard_definition)
|
|
||||||
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
|
|
||||||
|
|
||||||
|
# Declares a group of guards.
|
||||||
|
#
|
||||||
|
# @param [String] name the group's name called from the CLI
|
||||||
|
# @yield a block where you can declare several guards
|
||||||
|
#
|
||||||
|
# @see Guard::Dsl
|
||||||
|
#
|
||||||
|
def group(name)
|
||||||
|
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
|
||||||
@group = true
|
@group = true
|
||||||
guard_definition.call
|
|
||||||
|
yield if block_given?
|
||||||
|
|
||||||
@group = false
|
@group = false
|
||||||
end
|
end
|
||||||
|
|
||||||
def guard(name, options = {}, &watch_definition)
|
# Declare 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
|
||||||
|
#
|
||||||
|
def guard(name, options = {})
|
||||||
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
|
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
|
||||||
|
|
||||||
node[:guards] << { :name => name, :options => options }
|
node[:guards] << { :name => name, :options => options }
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,21 +1,39 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Main class that every Guard implementation must subclass.
|
||||||
|
#
|
||||||
|
# Guard will trigger the `start`, `stop`, `reload`, `run_all` and `run_on_change`
|
||||||
|
# methods depending on user interaction and file modification.
|
||||||
|
#
|
||||||
|
# Each Guard should provide a template Guardfile located within the Gem
|
||||||
|
# at `lib/guard/guard-name/templates/Guardfile`.
|
||||||
|
#
|
||||||
class Guard
|
class Guard
|
||||||
include Hook
|
include Hook
|
||||||
|
|
||||||
attr_accessor :watchers, :options, :group
|
attr_accessor :watchers, :options, :group
|
||||||
|
|
||||||
|
# initialize a Guard.
|
||||||
|
#
|
||||||
|
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
|
||||||
|
# @param [Hash] options the custom Guard options.
|
||||||
|
#
|
||||||
def initialize(watchers = [], options = {})
|
def initialize(watchers = [], options = {})
|
||||||
@group = options.delete(:group) || :default
|
@group = options.delete(:group) || :default
|
||||||
@watchers, @options = watchers, options
|
@watchers, @options = watchers, options
|
||||||
end
|
end
|
||||||
|
|
||||||
# Guardfile template needed inside guard gem
|
# Initialize the Guard. This will copy the Guardfile template inside the Guard gem.
|
||||||
|
# The template Guardfile must be located within the Gem at `lib/guard/guard-name/templates/Guardfile`.
|
||||||
|
#
|
||||||
|
# @param [String] name the name of the Guard
|
||||||
|
#
|
||||||
def self.init(name)
|
def self.init(name)
|
||||||
if ::Guard::Dsl.guardfile_include?(name)
|
if ::Guard::Dsl.guardfile_include?(name)
|
||||||
::Guard::UI.info "Guardfile already includes #{name} guard"
|
::Guard::UI.info "Guardfile already includes #{ name } guard"
|
||||||
else
|
else
|
||||||
content = File.read('Guardfile')
|
content = File.read('Guardfile')
|
||||||
guard = File.read("#{::Guard.locate_guard(name)}/lib/guard/#{name}/templates/Guardfile")
|
guard = File.read("#{ ::Guard.locate_guard(name) }/lib/guard/#{ name }/templates/Guardfile")
|
||||||
File.open('Guardfile', 'wb') do |f|
|
File.open('Guardfile', 'wb') do |f|
|
||||||
f.puts(content)
|
f.puts(content)
|
||||||
f.puts("")
|
f.puts("")
|
||||||
@ -25,31 +43,43 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ================
|
# Call once when guard starts. Please override initialize method to init stuff.
|
||||||
# = Guard method =
|
#
|
||||||
# ================
|
# @return [Boolean] Whether the start action was successful or not
|
||||||
|
#
|
||||||
# Call once when guard starts
|
|
||||||
# Please override initialize method to init stuff
|
|
||||||
def start
|
def start
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Call once when guard quit
|
# Call once when guard quit.
|
||||||
|
#
|
||||||
|
# @return [Boolean] Whether the stop action was successful or not
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
|
# Should be used for "reload" (really!) actions like reloading passenger/spork/bundler/...
|
||||||
|
#
|
||||||
|
# @return [Boolean] Whether the reload action was successful or not
|
||||||
|
#
|
||||||
def reload
|
def reload
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Should be principally used for long action like running all specs/tests/...
|
# Should be used for long action like running all specs/tests/...
|
||||||
|
#
|
||||||
|
# @return [Boolean] Whether the run_all action was successful or not
|
||||||
|
#
|
||||||
def run_all
|
def run_all
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Will be triggered when a file change matched a watcher.
|
||||||
|
#
|
||||||
|
# @param [Array<String>] paths the changes files or paths
|
||||||
|
# @return [Boolean] Whether the run_all action was successful or not
|
||||||
|
#
|
||||||
def run_on_change(paths)
|
def run_on_change(paths)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -1,51 +1,80 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Guard has a hook mechanism that allows you to insert callbacks for individual Guards.
|
||||||
|
# By default, each of the Guard instance methods has a "_begin" and an "_end" hook.
|
||||||
|
# For example, the Guard::Guard#start method has a :start_begin hook that is run immediately
|
||||||
|
# before Guard::Guard#start and a :start_end hook that is run immediately after Guard::Guard#start.
|
||||||
|
#
|
||||||
|
# Read more about [hooks and callbacks on the wiki](https://github.com/guard/guard/wiki/Hooks-and-callbacks).
|
||||||
|
#
|
||||||
module Hook
|
module Hook
|
||||||
|
|
||||||
|
# The Hook module gets included.
|
||||||
|
#
|
||||||
|
# @param [Class] base the class that includes the module
|
||||||
|
#
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.send :include, InstanceMethods
|
base.send :include, InstanceMethods
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Instance methods that gets included in the base class.
|
||||||
|
#
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
# When +event+ is a Symbol, #hook will generate a hook name
|
|
||||||
# by concatenating the method name from where #hook is called
|
# When +event+ is a Symbol, {#hook} will generate a hook name
|
||||||
|
# by concatenating the method name from where {#hook} is called
|
||||||
# with the given Symbol.
|
# with the given Symbol.
|
||||||
# Example:
|
#
|
||||||
|
# @example Add a hook with a Symbol
|
||||||
# def run_all
|
# def run_all
|
||||||
# hook :foo
|
# hook :foo
|
||||||
# end
|
# end
|
||||||
# Here, when #run_all is called, #hook will notify callbacks
|
#
|
||||||
|
# Here, when {Guard::Guard#run_all} is called, {#hook} will notify callbacks
|
||||||
# registered for the "run_all_foo" event.
|
# registered for the "run_all_foo" event.
|
||||||
#
|
#
|
||||||
# When +event+ is a String, #hook will directly turn the String
|
# When +event+ is a String, {#hook} will directly turn the String
|
||||||
# into a Symbol.
|
# into a Symbol.
|
||||||
# Example:
|
#
|
||||||
|
# @example Add a hook with a String
|
||||||
# def run_all
|
# def run_all
|
||||||
# hook "foo_bar"
|
# hook "foo_bar"
|
||||||
# end
|
# end
|
||||||
# Here, when #run_all is called, #hook will notify callbacks
|
#
|
||||||
|
# When {Guard::Guard#run_all} is called, {#hook} will notify callbacks
|
||||||
# registered for the "foo_bar" event.
|
# registered for the "foo_bar" event.
|
||||||
#
|
#
|
||||||
# +args+ parameter is passed as is to the callbacks registered
|
# @param [Symbol, String] event the name of the Guard event
|
||||||
# for the given event.
|
# @param [Array] args the parameters are passed as is to the callbacks registered for the given event.
|
||||||
|
#
|
||||||
def hook(event, *args)
|
def hook(event, *args)
|
||||||
hook_name = if event.is_a? Symbol
|
hook_name = if event.is_a? Symbol
|
||||||
calling_method = caller[0][/`([^']*)'/, 1]
|
calling_method = caller[0][/`([^']*)'/, 1]
|
||||||
"#{calling_method}_#{event}"
|
"#{ calling_method }_#{ event }"
|
||||||
else
|
else
|
||||||
event
|
event
|
||||||
end.to_sym
|
end.to_sym
|
||||||
|
|
||||||
UI.debug "Hook :#{hook_name} executed for #{self.class}"
|
UI.debug "Hook :#{ hook_name } executed for #{ self.class }"
|
||||||
|
|
||||||
Hook.notify(self.class, hook_name, *args)
|
Hook.notify(self.class, hook_name, *args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
||||||
|
# Get all callbacks
|
||||||
|
#
|
||||||
def callbacks
|
def callbacks
|
||||||
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
|
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add a callback.
|
||||||
|
#
|
||||||
|
# @param [Block] listener the listener to notify
|
||||||
|
# @param [Guard::Guard] guard_class the Guard class to add the callback
|
||||||
|
# @param [Array<Symbol>] events the events to register
|
||||||
|
#
|
||||||
def add_callback(listener, guard_class, events)
|
def add_callback(listener, guard_class, events)
|
||||||
_events = events.is_a?(Array) ? events : [events]
|
_events = events.is_a?(Array) ? events : [events]
|
||||||
_events.each do |event|
|
_events.each do |event|
|
||||||
@ -53,19 +82,34 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Checks if a callback has been registered.
|
||||||
|
#
|
||||||
|
# @param [Block] listener the listener to notify
|
||||||
|
# @param [Guard::Guard] guard_class the Guard class to add the callback
|
||||||
|
# @param [Symbol] event the event to look for
|
||||||
|
#
|
||||||
def has_callback?(listener, guard_class, event)
|
def has_callback?(listener, guard_class, event)
|
||||||
callbacks[[guard_class, event]].include?(listener)
|
callbacks[[guard_class, event]].include?(listener)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Notify a callback.
|
||||||
|
#
|
||||||
|
# @param [Guard::Guard] guard_class the Guard class to add the callback
|
||||||
|
# @param [Symbol] event the event to trigger
|
||||||
|
# @param [Array] args the arguments for the listener
|
||||||
|
#
|
||||||
def notify(guard_class, event, *args)
|
def notify(guard_class, event, *args)
|
||||||
callbacks[[guard_class, event]].each do |listener|
|
callbacks[[guard_class, event]].each do |listener|
|
||||||
listener.call(guard_class, event, *args)
|
listener.call(guard_class, event, *args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reset all callbacks
|
||||||
|
#
|
||||||
def reset_callbacks!
|
def reset_callbacks!
|
||||||
@callbacks = nil
|
@callbacks = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,14 +1,29 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# The interactor reads user input and triggers
|
||||||
|
# specific action upon them unless its locked.
|
||||||
|
#
|
||||||
|
# Currently the following actions are implemented:
|
||||||
|
# - stop, quit, exit, s, q, e => Exit Guard
|
||||||
|
# - reload, r, z => Reload Guard
|
||||||
|
# - pause, p => Pause Guard
|
||||||
|
# - Everything else => Run all
|
||||||
|
#
|
||||||
class Interactor
|
class Interactor
|
||||||
|
|
||||||
attr_reader :locked
|
attr_reader :locked
|
||||||
|
|
||||||
|
# Initialize the interactor in unlocked state.
|
||||||
|
#
|
||||||
def initialize
|
def initialize
|
||||||
@locked = false
|
@locked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start the interactor in a own thread.
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
return if ENV["GUARD_ENV"] == 'test'
|
return if ENV["GUARD_ENV"] == 'test'
|
||||||
|
|
||||||
Thread.new do
|
Thread.new do
|
||||||
loop do
|
loop do
|
||||||
if (entry = $stdin.gets) && !@locked
|
if (entry = $stdin.gets) && !@locked
|
||||||
@ -28,10 +43,14 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Lock the interactor.
|
||||||
|
#
|
||||||
def lock
|
def lock
|
||||||
@locked = true
|
@locked = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Unlock the interactor.
|
||||||
|
#
|
||||||
def unlock
|
def unlock
|
||||||
@locked = false
|
@locked = false
|
||||||
end
|
end
|
||||||
|
@ -8,42 +8,65 @@ module Guard
|
|||||||
autoload :Windows, 'guard/listeners/windows'
|
autoload :Windows, 'guard/listeners/windows'
|
||||||
autoload :Polling, 'guard/listeners/polling'
|
autoload :Polling, 'guard/listeners/polling'
|
||||||
|
|
||||||
|
# The Listener is the base class for all listener
|
||||||
|
# implementations.
|
||||||
|
#
|
||||||
|
# @abstract
|
||||||
|
#
|
||||||
class Listener
|
class Listener
|
||||||
|
|
||||||
DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor]
|
# Default paths that gets ignored by the listener
|
||||||
|
DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
|
||||||
|
|
||||||
attr_accessor :changed_files
|
attr_accessor :changed_files
|
||||||
attr_reader :directory, :ignore_paths, :locked
|
attr_reader :directory, :ignore_paths, :locked
|
||||||
|
|
||||||
def self.select_and_init(*a)
|
# Select the appropriate listener implementation for the
|
||||||
|
# current OS and initializes it.
|
||||||
|
#
|
||||||
|
# @param [Array] args the arguments for the listener
|
||||||
|
# @return [Guard::Listener] the chosen listener
|
||||||
|
#
|
||||||
|
def self.select_and_init(*args)
|
||||||
if mac? && Darwin.usable?
|
if mac? && Darwin.usable?
|
||||||
Darwin.new(*a)
|
Darwin.new(*args)
|
||||||
elsif linux? && Linux.usable?
|
elsif linux? && Linux.usable?
|
||||||
Linux.new(*a)
|
Linux.new(*args)
|
||||||
elsif windows? && Windows.usable?
|
elsif windows? && Windows.usable?
|
||||||
Windows.new(*a)
|
Windows.new(*args)
|
||||||
else
|
else
|
||||||
UI.info "Using polling (Please help us to support your system better than that.)"
|
UI.info 'Using polling (Please help us to support your system better than that).'
|
||||||
Polling.new(*a)
|
Polling.new(*args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 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<String>] ignore_paths the paths to ignore by the listener
|
||||||
|
#
|
||||||
def initialize(directory = Dir.pwd, options = {})
|
def initialize(directory = Dir.pwd, options = {})
|
||||||
@directory = directory.to_s
|
@directory = directory.to_s
|
||||||
@sha1_checksums_hash = {}
|
@sha1_checksums_hash = {}
|
||||||
@file_timestamp_hash = {}
|
@file_timestamp_hash = {}
|
||||||
@relativize_paths = options.fetch(:relativize_paths, true)
|
@relativize_paths = options.fetch(:relativize_paths, true)
|
||||||
@watch_deletions = options.fetch(:deletions, false)
|
|
||||||
@changed_files = []
|
@changed_files = []
|
||||||
@locked = false
|
@locked = false
|
||||||
@ignore_paths = DefaultIgnorePaths
|
@ignore_paths = DEFAULT_IGNORE_PATHS
|
||||||
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
|
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
|
||||||
|
@watch_all_modifications = options.fetch(:watch_all_modifications, false)
|
||||||
|
|
||||||
update_last_event
|
update_last_event
|
||||||
start_reactor
|
start_reactor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start the listener thread.
|
||||||
|
#
|
||||||
def start_reactor
|
def start_reactor
|
||||||
return if ENV["GUARD_ENV"] == 'test'
|
return if ENV["GUARD_ENV"] == 'test'
|
||||||
|
|
||||||
Thread.new do
|
Thread.new do
|
||||||
loop do
|
loop do
|
||||||
if @changed_files != [] && !@locked
|
if @changed_files != [] && !@locked
|
||||||
@ -57,38 +80,59 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start watching the root directory.
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
watch(@directory)
|
watch(@directory)
|
||||||
timestamp_files
|
timestamp_files
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stop listening for events.
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Lock the listener to ignore change events.
|
||||||
|
#
|
||||||
def lock
|
def lock
|
||||||
@locked = true
|
@locked = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Unlock the listener to listen again to change events.
|
||||||
|
#
|
||||||
def unlock
|
def unlock
|
||||||
@locked = false
|
@locked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Clear the list of changed files.
|
||||||
|
#
|
||||||
def clear_changed_files
|
def clear_changed_files
|
||||||
@changed_files.clear
|
@changed_files.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Store a listener callback.
|
||||||
|
#
|
||||||
|
# @param [Block] callback the callback to store
|
||||||
|
#
|
||||||
def on_change(&callback)
|
def on_change(&callback)
|
||||||
@callback = callback
|
@callback = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Updates the timestamp of the last event.
|
||||||
|
#
|
||||||
def update_last_event
|
def update_last_event
|
||||||
@last_event = Time.now
|
@last_event = Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
def modified_files(dirs, options={})
|
# Get the modified files.
|
||||||
|
#
|
||||||
|
# @param [Array<String>] dirs the watched directories
|
||||||
|
# @param [Hash] options the listener options
|
||||||
|
#
|
||||||
|
def modified_files(dirs, options = {})
|
||||||
last_event = @last_event
|
last_event = @last_event
|
||||||
files = []
|
files = []
|
||||||
if @watch_deletions
|
if @watch_all_modifications
|
||||||
deleted_files = @file_timestamp_hash.collect do |path, ts|
|
deleted_files = @file_timestamp_hash.collect do |path, ts|
|
||||||
unless File.exists?(path)
|
unless File.exists?(path)
|
||||||
@sha1_checksums_hash.delete(path)
|
@sha1_checksums_hash.delete(path)
|
||||||
@ -103,46 +147,70 @@ module Guard
|
|||||||
relativize_paths(files)
|
relativize_paths(files)
|
||||||
end
|
end
|
||||||
|
|
||||||
def worker
|
# Register a directory to watch.
|
||||||
raise NotImplementedError, "should respond to #watch"
|
# Must be implemented by the subclasses.
|
||||||
end
|
#
|
||||||
|
# @param [String] directory the directory to watch
|
||||||
# register a directory to watch. must be implemented by the subclasses
|
#
|
||||||
def watch(directory)
|
def watch(directory)
|
||||||
raise NotImplementedError, "do whatever you want here, given the directory as only argument"
|
raise NotImplementedError, "do whatever you want here, given the directory as only argument"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get all files that are in the watched directory.
|
||||||
|
#
|
||||||
|
# @return [Array<String>] the list of files
|
||||||
|
#
|
||||||
def all_files
|
def all_files
|
||||||
potentially_modified_files([@directory], :all => true)
|
potentially_modified_files([@directory], :all => true)
|
||||||
end
|
end
|
||||||
|
|
||||||
# scopes all given paths to the current #directory
|
# Scopes all given paths to the current directory.
|
||||||
|
#
|
||||||
|
# @param [Array<String>] paths the paths to change
|
||||||
|
# @return [Array<String>] all paths now relative to the current dir
|
||||||
|
#
|
||||||
def relativize_paths(paths)
|
def relativize_paths(paths)
|
||||||
return paths unless relativize_paths?
|
return paths unless relativize_paths?
|
||||||
paths.map do |path|
|
paths.map do |path|
|
||||||
path.gsub(%r{#{@directory}/}, '')
|
path.gsub(%r{#{@directory}/}, '')
|
||||||
|
#path.gsub(%r{^#{ @directory }/}, '')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Use relative paths?
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether to use relative or absolute paths
|
||||||
|
#
|
||||||
def relativize_paths?
|
def relativize_paths?
|
||||||
!!@relativize_paths
|
!!@relativize_paths
|
||||||
end
|
end
|
||||||
|
|
||||||
# populate initial timestamp file hash to watch for deleted or moved files
|
# populate initial timestamp file hash to watch for deleted or moved files
|
||||||
def timestamp_files
|
def timestamp_files
|
||||||
all_files.each {|path| set_file_timestamp_hash(path, file_timestamp(path)) } if @watch_deletions
|
all_files.each {|path| set_file_timestamp_hash(path, file_timestamp(path)) } if @watch_all_modifications
|
||||||
end
|
end
|
||||||
|
|
||||||
# return children of the passed dirs that are not in the ignore_paths list
|
# Removes ignored paths from the directory list.
|
||||||
|
#
|
||||||
|
# @param [Array<String>] dirs the directory to listen to
|
||||||
|
# @param [Array<String>] ignore_paths the paths to ignore
|
||||||
|
# @return children of the passed dirs that are not in the ignore_paths list
|
||||||
|
#
|
||||||
def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
|
def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
|
||||||
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
|
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
|
||||||
ignore_paths.include?(File.basename(path))
|
ignore_paths.include?(File.basename(path))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def potentially_modified_files(dirs, options={})
|
# Gets a list of files that are in the modified firectories.
|
||||||
|
#
|
||||||
|
# @param [Array<String>] 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 = {})
|
||||||
paths = exclude_ignored_paths(dirs)
|
paths = exclude_ignored_paths(dirs)
|
||||||
|
|
||||||
if options[:all]
|
if options[:all]
|
||||||
@ -150,7 +218,7 @@ module Guard
|
|||||||
if File.file?(path)
|
if File.file?(path)
|
||||||
array << path
|
array << path
|
||||||
else
|
else
|
||||||
array += Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
|
array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
|
||||||
end
|
end
|
||||||
array
|
array
|
||||||
end
|
end
|
||||||
@ -159,9 +227,17 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if the file content has changed.
|
||||||
|
#
|
||||||
# Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
|
# Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
|
||||||
# both values down to the second for the comparison.
|
# both values down to the second for the comparison.
|
||||||
|
#
|
||||||
# ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
|
# ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
|
||||||
|
#
|
||||||
|
# @param [String] path the file path
|
||||||
|
# @param [Time] last_event the time of the last event
|
||||||
|
# @return [Boolean] Whether the file content has changed or not.
|
||||||
|
#
|
||||||
def file_modified?(path, last_event)
|
def file_modified?(path, last_event)
|
||||||
ctime = File.ctime(path).to_i
|
ctime = File.ctime(path).to_i
|
||||||
mtime = File.mtime(path).to_i
|
mtime = File.mtime(path).to_i
|
||||||
@ -170,7 +246,7 @@ module Guard
|
|||||||
elsif mtime > last_event.to_i
|
elsif mtime > last_event.to_i
|
||||||
set_sha1_checksums_hash(path, sha1_checksum(path))
|
set_sha1_checksums_hash(path, sha1_checksum(path))
|
||||||
true
|
true
|
||||||
elsif @watch_deletions
|
elsif @watch_all_modifications
|
||||||
ts = file_timestamp(path)
|
ts = file_timestamp(path)
|
||||||
if ts != @file_timestamp_hash[path]
|
if ts != @file_timestamp_hash[path]
|
||||||
set_file_timestamp_hash(path, ts)
|
set_file_timestamp_hash(path, ts)
|
||||||
@ -183,6 +259,12 @@ module Guard
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tests if the file content has been modified by
|
||||||
|
# comparing the SHA1 checksum.
|
||||||
|
#
|
||||||
|
# @param [String] path the file path
|
||||||
|
# @param [String] sha1_checksum the checksum of the file
|
||||||
|
#
|
||||||
def file_content_modified?(path, sha1_checksum)
|
def file_content_modified?(path, sha1_checksum)
|
||||||
if @sha1_checksums_hash[path] != sha1_checksum
|
if @sha1_checksums_hash[path] != sha1_checksum
|
||||||
set_sha1_checksums_hash(path, sha1_checksum)
|
set_sha1_checksums_hash(path, sha1_checksum)
|
||||||
@ -196,6 +278,11 @@ module Guard
|
|||||||
@file_timestamp_hash[path] = file_timestamp
|
@file_timestamp_hash[path] = file_timestamp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set the current checksum of a file.
|
||||||
|
#
|
||||||
|
# @param [String] path the file path
|
||||||
|
# @param [String] sha1_checksum the checksum of the file
|
||||||
|
#
|
||||||
def set_sha1_checksums_hash(path, sha1_checksum)
|
def set_sha1_checksums_hash(path, sha1_checksum)
|
||||||
@sha1_checksums_hash[path] = sha1_checksum
|
@sha1_checksums_hash[path] = sha1_checksum
|
||||||
end
|
end
|
||||||
@ -204,18 +291,35 @@ module Guard
|
|||||||
File.mtime(path).to_i
|
File.mtime(path).to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Calculates the SHA1 checksum of a file.
|
||||||
|
#
|
||||||
|
# @param [String] path the path to the file
|
||||||
|
# @return [String] the SHA1 checksum
|
||||||
|
#
|
||||||
def sha1_checksum(path)
|
def sha1_checksum(path)
|
||||||
Digest::SHA1.file(path).to_s
|
Digest::SHA1.file(path).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if the OS is Mac OS X.
|
||||||
|
#
|
||||||
|
# @return [Boolean] Whether the OS is Mac OS X
|
||||||
|
#
|
||||||
def self.mac?
|
def self.mac?
|
||||||
RbConfig::CONFIG['target_os'] =~ /darwin/i
|
RbConfig::CONFIG['target_os'] =~ /darwin/i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if the OS is Linux.
|
||||||
|
#
|
||||||
|
# @return [Boolean] Whether the OS is Linux
|
||||||
|
#
|
||||||
def self.linux?
|
def self.linux?
|
||||||
RbConfig::CONFIG['target_os'] =~ /linux/i
|
RbConfig::CONFIG['target_os'] =~ /linux/i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if the OS is Windows.
|
||||||
|
#
|
||||||
|
# @return [Boolean] Whether the OS is Windows
|
||||||
|
#
|
||||||
def self.windows?
|
def self.windows?
|
||||||
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||||
end
|
end
|
||||||
|
@ -1,41 +1,60 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Listener implementation for Mac OS X FSEvents.
|
||||||
|
#
|
||||||
class Darwin < Listener
|
class Darwin < Listener
|
||||||
|
|
||||||
|
# Initialize the Listener.
|
||||||
|
#
|
||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@fsevent = FSEvent.new
|
@fsevent = FSEvent.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def worker
|
# Start the listener.
|
||||||
@fsevent
|
#
|
||||||
end
|
|
||||||
|
|
||||||
def start
|
def start
|
||||||
super
|
super
|
||||||
worker.run
|
worker.run
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stop the listener.
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
super
|
super
|
||||||
worker.stop
|
worker.stop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if the listener is usable on the current OS.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether usable or not
|
||||||
|
#
|
||||||
def self.usable?
|
def self.usable?
|
||||||
require 'rb-fsevent'
|
require 'rb-fsevent'
|
||||||
if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) &&
|
if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) &&
|
||||||
Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0'))
|
Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0'))
|
||||||
UI.info "Please update rb-fsevent (>= 0.4.0)"
|
UI.info 'Please update rb-fsevent (>= 0.4.0)'
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
UI.info "Please install rb-fsevent gem for Mac OSX FSEvents support"
|
UI.info 'Please install rb-fsevent gem for Mac OSX FSEvents support'
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Get the listener worker.
|
||||||
|
#
|
||||||
|
def worker
|
||||||
|
@fsevent
|
||||||
|
end
|
||||||
|
|
||||||
|
# Watch the given directory for file changes.
|
||||||
|
#
|
||||||
|
# @param [String] directory the directory to watch
|
||||||
|
#
|
||||||
def watch(directory)
|
def watch(directory)
|
||||||
worker.watch(directory) do |modified_dirs|
|
worker.watch(directory) do |modified_dirs|
|
||||||
files = modified_files(modified_dirs)
|
files = modified_files(modified_dirs)
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Listener implementation for Linux inotify.
|
||||||
|
#
|
||||||
class Linux < Listener
|
class Linux < Listener
|
||||||
|
|
||||||
|
# Initialize the Listener.
|
||||||
|
#
|
||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@inotify = INotify::Notifier.new
|
@inotify = INotify::Notifier.new
|
||||||
@ -8,44 +13,53 @@ module Guard
|
|||||||
@latency = 0.5
|
@latency = 0.5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start the listener.
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
@stop = false
|
@stop = false
|
||||||
super
|
super
|
||||||
watch_change unless watch_change?
|
watch_change unless watch_change?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stop the listener.
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
super
|
super
|
||||||
@stop = true
|
@stop = true
|
||||||
sleep(@latency)
|
sleep(@latency)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if the listener is usable on the current OS.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether usable or not
|
||||||
|
#
|
||||||
def self.usable?
|
def self.usable?
|
||||||
require 'rb-inotify'
|
require 'rb-inotify'
|
||||||
if !defined?(INotify::VERSION) || (defined?(Gem::Version) &&
|
if !defined?(INotify::VERSION) || (defined?(Gem::Version) &&
|
||||||
Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5'))
|
Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5'))
|
||||||
UI.info "Please update rb-inotify (>= 0.8.5)"
|
UI.info 'Please update rb-inotify (>= 0.8.5)'
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
UI.info "Please install rb-inotify gem for Linux inotify support"
|
UI.info 'Please install rb-inotify gem for Linux inotify support'
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def watch_change?
|
private
|
||||||
!!@watch_change
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
|
# Get the listener worker.
|
||||||
|
#
|
||||||
def worker
|
def worker
|
||||||
@inotify
|
@inotify
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Watch the given directory for file changes.
|
||||||
|
#
|
||||||
|
# @param [String] directory the directory to watch
|
||||||
|
#
|
||||||
def watch(directory)
|
def watch(directory)
|
||||||
# The event selection is based on https://github.com/guard/guard/wiki/Analysis-of-inotify-events-for-different-editors
|
|
||||||
worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
|
worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
|
||||||
unless event.name == "" # Event on root directory
|
unless event.name == "" # Event on root directory
|
||||||
@files << event.absolute_name
|
@files << event.absolute_name
|
||||||
@ -54,6 +68,16 @@ module Guard
|
|||||||
rescue Interrupt
|
rescue Interrupt
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if inotify is watching for changes.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether inotify is active or not
|
||||||
|
#
|
||||||
|
def watch_change?
|
||||||
|
!!@watch_change
|
||||||
|
end
|
||||||
|
|
||||||
|
# Watch for file system changes.
|
||||||
|
#
|
||||||
def watch_change
|
def watch_change
|
||||||
@watch_change = true
|
@watch_change = true
|
||||||
until @stop
|
until @stop
|
||||||
|
@ -1,24 +1,46 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Polling listener that works cross-plattform and
|
||||||
|
# has no dependencies. This is the listener that
|
||||||
|
# uses the most CPU processing power and has higher
|
||||||
|
# File IO that the other implementations.
|
||||||
|
#
|
||||||
class Polling < Listener
|
class Polling < Listener
|
||||||
|
|
||||||
|
# Initialize the Listener.
|
||||||
|
#
|
||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@latency = 1.5
|
@latency = 1.5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start the listener.
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
@stop = false
|
@stop = false
|
||||||
super
|
super
|
||||||
watch_change
|
watch_change
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stop the listener.
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
super
|
super
|
||||||
@stop = true
|
@stop = true
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# Watch the given directory for file changes.
|
||||||
|
#
|
||||||
|
# @param [String] directory the directory to watch
|
||||||
|
#
|
||||||
|
def watch(directory)
|
||||||
|
@existing = all_files
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Watch for file system changes.
|
||||||
|
#
|
||||||
def watch_change
|
def watch_change
|
||||||
until @stop
|
until @stop
|
||||||
start = Time.now.to_f
|
start = Time.now.to_f
|
||||||
@ -29,9 +51,5 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def watch(directory)
|
|
||||||
@existing = all_files
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,35 +1,48 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# Listener implementation for Windows fchange.
|
||||||
|
#
|
||||||
class Windows < Listener
|
class Windows < Listener
|
||||||
|
|
||||||
|
# Initialize the Listener.
|
||||||
|
#
|
||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@fchange = FChange::Notifier.new
|
@fchange = FChange::Notifier.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start the listener.
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
super
|
super
|
||||||
worker.run
|
worker.run
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stop the listener.
|
||||||
|
#
|
||||||
def stop
|
def stop
|
||||||
super
|
super
|
||||||
worker.stop
|
worker.stop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if the listener is usable on the current OS.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether usable or not
|
||||||
|
#
|
||||||
def self.usable?
|
def self.usable?
|
||||||
require 'rb-fchange'
|
require 'rb-fchange'
|
||||||
true
|
true
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
UI.info "Please install rb-fchange gem for Windows file events support"
|
UI.info 'Please install rb-fchange gem for Windows file events support'
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def worker
|
|
||||||
@fchange
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# Watch the given directory for file changes.
|
||||||
|
#
|
||||||
|
# @param [String] directory the directory to watch
|
||||||
|
#
|
||||||
def watch(directory)
|
def watch(directory)
|
||||||
worker.watch(directory, :all_events, :recursive) do |event|
|
worker.watch(directory, :all_events, :recursive) do |event|
|
||||||
paths = [File.expand_path(event.watcher.path)]
|
paths = [File.expand_path(event.watcher.path)]
|
||||||
@ -38,5 +51,11 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the listener worker.
|
||||||
|
#
|
||||||
|
def worker
|
||||||
|
@fchange
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,13 +3,28 @@ require 'pathname'
|
|||||||
require 'guard/ui'
|
require 'guard/ui'
|
||||||
|
|
||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# The notifier class handles cross-platform system notifications that supports:
|
||||||
|
# - Growl on Mac OS X
|
||||||
|
# - Libnotify on Linux
|
||||||
|
# - Notifu on Windows
|
||||||
|
#
|
||||||
module Notifier
|
module Notifier
|
||||||
|
|
||||||
|
# Application name as shown in the specific notification settings
|
||||||
APPLICATION_NAME = "Guard"
|
APPLICATION_NAME = "Guard"
|
||||||
|
|
||||||
|
# Turn notifications of.
|
||||||
|
#
|
||||||
def self.turn_off
|
def self.turn_off
|
||||||
ENV["GUARD_NOTIFY"] = 'false'
|
ENV["GUARD_NOTIFY"] = 'false'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Turn notifications on. This tries to load the platform
|
||||||
|
# specific notification library.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether the notification could be enabled.
|
||||||
|
#
|
||||||
def self.turn_on
|
def self.turn_on
|
||||||
ENV["GUARD_NOTIFY"] = 'true'
|
ENV["GUARD_NOTIFY"] = 'true'
|
||||||
case RbConfig::CONFIG['target_os']
|
case RbConfig::CONFIG['target_os']
|
||||||
@ -22,6 +37,15 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Show a message with the system notification.
|
||||||
|
#
|
||||||
|
# @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
|
||||||
|
#
|
||||||
def self.notify(message, options = {})
|
def self.notify(message, options = {})
|
||||||
if enabled?
|
if enabled?
|
||||||
image = options.delete(:image) || :success
|
image = options.delete(:image) || :success
|
||||||
@ -38,12 +62,23 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if the notifications are enabled and available.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether the notifications are available
|
||||||
|
#
|
||||||
def self.enabled?
|
def self.enabled?
|
||||||
ENV["GUARD_NOTIFY"] == 'true'
|
ENV["GUARD_NOTIFY"] == 'true'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Send a message to Growl either with the growl gem or the growl_notify gem.
|
||||||
|
#
|
||||||
|
# @param [String] title the notification title
|
||||||
|
# @param [String] message the message to show
|
||||||
|
# @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
|
require_growl # need for guard-rspec formatter that is called out of guard scope
|
||||||
|
|
||||||
@ -61,18 +96,42 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Send a message to libnotify.
|
||||||
|
#
|
||||||
|
# @param [String] title the notification title
|
||||||
|
# @param [String] message the message to show
|
||||||
|
# @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
|
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 }
|
default_options = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
|
||||||
Libnotify.show default_options.merge(options) if enabled?
|
Libnotify.show default_options.merge(options) if enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Send a message to notifu.
|
||||||
|
#
|
||||||
|
# @param [String] title the notification title
|
||||||
|
# @param [String] message the message to show
|
||||||
|
# @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
|
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 }
|
default_options = { :message => message, :title => title, :type => image_level(image), :time => 3 }
|
||||||
Notifu.show default_options.merge(options) if enabled?
|
Notifu.show default_options.merge(options) if enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the image path for an image symbol.
|
||||||
|
#
|
||||||
|
# Known symbols are:
|
||||||
|
# - failed
|
||||||
|
# - pending
|
||||||
|
# - success
|
||||||
|
#
|
||||||
|
# @param [Symbol] image the image name
|
||||||
|
# @return [String] the image path
|
||||||
|
#
|
||||||
def self.image_path(image)
|
def self.image_path(image)
|
||||||
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
|
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
|
||||||
case image
|
case image
|
||||||
@ -88,6 +147,11 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The notification level type for the given image.
|
||||||
|
#
|
||||||
|
# @param [Symbol] image the image
|
||||||
|
# @return [Symbol] the level
|
||||||
|
#
|
||||||
def self.image_level(image)
|
def self.image_level(image)
|
||||||
case image
|
case image
|
||||||
when :failed
|
when :failed
|
||||||
@ -101,6 +165,9 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Try to safely load growl and turns notifications
|
||||||
|
# off on load failure.
|
||||||
|
#
|
||||||
def self.require_growl
|
def self.require_growl
|
||||||
begin
|
begin
|
||||||
require 'growl_notify'
|
require 'growl_notify'
|
||||||
@ -119,6 +186,9 @@ module Guard
|
|||||||
UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
|
UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Try to safely load libnotify and turns notifications
|
||||||
|
# off on load failure.
|
||||||
|
#
|
||||||
def self.require_libnotify
|
def self.require_libnotify
|
||||||
require 'libnotify'
|
require 'libnotify'
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
@ -126,11 +196,15 @@ module Guard
|
|||||||
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
|
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Try to safely load rb-notifu and turns notifications
|
||||||
|
# off on load failure.
|
||||||
|
#
|
||||||
def self.require_rbnotifu
|
def self.require_rbnotifu
|
||||||
require 'rb-notifu'
|
require 'rb-notifu'
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
turn_off
|
turn_off
|
||||||
UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
|
UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
175
lib/guard/ui.rb
175
lib/guard/ui.rb
@ -1,88 +1,92 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# The UI class helps to format messages for the user.
|
||||||
|
#
|
||||||
module UI
|
module UI
|
||||||
|
|
||||||
ANSI_ESCAPE_BRIGHT = "1"
|
|
||||||
|
|
||||||
ANSI_ESCAPE_BLACK = "30"
|
|
||||||
ANSI_ESCAPE_RED = "31"
|
|
||||||
ANSI_ESCAPE_GREEN = "32"
|
|
||||||
ANSI_ESCAPE_YELLOW = "33"
|
|
||||||
ANSI_ESCAPE_BLUE = "34"
|
|
||||||
ANSI_ESCAPE_MAGENTA = "35"
|
|
||||||
ANSI_ESCAPE_CYAN = "36"
|
|
||||||
ANSI_ESCAPE_WHITE = "37"
|
|
||||||
|
|
||||||
ANSI_ESCAPE_BGBLACK = "40"
|
|
||||||
ANSI_ESCAPE_BGRED = "41"
|
|
||||||
ANSI_ESCAPE_BGGREEN = "42"
|
|
||||||
ANSI_ESCAPE_BGYELLOW = "43"
|
|
||||||
ANSI_ESCAPE_BGBLUE = "44"
|
|
||||||
ANSI_ESCAPE_BGMAGENTA = "45"
|
|
||||||
ANSI_ESCAPE_BGCYAN = "46"
|
|
||||||
ANSI_ESCAPE_BGWHITE = "47"
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
||||||
color_enabled = nil
|
color_enabled = nil
|
||||||
|
|
||||||
def info(message, options = {})
|
# Show an info message.
|
||||||
unless ENV["GUARD_ENV"] == "test"
|
#
|
||||||
|
# @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 = { })
|
||||||
|
unless ENV['GUARD_ENV'] == 'test'
|
||||||
reset_line if options[:reset]
|
reset_line if options[:reset]
|
||||||
puts color(message) if message != ''
|
puts color(message) if message != ''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def error(message, options={})
|
# Show a red error message that is prefixed with ERROR.
|
||||||
unless ENV["GUARD_ENV"] == "test"
|
#
|
||||||
|
# @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 = { })
|
||||||
|
unless ENV['GUARD_ENV'] == 'test'
|
||||||
reset_line if options[:reset]
|
reset_line if options[:reset]
|
||||||
puts color('ERROR: ', :red) + message
|
puts color('ERROR: ', :red) + message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def deprecation(message, options = {})
|
# Show a red deprecation message that is prefixed with DEPRECATION.
|
||||||
unless ENV["GUARD_ENV"] == "test"
|
#
|
||||||
|
# @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 = { })
|
||||||
|
unless ENV['GUARD_ENV'] == 'test'
|
||||||
reset_line if options[:reset]
|
reset_line if options[:reset]
|
||||||
puts color('DEPRECATION: ', :red) + message
|
puts color('DEPRECATION: ', :red) + message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def debug(message, options={})
|
# Show a debug message that is prefixed with DEBUG and a timestampe.
|
||||||
unless ENV["GUARD_ENV"] == "test"
|
#
|
||||||
|
# @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={ })
|
||||||
|
unless ENV['GUARD_ENV'] == 'test'
|
||||||
reset_line if options[:reset]
|
reset_line if options[:reset]
|
||||||
puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
|
puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reset a line.
|
||||||
|
#
|
||||||
def reset_line
|
def reset_line
|
||||||
print(color_enabled? ? "\r\e[0m" : "\r\n")
|
print(color_enabled? ? "\r\e[0m" : "\r\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Clear the output.
|
||||||
|
#
|
||||||
def clear
|
def clear
|
||||||
system("clear;")
|
system('clear;')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Reset a color sequence.
|
||||||
|
#
|
||||||
# @deprecated
|
# @deprecated
|
||||||
|
# @param [String] text the text
|
||||||
|
#
|
||||||
def reset_color(text)
|
def reset_color(text)
|
||||||
deprecation('UI.reset_color(text) is deprecated, please use color(text, "") instead.')
|
deprecation('UI.reset_color(text) is deprecated, please use color(text, ' ') instead.')
|
||||||
color(text, "")
|
color(text, '')
|
||||||
end
|
|
||||||
|
|
||||||
def color(text, *color_options)
|
|
||||||
color_code = ""
|
|
||||||
color_options.each do |color_option|
|
|
||||||
color_option = color_option.to_s
|
|
||||||
if color_option != ""
|
|
||||||
if !(color_option =~ /\d+/)
|
|
||||||
color_option = const_get("ANSI_ESCAPE_#{color_option.upcase}")
|
|
||||||
end
|
|
||||||
color_code += ";" + color_option
|
|
||||||
end
|
|
||||||
end
|
|
||||||
color_enabled? ? "\e[0#{color_code}m#{text}\e[0m" : text
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Checks if color output can be enabled.
|
||||||
|
#
|
||||||
|
# @return [Boolean] whether color is enabled or not
|
||||||
|
#
|
||||||
def color_enabled?
|
def color_enabled?
|
||||||
if @color_enabled.nil?
|
if @color_enabled.nil?
|
||||||
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||||
@ -102,9 +106,86 @@ module Guard
|
|||||||
@color_enabled = true
|
@color_enabled = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@color_enabled
|
@color_enabled
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Colorizes a text message. See the constant below for possible
|
||||||
|
# color_options parameters. You can pass :bright, a foreground
|
||||||
|
# and a background color.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# color('Hello World', :red, :bright)
|
||||||
|
#
|
||||||
|
# @param [String] the text to colorize
|
||||||
|
# @param [Array] color_options the color options
|
||||||
|
#
|
||||||
|
def color(text, *color_options)
|
||||||
|
color_code = ''
|
||||||
|
color_options.each do |color_option|
|
||||||
|
color_option = color_option.to_s
|
||||||
|
if color_option != ''
|
||||||
|
if !(color_option =~ /\d+/)
|
||||||
|
color_option = const_get("ANSI_ESCAPE_#{ color_option.upcase }")
|
||||||
|
end
|
||||||
|
color_code += ';' + color_option
|
||||||
|
end
|
||||||
|
end
|
||||||
|
color_enabled? ? "\e[0#{ color_code }m#{ text }\e[0m" : text
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# bright color
|
||||||
|
ANSI_ESCAPE_BRIGHT = '1'
|
||||||
|
|
||||||
|
# black foreground color
|
||||||
|
ANSI_ESCAPE_BLACK = '30'
|
||||||
|
|
||||||
|
# red foreground color
|
||||||
|
ANSI_ESCAPE_RED = '31'
|
||||||
|
|
||||||
|
# green foreground color
|
||||||
|
ANSI_ESCAPE_GREEN = '32'
|
||||||
|
|
||||||
|
# yellow foreground color
|
||||||
|
ANSI_ESCAPE_YELLOW = '33'
|
||||||
|
|
||||||
|
# blue foreground color
|
||||||
|
ANSI_ESCAPE_BLUE = '34'
|
||||||
|
|
||||||
|
# magenta foreground color
|
||||||
|
ANSI_ESCAPE_MAGENTA = '35'
|
||||||
|
|
||||||
|
# cyan foreground color
|
||||||
|
ANSI_ESCAPE_CYAN = '36'
|
||||||
|
|
||||||
|
# white foreground color
|
||||||
|
ANSI_ESCAPE_WHITE = '37'
|
||||||
|
|
||||||
|
# black background color
|
||||||
|
ANSI_ESCAPE_BGBLACK = '40'
|
||||||
|
|
||||||
|
# red background color
|
||||||
|
ANSI_ESCAPE_BGRED = '41'
|
||||||
|
|
||||||
|
# green background color
|
||||||
|
ANSI_ESCAPE_BGGREEN = '42'
|
||||||
|
|
||||||
|
# yellow background color
|
||||||
|
ANSI_ESCAPE_BGYELLOW = '43'
|
||||||
|
|
||||||
|
# blue background color
|
||||||
|
ANSI_ESCAPE_BGBLUE = '44'
|
||||||
|
|
||||||
|
# magenta background color
|
||||||
|
ANSI_ESCAPE_BGMAGENTA = '45'
|
||||||
|
|
||||||
|
# cyan background color
|
||||||
|
ANSI_ESCAPE_BGCYAN = '46'
|
||||||
|
|
||||||
|
# white background color
|
||||||
|
ANSI_ESCAPE_BGWHITE = '47'
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
module Guard
|
module Guard
|
||||||
VERSION = "0.7.0" unless defined? Guard::VERSION
|
unless defined? Guard::VERSION
|
||||||
|
# The current gem version of Guard
|
||||||
|
VERSION = '0.7.0'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
module Guard
|
module Guard
|
||||||
|
|
||||||
|
# The watcher defines a RegEx that will be matched against file system modifications.
|
||||||
|
# When a watcher matches a change, an optional action block is executed to enable
|
||||||
|
# processing the file system change result.
|
||||||
|
#
|
||||||
class Watcher
|
class Watcher
|
||||||
|
|
||||||
attr_accessor :pattern, :action
|
attr_accessor :pattern, :action
|
||||||
|
|
||||||
|
# Initialize a file watcher.
|
||||||
|
#
|
||||||
|
# @param [String, Regexp] pattern the pattern to be watched by the guard
|
||||||
|
# @param [Block] action the action to execute before passing the result to the Guard
|
||||||
|
#
|
||||||
def initialize(pattern, action = nil)
|
def initialize(pattern, action = nil)
|
||||||
@pattern, @action = pattern, action
|
@pattern, @action = pattern, action
|
||||||
@@warning_printed ||= false
|
@@warning_printed ||= false
|
||||||
@ -10,14 +21,25 @@ module Guard
|
|||||||
if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
|
if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
|
||||||
unless @@warning_printed
|
unless @@warning_printed
|
||||||
UI.info "*"*20 + "\nDEPRECATION WARNING!\n" + "*"*20
|
UI.info "*"*20 + "\nDEPRECATION WARNING!\n" + "*"*20
|
||||||
UI.info "You have strings in your Guardfile's watch patterns that seem to represent regexps.\nGuard matchs String with == and Regexp with Regexp#match.\nYou should either use plain String (without Regexp special characters) or real Regexp.\n"
|
UI.info <<-MSG
|
||||||
|
You have a string in your Guardfile watch patterns that seem to represent a Regexp.
|
||||||
|
Guard matches String with == and Regexp with Regexp#match.
|
||||||
|
You should either use plain String (without Regexp special characters) or real Regexp.
|
||||||
|
MSG
|
||||||
@@warning_printed = true
|
@@warning_printed = true
|
||||||
end
|
end
|
||||||
UI.info "\"#{@pattern}\" has been converted to #{Regexp.new(@pattern).inspect}\n"
|
|
||||||
|
UI.info "\"#{@pattern}\" has been converted to #{ Regexp.new(@pattern).inspect }\n"
|
||||||
@pattern = Regexp.new(@pattern)
|
@pattern = Regexp.new(@pattern)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Finds the files that matches a Guard.
|
||||||
|
#
|
||||||
|
# @param [Guard::Guard] guard the guard which watchers are used
|
||||||
|
# @param [Array<String>] files the changed files
|
||||||
|
# @return [Array<String>] the matched files
|
||||||
|
#
|
||||||
def self.match_files(guard, files)
|
def self.match_files(guard, files)
|
||||||
guard.watchers.inject([]) do |paths, watcher|
|
guard.watchers.inject([]) do |paths, watcher|
|
||||||
files.each do |file|
|
files.each do |file|
|
||||||
@ -30,10 +52,17 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
paths.flatten.map { |p| p.to_s }
|
paths.flatten.map { |p| p.to_s }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if a file would be matched by any of the Guards watchers.
|
||||||
|
#
|
||||||
|
# @param [Array<Guard::Guard>] guards the guards to use the watchers from
|
||||||
|
# @param [Array<String>] files the files to test
|
||||||
|
# @return [Boolean] Whether a file matches
|
||||||
|
#
|
||||||
def self.match_files?(guards, files)
|
def self.match_files?(guards, files)
|
||||||
guards.any? do |guard|
|
guards.any? do |guard|
|
||||||
guard.watchers.any? do |watcher|
|
guard.watchers.any? do |watcher|
|
||||||
@ -42,6 +71,11 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test the watchers pattern against a file.
|
||||||
|
#
|
||||||
|
# @param [String] file the file to test
|
||||||
|
# @return [Boolean] whether the given file is matched
|
||||||
|
#
|
||||||
def match_file?(file)
|
def match_file?(file)
|
||||||
if @pattern.is_a?(Regexp)
|
if @pattern.is_a?(Regexp)
|
||||||
file.match(@pattern)
|
file.match(@pattern)
|
||||||
@ -50,15 +84,25 @@ module Guard
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test if any of the files is the Guardfile.
|
||||||
|
#
|
||||||
|
# @param [Array<String>] the files to test
|
||||||
|
# @return [Boolean] whether one of these files is the Guardfile
|
||||||
|
#
|
||||||
def self.match_guardfile?(files)
|
def self.match_guardfile?(files)
|
||||||
files.any? { |file| "#{Dir.pwd}/#{file}" == Dsl.guardfile_path }
|
files.any? { |file| "#{ Dir.pwd }/#{ file }" == Dsl.guardfile_path }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Executes a watcher action.
|
||||||
|
#
|
||||||
|
# @param [String, MatchData] the matched path or the match from the Regex
|
||||||
|
# @return [String] the final paths
|
||||||
|
#
|
||||||
def call_action(matches)
|
def call_action(matches)
|
||||||
begin
|
begin
|
||||||
@action.arity > 0 ? @action.call(matches) : @action.call
|
@action.arity > 0 ? @action.call(matches) : @action.call
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
UI.error "Problem with watch action!\n#{e.message}\n\n#{e.backtrace.join("\n")}"
|
UI.error "Problem with watch action!\n#{ e.message }\n\n#{ e.backtrace.join("\n") }"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -220,16 +220,16 @@ describe Guard::Dsl do
|
|||||||
|
|
||||||
describe "#ignore_paths" do
|
describe "#ignore_paths" do
|
||||||
disable_user_config
|
disable_user_config
|
||||||
|
|
||||||
it "adds the paths to the listener's ignore_paths" do
|
it "adds the paths to the listener's ignore_paths" do
|
||||||
::Guard.stub!(:listener).and_return(mock('Listener'))
|
::Guard.stub!(:listener).and_return(mock('Listener'))
|
||||||
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
|
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
|
||||||
|
|
||||||
subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
|
subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
|
||||||
ignore_paths.should == ['faz', 'foo', 'bar']
|
ignore_paths.should == ['faz', 'foo', 'bar']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#group" do
|
describe "#group" do
|
||||||
disable_user_config
|
disable_user_config
|
||||||
|
|
||||||
|
@ -134,12 +134,12 @@ describe Guard::Listener do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "without watch_deletions" do
|
context "without watch_all_modifications" do
|
||||||
|
|
||||||
after { FileUtils.touch(file3) }
|
after { FileUtils.touch(file3) }
|
||||||
|
|
||||||
it "defaults to false" do
|
it "defaults to false" do
|
||||||
subject.instance_variable_get(:@watch_deletions).should eql false
|
subject.instance_variable_get(:@watch_all_modifications).should eql false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "it should not track deleted files" do
|
it "it should not track deleted files" do
|
||||||
@ -152,8 +152,8 @@ describe Guard::Listener do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with watch_deletions" do
|
context "with watch_all_modifications" do
|
||||||
subject { described_class.new(Dir.pwd, :deletions=>true) }
|
subject { described_class.new(Dir.pwd, :watch_all_modifications=>true) }
|
||||||
|
|
||||||
before :each do
|
before :each do
|
||||||
subject.timestamp_files
|
subject.timestamp_files
|
||||||
@ -166,7 +166,7 @@ describe Guard::Listener do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should be true when set" do
|
it "should be true when set" do
|
||||||
subject.instance_variable_get(:@watch_deletions).should eql true
|
subject.instance_variable_get(:@watch_all_modifications).should eql true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should track deleted files" do
|
it "should track deleted files" do
|
||||||
@ -256,7 +256,7 @@ describe Guard::Listener do
|
|||||||
|
|
||||||
describe "#ignore_paths" do
|
describe "#ignore_paths" do
|
||||||
it "defaults to the default ignore paths" do
|
it "defaults to the default ignore paths" do
|
||||||
subject.new.ignore_paths.should == Guard::Listener::DefaultIgnorePaths
|
subject.new.ignore_paths.should == Guard::Listener::DEFAULT_IGNORE_PATHS
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can be added to via :ignore_paths option" do
|
it "can be added to via :ignore_paths option" do
|
||||||
|
Loading…
Reference in New Issue
Block a user