class Guard::Less

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method
# File lib/guard/less.rb, line 8
def initialize(options = {})
  defaults = {
    all_after_change: true,
    all_on_start: true,
    output: nil,
    import_paths: [],
    compress: false,
    patterns: [],
    yuicompress: false
  }
  super(defaults.merge(options))
end

Public Instance Methods

run(paths) click to toggle source
# File lib/guard/less.rb, line 40
def run(paths)
  directories = nested_directory_map(paths)

  directories.each do |destination, stylesheets|
    stylesheets.each do |lessfile|
      # Skip partials
      basename = File.basename(lessfile)
      next if basename[0, 1] == '_'

      cssfile = File.join(destination, basename.gsub(/\.less$/, '.css'))

      # Just in case
      if cssfile == lessfile
        Compat::UI.info "Guard::Less: Skipping #{lessfile} since output and source are the same"
      elsif mtime(cssfile) >= mtime_including_imports(lessfile)
        Compat::UI.info "Guard::Less: Skipping #{lessfile} because #{cssfile} is already up-to-date"
      else
        Compat::UI.info "Guard::Less: #{lessfile} -> #{cssfile}\n"
        FileUtils.mkdir_p(File.expand_path(destination))
        compile(lessfile, cssfile)
      end
    end
  end
end
run_all() click to toggle source

Call with Ctrl-/ signal This method should be principally used for long action like running all specs/tests/…

# File lib/guard/less.rb, line 28
def run_all
  Compat::UI.info 'Guard::Less: compiling all files'
  files = Dir.glob('**/*.*')
  paths = Compat.matching_files(self, files).uniq
  run(paths)
end
run_on_changes(paths) click to toggle source

Call on file(s) modifications

# File lib/guard/less.rb, line 36
def run_on_changes(paths)
  options[:all_after_change] ? run_all : run(paths)
end
start() click to toggle source
# File lib/guard/less.rb, line 21
def start
  Compat::UI.info "Guard::Less #{LessVersion::VERSION} is on the job!"
  run_all if options[:all_on_start]
end

Private Instance Methods

compile(lessfile, cssfile) click to toggle source

Parse the source lessfile and write to target cssfile

# File lib/guard/less.rb, line 68
def compile(lessfile, cssfile)
  import_paths = options[:import_paths].unshift(File.dirname(lessfile))
  parser = ::Less::Parser.new paths: import_paths, filename: lessfile
  File.open(lessfile, 'r') do |infile|
    File.open(cssfile, 'w') do |outfile|
      tree = parser.parse(infile.read)
      outfile << tree.to_css(compress: options[:compress], yuicompress: options[:yuicompress])
    end
  end
  true
rescue StandardError => e
  Compat::UI.info "Guard::Less: Compiling #{lessfile} failed with message: #{e.message}"
  false
end
mtime(file) click to toggle source

mtime checking borrowed from the old official LESS Rails plugin:

https://github.com/cloudhead/more
# File lib/guard/less.rb, line 107
def mtime(file)
  return 0 unless File.file?(file)
  File.mtime(file).to_i
end
mtime_including_imports(file) click to toggle source

consider imports for mtime just 1 level deep so we do not get any looping/nesting errors

# File lib/guard/less.rb, line 114
def mtime_including_imports(file)
  mtimes = [mtime(file)]
  File.readlines(file).each do |line|
    next unless line =~ /^\s*@import ['"]([^'"]+)/

    imported = File.join(File.dirname(file), Regexp.last_match[1])

    # complete path given ?
    mod_time = if imported =~ /\.le?ss$/
                 mtime(imported)
               else
                 # we need to add .less or .lss
                 [mtime("#{imported}.less"), mtime("#{imported}.lss")].max
               end
    mtimes << mod_time
  end
  mtimes.max
end
nested_directory_map(paths) click to toggle source

Creates a hash of changed files keyed by their target nested directory, which is based on either the original source directory or the :output option, plus the regex match group in a watcher like:

%r{^app/stylesheets/(.+\.less)$}
# File lib/guard/less.rb, line 88
def nested_directory_map(paths)
  directories = {}

  options[:patterns].product(paths).each do |pattern, path|
    next unless (matches = path.match(pattern))

    target = options[:output] || File.dirname(path)
    if (subpath = matches[1])
      target = File.join(target, File.dirname(subpath)).gsub(/\/\.$/, '')
    end

    (directories[target] ||= []) << path
  end

  directories
end