1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
|
require "guard/guardfile/evaluator"
require "guard/interactor"
require "guard/notifier"
require "guard/ui"
require "guard/watcher"
require "guard/deprecated/dsl" unless Guard::Config.new.strict?
require "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}. These are necessary
# to define the used Guard plugins and the file changes they are watching.
#
# You can optionally group the Guard plugins with the {#group} keyword and
# ignore and filter certain paths with the {#ignore} and {#filter} keywords.
#
# You can set your preferred system notification library with {#notification}
# and pass some optional configuration options for the library. If you don't
# configure a library, Guard will automatically pick one with default options
# (if you don't want notifications, specify `:off` as library). Please see
# {Notifier} for more information about the supported libraries.
#
# A more advanced DSL use is the {#callback} keyword that allows you to
# execute arbitrary code before or after any of the {Plugin#start},
# {Plugin#stop}, {Plugin#reload}, {Plugin#run_all},
# {Plugin#run_on_changes}, {Plugin#run_on_additions},
# {Plugin#run_on_modifications} and {Plugin#run_on_removals}
# Guard plugins 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` or `guardfile.rb` 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`.
#
# @see https://github.com/guard/guard/wiki/Guardfile-examples
#
class Dsl
Deprecated::Dsl.add_deprecated(self) unless Config.new.strict?
# Wrap exceptions during parsing Guardfile
class Error < RuntimeError
end
WARN_INVALID_LOG_LEVEL = "Invalid log level `%s` ignored. "\
"Please use either :debug, :info, :warn or :error."
WARN_INVALID_LOG_OPTIONS = "You cannot specify the logger options"\
" :only and :except at the same time."
# Set notification options for the system notifications.
# You can set multiple notifications, which allows you to show local
# system notifications and remote notifications with separate libraries.
# You can also pass `:off` as library to turn off notifications.
#
# @example Define multiple notifications
# notification :ruby_gntp
# notification :ruby_gntp, host: '192.168.1.5'
#
# @param [Symbol, String] notifier the name of the notifier to use
# @param [Hash] opts the notification library options
#
# @see Guard::Notifier for available notifier and its options.
#
def notification(notifier, opts = {})
Guard.state.session.guardfile_notification = { notifier.to_sym => opts }
end
# Sets the interactor options or disable the interactor.
#
# @example Pass options to the interactor
# interactor option1: 'value1', option2: 'value2'
#
# @example Turn off interactions
# interactor :off
#
# @param [Symbol, Hash] options either `:off` or a Hash with interactor
# options
#
def interactor(options)
# TODO: remove dependency on Interactor (let session handle this)
case options
when :off
Interactor.enabled = false
when Hash
Interactor.options = options
end
end
# Declares a group of Guard plugins to be run with `guard start --group
# group_name`.
#
# @example Declare two groups of Guard plugins
# group :backend do
# guard :spork
# guard :rspec
# end
#
# group :frontend do
# guard :passenger
# guard :livereload
# end
#
# @param [Symbol, String, Array<Symbol, String>] name the group name called
# from the CLI
# @param [Hash] options the options accepted by the group
# @yield a block where you can declare several Guard plugins
#
# @see Group
# @see Guard.add_group
# @see #guard
#
def group(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
groups = args
groups.each do |group|
next unless group.to_sym == :all
fail ArgumentError, "'all' is not an allowed group name!"
end
if block_given?
groups.each do |group|
# TODO: let groups be added *after* evaluation
Guard.state.session.groups.add(group, options)
end
@current_groups ||= []
@current_groups.push(groups)
yield
@current_groups.pop
else
UI.error \
"No Guard plugins found in the group '#{ groups.join(', ') }',"\
" please add at least one."
end
end
# Declares a Guard plugin 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 without `watch` patterns
# guard :rspec
#
# @example Declare a Guard with a `watch` pattern
# guard :rspec do
# watch %r{.*_spec.rb}
# end
#
# @param [String] name the Guard plugin name
# @param [Hash] options the options accepted by the Guard plugin
# @yield a block where you can declare several watch patterns and actions
#
# @see Plugin
# @see Guard.add_plugin
# @see #watch
# @see #group
#
def guard(name, options = {})
@plugin_options = options.merge(watchers: [], callbacks: [])
yield if block_given?
@current_groups ||= []
groups = @current_groups && @current_groups.last || [:default]
groups.each do |group|
opts = @plugin_options.merge(group: group)
# TODO: let plugins be added *after* evaluation
Guard.state.session.plugins.add(name, opts)
end
@plugin_options = nil
end
# Defines 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}) do |m|
# 'spec/acceptance/#{m[1]}s_spec.rb'
# end
# end
#
# @example Declare global watchers outside of a Guard
# watch(%r{^(.+)$}) { |m| puts "#{m[1]} changed." }
#
# @param [String, Regexp] pattern the pattern that Guard must watch for
# modification
#
# @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)
#
# @see Guard::Watcher
# @see #guard
#
def watch(pattern, &action)
# Allow watches in the global scope (to execute arbitrary commands) by
# building a generic Guard::Plugin.
@plugin_options ||= nil
return guard(:plugin) { watch(pattern, &action) } unless @plugin_options
@plugin_options[:watchers] << Watcher.new(pattern, action)
end
# Defines a callback to execute arbitrary code before or after any of
# the `start`, `stop`, `reload`, `run_all`, `run_on_changes`,
# `run_on_additions`, `run_on_modifications` and `run_on_removals` plugin
# method.
#
# @example Add callback before the `reload` action.
# callback(:reload_begin) { puts "Let's reload!" }
#
# @example Add callback before the `start` and `stop` actions.
#
# my_lambda = lambda do |plugin, event, *args|
# puts "Let's #{event} #{plugin} with #{args}!"
# end
#
# callback(my_lambda, [:start_begin, :start_end])
#
# @param [Array] args the callback arguments
# @yield a callback block
#
def callback(*args, &block)
@plugin_options ||= nil
fail "callback must be called within a guard block" unless @plugin_options
block, events = if args.size > 1
# block must be the first argument in that case, the
# yielded block is ignored
args
else
[block, args[0]]
end
@plugin_options[:callbacks] << { events: events, listener: block }
end
# Ignores certain paths globally.
#
# @example Ignore some paths
# ignore %r{^ignored/path/}, /man/
#
# @param [Regexp] regexps a pattern (or list of patterns) for ignoring paths
#
def ignore(*regexps)
# TODO: use guardfile results class
Guard.state.session.guardfile_ignore = regexps
end
# TODO: deprecate
alias filter ignore
# Replaces ignored paths globally
#
# @example Ignore only these paths
# ignore! %r{^ignored/path/}, /man/
#
# @param [Regexp] regexps a pattern (or list of patterns) for ignoring paths
#
def ignore!(*regexps)
@ignore_regexps ||= []
@ignore_regexps << regexps
# TODO: use guardfile results class
Guard.state.session.guardfile_ignore_bang = @ignore_regexps
end
# TODO: deprecate
alias filter! ignore!
# Configures the Guard logger.
#
# * Log level must be either `:debug`, `:info`, `:warn` or `:error`.
# * Template supports the following placeholders: `:time`, `:severity`,
# `:progname`, `:pid`, `:unit_of_work_id` and `:message`.
# * Time format directives are the same as `Time#strftime` or
# `:milliseconds`.
# * The `:only` and `:except` options must be a `RegExp`.
#
# @example Set the log level
# logger level: :warn
#
# @example Set a custom log template
# logger template: '[Guard - :severity - :progname - :time] :message'
#
# @example Set a custom time format
# logger time_format: '%h'
#
# @example Limit logging to a Guard plugin
# logger only: :jasmine
#
# @example Log all but not the messages from a specific Guard plugin
# logger except: :jasmine
#
# @param [Hash] options the log options
# @option options [String, Symbol] level the log level
# @option options [String] template the logger template
# @option options [String, Symbol] time_format the time format
# @option options [Regexp] only show only messages from the matching Guard
# plugin
# @option options [Regexp] except does not show messages from the matching
# Guard plugin
#
def logger(options)
if options[:level]
options[:level] = options[:level].to_sym
unless [:debug, :info, :warn, :error].include? options[:level]
UI.warning(format(WARN_INVALID_LOG_LEVEL, options[:level]))
options.delete :level
end
end
if options[:only] && options[:except]
UI.warning WARN_INVALID_LOG_OPTIONS
options.delete :only
options.delete :except
end
# Convert the :only and :except options to a regular expression
[:only, :except].each do |name|
next unless options[name]
list = [].push(options[name]).flatten.map do |plugin|
Regexp.escape(plugin.to_s)
end
options[name] = Regexp.new(list.join("|"), Regexp::IGNORECASE)
end
UI.options = UI.options.merge(options)
end
# Sets the default scope on startup
#
# @example Scope Guard to a single group
# scope group: :frontend
#
# @example Scope Guard to multiple groups
# scope groups: [:specs, :docs]
#
# @example Scope Guard to a single plugin
# scope plugin: :test
#
# @example Scope Guard to multiple plugins
# scope plugins: [:jasmine, :rspec]
#
# @param [Hash] scope the scope for the groups and plugins
#
def scope(scope = {})
# TODO: use a Guardfile::Results class
Guard.state.session.guardfile_scope(scope)
end
def evaluate(contents, filename, lineno) # :nodoc
instance_eval(contents, filename.to_s, lineno)
rescue StandardError, ScriptError => e
prefix = "\n\t(dsl)> "
cleaned_backtrace = _cleanup_backtrace(e.backtrace)
backtrace = "#{prefix}#{cleaned_backtrace.join(prefix)}"
msg = "Invalid Guardfile, original error is: \n\n%s, \nbacktrace: %s"
raise Error, format(msg, e, backtrace)
end
# Sets the directories to pass to Listen
#
# @example watch only given directories
# directories %w(lib specs)
#
# @param [Array] directories directories for Listen to watch
#
def directories(directories)
directories.each do |dir|
fail "Directory #{dir.inspect} does not exist!" unless Dir.exist?(dir)
end
Guard.state.session.watchdirs = directories
end
# Sets Guard to clear the screen before every task is run
#
# @example switching clearing the screen on
# clearing(:on)
#
# @param [Symbol] on ':on' to turn on, ':off' (default) to turn off
#
def clearing(on)
Guard.state.session.clearing(on == :on)
end
private
def _cleanup_backtrace(backtrace)
dirs = { File.realpath(Dir.pwd) => ".", }
gem_env = ENV["GEM_HOME"] || ""
dirs[gem_env] = "$GEM_HOME" unless gem_env.empty?
gem_paths = (ENV["GEM_PATH"] || "").split(File::PATH_SEPARATOR)
gem_paths.each_with_index do |path, index|
dirs[path] = "$GEM_PATH[#{index}]"
end
backtrace.dup.map do |raw_line|
path = nil
symlinked_path = raw_line.split(":").first
begin
path = raw_line.sub(symlinked_path, File.realpath(symlinked_path))
dirs.detect { |dir, name| path.sub!(File.realpath(dir), name) }
path
rescue Errno::ENOENT
path || symlinked_path
end
end
end
end
end
|