master/lib/guard/dsl.rb

355 lines
12 KiB
Ruby
Raw Normal View History

2010-10-03 21:00:33 +00:00
module Guard
2011-09-20 09:11:40 +00:00
# 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
#
2010-10-03 21:00:33 +00:00
class Dsl
class << self
2011-09-20 09:11:40 +00:00
2011-05-28 16:18:45 +00:00
@@options = nil
2011-09-20 09:11:40 +00:00
# Evaluate the DSL methods in the `Guardfile`.
#
# @param [Hash] options the Guard options
2011-09-20 12:26:27 +00:00
# @option options [Array<Symbol,String>] groups the groups to evaluate
2011-09-20 09:23:37 +00:00
# @option options [String] guardfile the path to a valid Guardfile
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile
2011-09-20 09:11:40 +00:00
# @raise [ArgumentError] when options are not a Hash
#
2011-09-20 12:26:27 +00:00
def evaluate_guardfile(options = {})
2011-09-20 09:11:40 +00:00
raise ArgumentError.new('No option hash passed to evaluate_guardfile!') unless options.is_a?(Hash)
@@options = options.dup
2011-09-20 09:11:40 +00:00
fetch_guardfile_contents
instance_eval_guardfile(guardfile_contents_with_user_config)
2011-09-20 09:11:40 +00:00
UI.error 'No guards found in Guardfile, please add at least one.' if !::Guard.guards.nil? && ::Guard.guards.empty?
end
2011-09-20 09:11:40 +00:00
# Reevaluate the Guardfile to update the current Guard configuration
# when the `Guardfile` has been changed after Guard is started.
#
def reevaluate_guardfile
::Guard.guards.clear
@@options.delete(:guardfile_contents)
Dsl.evaluate_guardfile(@@options)
2011-09-20 09:11:40 +00:00
msg = 'Guardfile has been re-evaluated.'
UI.info(msg)
Notifier.notify(msg)
end
2011-09-20 09:11:40 +00:00
# Evaluate the content of the `Guardfile`.
#
# @param [String] contents the content to evaluate.
#
def instance_eval_guardfile(contents)
2011-09-20 09:11:40 +00:00
new.instance_eval(contents, @@options[:guardfile_path], 1)
rescue
UI.error "Invalid Guardfile, original error is:\n#{ $! }"
exit 1
end
2011-09-20 09:11:40 +00:00
# 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)
2011-09-20 09:11:40 +00:00
guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
end
2011-09-20 09:11:40 +00:00
# Read the current `Guardfile` content.
#
# @param [String] the path to the Guardfile
#
def read_guardfile(guardfile_path)
2011-09-20 09:11:40 +00:00
@@options[:guardfile_path] = guardfile_path
@@options[:guardfile_contents] = File.read(guardfile_path)
rescue
UI.error("Error reading file #{ guardfile_path }")
exit 1
end
2011-09-20 12:26:27 +00:00
# Get the content to evaluate and stores it into
# the options as :guardfile_contents.
2011-09-20 09:11:40 +00:00
#
def fetch_guardfile_contents
if @@options[:guardfile_contents]
2011-09-20 09:11:40 +00:00
UI.info 'Using inline Guardfile.'
@@options[:guardfile_path] = 'Inline Guardfile'
elsif @@options[:guardfile]
if File.exist?(@@options[:guardfile])
read_guardfile(@@options[:guardfile])
2011-09-20 09:11:40 +00:00
UI.info "Using Guardfile at #{ @@options[:guardfile] }."
else
2011-09-20 09:11:40 +00:00
UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
exit 1
end
else
if File.exist?(guardfile_default_path)
read_guardfile(guardfile_default_path)
else
2011-09-20 09:11:40 +00:00
UI.error 'No Guardfile found, please create one with `guard init`.'
exit 1
end
end
unless guardfile_contents_usable?
2011-09-20 09:11:40 +00:00
UI.error "The command file(#{ @@options[:guardfile] }) seems to be empty."
exit 1
end
end
2011-09-20 09:11:40 +00:00
# Get the content of the `Guardfile`.
#
# @return [String] the Guardfile content
#
def guardfile_contents
2011-09-20 09:11:40 +00:00
@@options ? @@options[:guardfile_contents] : ''
end
2011-09-20 09:11:40 +00:00
# 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
config = File.read(user_config_path) if File.exist?(user_config_path)
[guardfile_contents, config].join("\n")
end
2011-09-20 09:11:40 +00:00
# Get the file path to the project `Guardfile`.
#
# @return [String] the path to the Guardfile
#
def guardfile_path
2011-09-20 09:11:40 +00:00
@@options ? @@options[:guardfile_path] : ''
end
2011-09-20 09:11:40 +00:00
# Tests if the current `Guardfile` content is usable.
#
# @return [Boolean] if the Guardfile is usable
#
def guardfile_contents_usable?
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # smallest guard-definition
end
2011-09-20 09:11:40 +00:00
# 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
2011-05-25 18:23:02 +00:00
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
2011-05-24 21:32:02 +00:00
end
2011-09-20 09:11:40 +00:00
private
2011-05-25 19:08:11 +00:00
2011-09-20 09:11:40 +00:00
# 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
#
2011-05-24 21:32:02 +00:00
def local_guardfile_path
2011-09-20 09:11:40 +00:00
File.join(Dir.pwd, 'Guardfile')
2011-05-24 21:32:02 +00:00
end
2011-09-20 09:11:40 +00:00
# The path to the `.Guardfile` that is located at
# the users home directory.
#
# @param [String] the path to ~/.Guardfile
#
2011-05-24 21:32:02 +00:00
def home_guardfile_path
2011-09-20 09:11:40 +00:00
File.expand_path(File.join('~', '.Guardfile'))
end
2011-09-20 09:11:40 +00:00
# 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
2011-09-20 09:11:40 +00:00
File.expand_path(File.join('~', '.guard.rb'))
end
2010-10-03 21:00:33 +00:00
end
2011-09-20 09:49:05 +00:00
# Declares a group of guards to be run with `guard start --group group_name`.
2011-09-20 09:11:40 +00:00
#
# @example Declare two groups of Guards
# group 'backend' do
# guard 'spork'
# guard 'rspec'
# end
#
# group 'frontend' do
# guard 'passenger'
# guard 'livereload'
# end
#
2011-09-20 12:26:27 +00:00
# @param [Symbol, String] name the group's name called from the CLI
2011-09-20 09:11:40 +00:00
# @yield a block where you can declare several guards
#
# @see Dsl#guard
2011-09-20 09:49:05 +00:00
# @see Guard::DslDescriber
2011-09-20 09:11:40 +00:00
#
2011-09-20 09:49:05 +00:00
def group(name)
@groups = @@options[:group] || []
2011-09-20 09:11:40 +00:00
name = name.to_sym
2011-09-20 12:26:27 +00:00
if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
@current_group = name
2011-09-20 09:49:05 +00:00
yield if block_given?
@current_group = nil
end
2010-10-07 20:37:30 +00:00
end
2011-09-20 09:11:40 +00:00
# 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
2011-09-20 09:49:05 +00:00
# @see Guard::DslDescriber
2011-09-20 09:11:40 +00:00
#
def guard(name, options = {})
@watchers = []
@callbacks = []
2011-09-20 09:11:40 +00:00
yield if block_given?
options.update(:group => (@current_group || :default))
::Guard.add_guard(name.to_s.downcase.to_sym, @watchers, @callbacks, options)
2010-10-03 21:00:33 +00:00
end
2011-09-20 09:11:40 +00:00
# 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)
#
2010-10-03 21:00:33 +00:00
def watch(pattern, &action)
2010-10-07 20:37:30 +00:00
@watchers << ::Guard::Watcher.new(pattern, action)
end
2011-09-20 09:11:40 +00:00
# Define a callback to execute arbitary code before or after any of
# the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method.
#
2011-09-20 09:13:12 +00:00
# @param [Array] args the callback arguments
2011-09-20 09:11:40 +00:00
# @yield a block with listeners
#
def callback(*args, &listener)
listener, events = args.size > 1 ? args : [listener, args[0]]
@callbacks << { :events => events, :listener => listener }
2010-10-03 21:00:33 +00:00
end
2011-09-20 09:11:40 +00:00
# Ignore certain paths globally.
#
# @example Ignore some paths
# ignore_paths .git, .svn
#
2011-09-20 09:13:12 +00:00
# @param [Array] paths the list of paths to ignore
2011-09-20 09:11:40 +00:00
#
2011-09-01 11:30:25 +00:00
def ignore_paths(*paths)
2011-09-20 09:11:40 +00:00
UI.info "Ignoring paths: #{ paths.join(', ') }"
2011-09-01 11:30:25 +00:00
::Guard.listener.ignore_paths.push(*paths)
end
2010-10-03 21:00:33 +00:00
end
end