Permalink
Browse files

Added Sass::Guard::Runner. Docs improved.

This takes care of compilitaion of the sass and writing the files.
  • Loading branch information...
1 parent 1a70136 commit c887ba40e2fcd7d6de10b78e9e8d04ede0f72931 @hawx hawx committed Oct 1, 2011
Showing with 175 additions and 97 deletions.
  1. +50 −93 lib/guard/sass.rb
  2. +15 −4 lib/guard/sass/formatter.rb
  3. +110 −0 lib/guard/sass/runner.rb
View
@@ -2,133 +2,90 @@
require 'guard/guard'
require 'guard/watcher'
-require 'sass'
-
module Guard
class Sass < Guard
+ autoload :Runner, 'guard/sass/runner'
autoload :Formatter, 'guard/sass/formatter'
DEFAULTS = {
- :output => 'css', # Output directory
- :notification => true, # Enable notifications?
- :shallow => false, # Output nested directories?
- :style => :nested, # Nested output
- :debug_info => false, # File and line number info for FireSass
- :noop => false, # Do no write output file
- :hide_success => false, # Do not show success message
+ :output => 'css',
+ :style => :nested,
+ :notification => true,
+ :shallow => false,
+ :debug_info => false,
+ :noop => false,
+ :hide_success => false,
:load_paths => Dir.glob('**/**').find_all {|i| File.directory?(i) }
}
- def initialize(watchers = [], options = {})
+ # @param watchers [Array<Guard::Watcher>]
+ # @param options [Hash]
+ # @option options [String] :input
+ # The input directory
+ # @option options [String] :output
+ # The output directory
+ # @option options [Array<String>] :load_paths
+ # List of directories you can @import from
+ # @option options [Boolean] :notification
+ # Whether to show notifications
+ # @option options [Boolean] :shallow
+ # Whether to output nested directories
+ # @option options [Boolean] :debug_info
+ # Whether to output file and line number info for FireSass
+ # @option options [Boolean] :noop
+ # Whether to run in "asset pipe" mode, no ouput, just validation
+ # @option options [Boolean] :hide_success
+ # Whether to hide all success messages
+ # @option options [Symbol] :style
+ # See http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#output_style
+ def initialize(watchers=[], options={})
if options[:input]
options[:output] = options[:input] unless options.has_key?(:output)
watchers << ::Guard::Watcher.new(%r{^#{options.delete(:input)}/(.+\.s[ac]ss)$})
end
options = DEFAULTS.merge(options)
-
- @formatter = Formatter.new(
- :notification => options[:notification],
- :show_success => !options[:hide_success]
- )
-
+ @runner = Runner.new(watchers, options)
super(watchers, options)
end
-
- # Builds the sass or scss. Determines engine to use by extension
- # of path given.
- #
- # @param file [String] path to file to build
- # @return [String] the output css
- #
- def build_sass(file)
- content = File.new(file).read
- # sass or scss?
- type = file[-4..-1].to_sym
- sass_options = {
- :syntax => type,
- :load_paths => options[:load_paths],
- :style => options[:style].to_sym,
- :debug_info => options[:debug_info],
- }
-
- ::Sass::Engine.new(content, sass_options).render
- end
-
- # Get the file path to output the css based on the file being
- # built.
- #
- # @param file [String] path to file being built
- # @return [String] path to file where output should be written
- #
- def get_output(file)
- folder = File.join ::Guard.listener.directory, options[:output]
-
- unless options[:shallow]
- watchers.product([file]).each do |watcher, file|
- if matches = file.match(watcher.pattern)
- if matches[1]
- folder = File.join(options[:output], File.dirname(matches[1])).gsub(/\/\.$/, '')
- break
- end
- end
- end
- end
-
- FileUtils.mkdir_p folder
- r = File.join folder, File.basename(file).split('.')[0]
- r << '.css'
- end
-
- def ignored?(path)
- File.basename(path)[0,1] == "_"
- end
-
# ================
# = Guard method =
# ================
# Build all files being watched
+ #
+ # @return [Boolean] No errors?
def run_all
- run_on_change(Watcher.match_files(self, Dir.glob(File.join('**', '[^_]*.s[ac]ss'))))
+ run_on_change(Watcher.match_files(self, Dir.glob(File.join('**', '*.*'))))
end
- # Build the files given
+ # Build the files given. If a 'partial' file is found (begins with '_') calls
+ # {#run_all} as we don't know which other files need to use it.
+ #
+ # @param paths [Array<String>]
+ # @return [Boolean] No errors?
def run_on_change(paths)
- partials = paths.select {|f| ignored?(f) }
+ partials = paths.select {|f| File.basename(f)[0,1] == "_" }
return run_all unless partials.empty?
-
- changed_files = paths.reject {|f| ignored?(f) }.map do |file|
- css_file = get_output(file)
- begin
- contents = build_sass(file)
- if contents
- File.open(css_file, 'w') {|f| f.write(contents) } unless options[:noop]
-
- message = options[:noop] ? "verified #{file}" : "compiled #{file} to #{css_file}"
- @formatter.success("-> #{message}", :notification => message)
- end
- css_file
- rescue ::Sass::SyntaxError => e
- @formatter.error(
- "Sass > #{e.sass_backtrace_str(file)}",
- :notification => (options[:noop] ? 'validation' : 'rebuild') + " of #{file} failed"
- )
-
- nil
- end
- end.compact
+
+ changed_files, success = @runner.run(paths)
notify changed_files
+ success
end
-
+
+ private
+
+ # Notify other guards about files that have been changed so that other guards can
+ # work on the changed files.
+ #
+ # @param changed_files [Array<String>]
def notify(changed_files)
::Guard.guards.each do |guard|
- next if guard == self
paths = Watcher.match_files(guard, changed_files)
- guard.run_on_change paths unless paths.empty?
+ guard.run_on_change(paths) unless paths.empty?
end
end
@@ -8,27 +8,38 @@ class Formatter
# Whether to show notifications
# @option otps [Boolean] :success
# Whether to print success messages
- #
def initialize(opts={})
@notification = opts.fetch(:notification, true)
@success = opts.fetch(:show_success, true)
end
+ # Show a success message and notification if successes are being shown.
+ #
+ # @param msg [String]
+ # @param opts [Hash]
def success(msg, opts={})
if @success
- ::Guard::UI.info(msg, opts.merge({:reset => true}))
+ ::Guard::UI.info(msg, ({:reset => true}).merge(opts))
notify(opts[:notification], :image => :success)
end
end
+ # Show an error message and notification.
+ #
+ # @param msg [String]
+ # @param opts [Hash]
def error(msg, opts={})
- ::Guard::UI.error(msg, opts.merge({:reset => true}))
+ ::Guard::UI.error(msg, ({:reset => true}).merge(opts))
notify(opts[:notification], :image => :failed)
end
+ # Show a system notification, if notifications are enabled.
+ #
+ # @param msg [String]
+ # @param opts [Hash] See http://rubydoc.info/github/guard/guard/master/Guard/Notifier.notify
def notify(msg, opts={})
if @notification
- ::Guard::Notifier.notify(msg, opts.merge({:title => "Guard::Sass"}))
+ ::Guard::Notifier.notify(msg, ({:title => "Guard::Sass"}).merge(opts))
end
end
View
@@ -0,0 +1,110 @@
+require 'sass'
+
+module Guard
+ class Sass
+
+ class Runner
+
+ attr_reader :options
+
+ # @param watchers [Array<Guard::Watcher>]
+ # @param options [Hash] See Guard::Sass::DEFAULTS for available options
+ def initialize(watchers, options={})
+ @watchers = watchers
+ @options = options
+ @formatter = Formatter.new(
+ :notification => options[:notification],
+ :show_success => !options[:hide_success]
+ )
+ end
+
+ # @param files [Array<String>]
+ # @return [Array<Array,Boolean>]
+ def run(files)
+ changed_files, errors = compile_files(files)
+ [changed_files, errors.empty?]
+ end
+
+ private
+
+ # @param files [Array<String>] Files to compile
+ # @return [Array<Array,Array>] The files which have been changed and an array
+ # of any error messages if any errors occurred.
+ def compile_files(files)
+ errors = []
+ changed_files = []
+
+ # Assume partials have been checked for previously, so no partials are included here
+ files.each do |file|
+ begin
+ css_file = write_file(compile(file), get_output_dir(file), file)
+
+ message = options[:noop] ? "verified #{file}" : "compiled #{file} to #{css_file}"
+ @formatter.success "-> #{message}", :notification => message
+
+ changed_files << css_file
+
+ rescue ::Sass::SyntaxError => e
+ message = (options[:noop] ? 'validation' : 'rebuild') + " of #{file} failed"
+ errors << message
+ @formatter.error "Sass > #{e.sass_backtrace_str(file)}", :notification => message
+ end
+ end
+
+ [changed_files.compact, errors]
+ end
+
+ # @param file [String] Path to sass/scss file to compile
+ # @return [String] Compiled css.
+ def compile(file)
+ content = IO.read(file)
+
+ sass_options = {
+ :syntax => file[-4..-1].to_sym,
+ :load_paths => options[:load_paths],
+ :style => options[:style],
+ :debug_info => options[:debug_info]
+ }
+
+ ::Sass::Engine.new(content, sass_options).render
+ end
+
+ # @param file [String]
+ # @return [String] Directory to write +file+ to
+ def get_output_dir(file)
+ folder = options[:output]
+
+ unless options[:shallow]
+ @watchers.product([file]).each do |watcher, file|
+ if matches = file.match(watcher.pattern)
+ if matches[1]
+ folder = File.join(options[:output], File.dirname(matches[1])).gsub(/\/\.$/, '')
+ break
+ end
+ end
+ end
+ end
+
+ folder
+ end
+
+ # Write file contents, creating directories where required.
+ #
+ # @param content [String] Contents of the file
+ # @param dir [String] Directory to write to
+ # @param file [String] Name of the file
+ # @return [String] Path of file written
+ def write_file(content, dir, file)
+ path = File.join(dir, File.basename(file.split('.')[0])) << '.css'
+
+ unless options[:noop]
+ FileUtils.mkdir_p(dir)
+ File.open(path, 'w') {|f| f.write(content) }
+ end
+
+ path
+ end
+
+ end
+ end
+end

0 comments on commit c887ba4

Please sign in to comment.