├── test ├── dummy_app │ ├── tmp │ │ ├── .gitkeep │ │ └── cache │ │ │ └── .gitkeep │ ├── app │ │ └── assets │ │ │ ├── stylesheets │ │ │ ├── frameworks │ │ │ │ └── bootstrap │ │ │ │ │ ├── variables.less │ │ │ │ │ └── mixins.less │ │ │ ├── basics.css.less │ │ │ └── helpers.css.less │ │ │ └── images │ │ │ ├── 1x1.png │ │ │ └── rails.png │ ├── vendor │ │ └── assets │ │ │ └── stylesheets │ │ │ └── vendored.less │ └── init.rb ├── cases │ ├── generators_spec.rb │ ├── railtie_spec.rb │ ├── basics_spec.rb │ └── helpers_spec.rb └── spec_helper.rb ├── lib ├── less-rails.rb ├── less │ ├── rails │ │ ├── version.rb │ │ ├── import_processor.rb │ │ ├── template_handlers.rb │ │ ├── railtie.rb │ │ └── helpers.rb │ └── rails.rb └── rails │ └── generators │ └── less │ ├── assets │ ├── templates │ │ └── stylesheet.css.less │ └── assets_generator.rb │ └── scaffold │ └── scaffold_generator.rb ├── .gitignore ├── Gemfile ├── Appraisals ├── gemfiles ├── rails31.gemfile ├── rails32.gemfile ├── rails40.gemfile ├── rails32.gemfile.lock ├── rails31.gemfile.lock └── rails40.gemfile.lock ├── Guardfile ├── .travis.yml ├── Rakefile ├── MIT-LICENSE ├── less-rails.gemspec ├── CHANGELOG.md └── README.md /test/dummy_app/tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy_app/tmp/cache/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/less-rails.rb: -------------------------------------------------------------------------------- 1 | require 'less/rails' 2 | -------------------------------------------------------------------------------- /lib/less/rails/version.rb: -------------------------------------------------------------------------------- 1 | module Less 2 | module Rails 3 | VERSION = "2.2.6" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | *.log 6 | tmp/* 7 | test/dummy_app/tmp/cache/assets/* -------------------------------------------------------------------------------- /test/dummy_app/app/assets/stylesheets/frameworks/bootstrap/variables.less: -------------------------------------------------------------------------------- 1 | 2 | @variableColor: #424242; 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/dummy_app/vendor/assets/stylesheets/vendored.less: -------------------------------------------------------------------------------- 1 | 2 | .vendored(@radius: 10px) { 3 | border-radius: @radius; 4 | } 5 | -------------------------------------------------------------------------------- /test/dummy_app/app/assets/images/1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjia/less-rails/master/test/dummy_app/app/assets/images/1x1.png -------------------------------------------------------------------------------- /test/dummy_app/app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengjia/less-rails/master/test/dummy_app/app/assets/images/rails.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem "therubyracer", "~> 0.10.0", :require => nil, :platforms => :ruby 6 | gem "therubyrhino", "~> 1.73.3", :require => nil, :platforms => :jruby 7 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | 2 | appraise 'rails31' do 3 | gem 'rails', '~> 3.1.0' 4 | end 5 | 6 | appraise 'rails32' do 7 | gem 'rails', '~> 3.2.0' 8 | end 9 | 10 | appraise 'rails40' do 11 | gem 'rails', :github => 'rails/rails' 12 | end 13 | -------------------------------------------------------------------------------- /lib/rails/generators/less/assets/templates/stylesheet.css.less: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the <%= name %> controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Less here: http://lesscss.org/ 4 | -------------------------------------------------------------------------------- /test/dummy_app/app/assets/stylesheets/frameworks/bootstrap/mixins.less: -------------------------------------------------------------------------------- 1 | 2 | @import "frameworks/bootstrap/variables"; 3 | 4 | .radiused(@radius: 5px) { 5 | border-radius: @radius; 6 | } 7 | 8 | .variableColored() { 9 | color: @variableColor; 10 | } 11 | -------------------------------------------------------------------------------- /gemfiles/rails31.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "therubyracer", "~> 0.10.0", :require=>nil, :platforms=>:ruby 6 | gem "therubyrhino", "~> 1.73.3", :require=>nil, :platforms=>:jruby 7 | gem "rails", "~> 3.1.0" 8 | 9 | gemspec :path=>"../" -------------------------------------------------------------------------------- /gemfiles/rails32.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "therubyracer", "~> 0.10.0", :require=>nil, :platforms=>:ruby 6 | gem "therubyrhino", "~> 1.73.3", :require=>nil, :platforms=>:jruby 7 | gem "rails", "~> 3.2.0" 8 | 9 | gemspec :path=>"../" -------------------------------------------------------------------------------- /gemfiles/rails40.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "therubyracer", "~> 0.10.0", :require=>nil, :platforms=>:ruby 6 | gem "therubyrhino", "~> 1.73.3", :require=>nil, :platforms=>:jruby 7 | gem "rails", :github=>"rails/rails" 8 | 9 | gemspec :path=>"../" -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'minitest' do 2 | watch(%r|^lib/less/rails/(.*)\.rb|) { |m| "test/cases/#{m[1]}_spec.rb" } 3 | # watch(%r|^lib/less/rails\.rb|) { "test/cases" } 4 | # watch(%r|^test/dummy_app/.*|) { "test/cases" } 5 | watch(%r|^test/spec_helper\.rb|) { "test/cases" } 6 | watch(%r|^test/cases/(.*)_spec\.rb|) 7 | end 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.8.7 3 | - 1.9.3 4 | - 2.0.0 5 | - jruby-18mode 6 | - jruby-19mode 7 | gemfile: 8 | - gemfiles/rails31.gemfile 9 | - gemfiles/rails32.gemfile 10 | - gemfiles/rails40.gemfile 11 | matrix: 12 | exclude: 13 | - rvm: 1.8.7 14 | gemfile: gemfiles/rails40.gemfile 15 | - rvm: jruby-18mode 16 | gemfile: gemfiles/rails40.gemfile 17 | - rvm: jruby-19mode 18 | gemfile: gemfiles/rails40.gemfile 19 | -------------------------------------------------------------------------------- /lib/less/rails.rb: -------------------------------------------------------------------------------- 1 | module Less 2 | module Rails 3 | end 4 | end 5 | 6 | require 'less' 7 | require 'rails' 8 | require 'tilt' 9 | require 'sprockets' 10 | begin 11 | require 'sprockets/railtie' 12 | rescue LoadError 13 | require 'sprockets/rails/railtie' 14 | end 15 | 16 | require 'less/rails/version' 17 | require 'less/rails/helpers' 18 | require 'less/rails/template_handlers' 19 | require 'less/rails/import_processor' 20 | require 'less/rails/railtie' 21 | -------------------------------------------------------------------------------- /test/dummy_app/app/assets/stylesheets/basics.css.less: -------------------------------------------------------------------------------- 1 | 2 | // Variables 3 | @color: #4D926F; 4 | #test-variable { color: @color; } 5 | 6 | // Mixins 7 | .bordered { border: 1px solid black; } 8 | #test-mixin span { .bordered; } 9 | 10 | // Frameworks 11 | @import "frameworks/bootstrap/mixins"; 12 | #test-radiused { .radiused; } 13 | #test-variable-colored { .variableColored; } 14 | 15 | // Vendored 16 | @import "vendored"; 17 | #test-vendored { .vendored; } 18 | -------------------------------------------------------------------------------- /lib/rails/generators/less/assets/assets_generator.rb: -------------------------------------------------------------------------------- 1 | require "rails/generators/named_base" 2 | 3 | module Less 4 | module Generators 5 | class AssetsGenerator < ::Rails::Generators::NamedBase 6 | 7 | source_root File.expand_path '../templates', __FILE__ 8 | 9 | def copy_less 10 | template 'stylesheet.css.less', File.join('app/assets/stylesheets', class_path, "#{file_name}.css.less") 11 | end 12 | 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/gem_tasks' 3 | require 'rake/testtask' 4 | require 'appraisal' 5 | 6 | Rake::TestTask.new do |t| 7 | t.libs = ['lib','test'] 8 | t.test_files = Dir.glob("test/**/*_spec.rb").sort 9 | t.verbose = true 10 | end 11 | 12 | task :default => [:test] 13 | 14 | desc "Setup Appraisal." 15 | task 'appraisal:setup' do 16 | Rake::Task['appraisal:cleanup'].invoke 17 | Rake::Task['appraisal:gemfiles'].invoke 18 | Rake::Task['appraisal:install'].invoke 19 | end 20 | -------------------------------------------------------------------------------- /lib/rails/generators/less/scaffold/scaffold_generator.rb: -------------------------------------------------------------------------------- 1 | require "less" 2 | require "rails/generators/named_base" 3 | require "rails/generators/rails/scaffold/scaffold_generator" 4 | 5 | module Less 6 | module Generators 7 | class ScaffoldGenerator < ::Rails::Generators::NamedBase 8 | 9 | def copy_stylesheet 10 | file = File.join ::Rails::Generators::ScaffoldGenerator.source_root, 'scaffold.css' 11 | css = Less::Parser.new.parse(File.read(file)).to_css 12 | create_file "app/assets/stylesheets/scaffolds.css.less", css 13 | end 14 | 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy_app/init.rb: -------------------------------------------------------------------------------- 1 | require 'sprockets/railtie' 2 | require 'action_controller/railtie' 3 | require 'action_view/railtie' 4 | require 'action_view/base' 5 | require 'action_controller/base' 6 | 7 | module Dummy 8 | class Application < ::Rails::Application 9 | 10 | config.root = File.join __FILE__, '..' 11 | config.active_support.deprecation = :stderr 12 | config.cache_store = :memory_store 13 | config.consider_all_requests_local = true 14 | config.eager_load = false 15 | 16 | config.assets.enabled = true 17 | config.assets.cache_store = [:file_store, "#{config.root}/tmp/cache/assets/"] 18 | 19 | end 20 | end 21 | 22 | Dummy::Application.initialize! 23 | -------------------------------------------------------------------------------- /test/dummy_app/app/assets/stylesheets/helpers.css.less: -------------------------------------------------------------------------------- 1 | 2 | .rails { 3 | asset-path: asset-path("rails.png"); 4 | asset-url: asset-url("rails.png"); 5 | image-path: image-path("rails.png"); 6 | image-url: image-url("rails.png"); 7 | video-path: video-path("rails.mp4"); 8 | video-url: video-url("rails.mp4"); 9 | audio-path: audio-path("rails.mp3"); 10 | audio-url: audio-url("rails.mp3"); 11 | font-path: font-path("rails.ttf"); 12 | font-url: font-url("rails.ttf"); 13 | javascript-path: javascript-path("rails.js"); 14 | javascript-url: javascript-url("rails.js"); 15 | stylesheet-path: stylesheet-path("rails.css"); 16 | stylesheet-url: stylesheet-url("rails.css"); 17 | asset-data-url: asset-data-url("1x1.png"); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /test/cases/generators_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rails/generators/less/assets/assets_generator' 3 | require 'rails/generators/less/scaffold/scaffold_generator' 4 | 5 | class AssetsGeneratorSpec < Less::Rails::GeneratorSpec 6 | 7 | it 'should generate a posts.css.less file' do 8 | run_generator ['posts'] 9 | assert_file 'app/assets/stylesheets/posts.css.less' do |contents| 10 | contents.must_match %r{Place all the styles related to the posts controller here} 11 | contents.must_match %r{You can use Less here} 12 | end 13 | end 14 | 15 | end 16 | 17 | class ScaffoldGeneratorSpec < Less::Rails::GeneratorSpec 18 | 19 | it 'should parse and copy the scaffold to a less file' do 20 | run_generator ['posts'] 21 | assert_file 'app/assets/stylesheets/scaffolds.css.less' do |contents| 22 | contents.must_match %r{background-color: #fff} 23 | end 24 | end 25 | 26 | end 27 | 28 | -------------------------------------------------------------------------------- /lib/less/rails/import_processor.rb: -------------------------------------------------------------------------------- 1 | module Less 2 | module Rails 3 | class ImportProcessor < Tilt::Template 4 | 5 | IMPORT_SCANNER = /@import\s*['"]([^'"]+)['"]\s*;/.freeze 6 | 7 | def prepare 8 | end 9 | 10 | def evaluate(scope, locals, &block) 11 | depend_on scope, data 12 | data 13 | end 14 | 15 | def depend_on(scope, data) 16 | import_paths = data.scan(IMPORT_SCANNER).flatten.compact.uniq 17 | import_paths.each do |path| 18 | pathname = begin 19 | scope.resolve(path) 20 | rescue Sprockets::FileNotFound 21 | nil 22 | end 23 | scope.depend_on(path) if pathname && pathname.to_s.ends_with?('.less') 24 | depend_on scope, File.read(pathname) if pathname 25 | end 26 | data 27 | end 28 | 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Ken Collins 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /less-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "less/rails/version" 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = "less-rails" 7 | gem.version = Less::Rails::VERSION 8 | gem.authors = ["Ken Collins"] 9 | gem.email = ["ken@metaskills.net"] 10 | gem.homepage = "http://github.com/metaskills/less-rails" 11 | gem.summary = %q{The dynamic stylesheet language for the Rails asset pipeline.} 12 | gem.description = %q{The dynamic stylesheet language for the Rails asset pipeline. Allows other gems to extend Less load path.} 13 | gem.files = `git ls-files`.split("\n") 14 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 15 | gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 16 | gem.require_paths = ["lib"] 17 | gem.add_runtime_dependency 'less', '~> 2.2.0' 18 | gem.add_runtime_dependency 'actionpack', '>= 3.1' 19 | gem.add_development_dependency 'appraisal' 20 | gem.add_development_dependency 'minitest' 21 | gem.add_development_dependency 'guard-minitest' 22 | gem.add_development_dependency 'rails' 23 | end 24 | -------------------------------------------------------------------------------- /lib/less/rails/template_handlers.rb: -------------------------------------------------------------------------------- 1 | module Less 2 | module Rails 3 | class LessTemplate < Tilt::LessTemplate 4 | 5 | self.default_mime_type = 'text/css' 6 | 7 | include Helpers 8 | 9 | TO_CSS_KEYS = [:compress, :optimization, :silent, :color] 10 | 11 | def prepare 12 | end 13 | 14 | def evaluate(scope, locals, &block) 15 | @output ||= begin 16 | Less.Parser['scope'] = scope 17 | parser = ::Less::Parser.new config_to_less_parser_options(scope) 18 | engine = parser.parse(data) 19 | engine.to_css config_to_css_options(scope) 20 | end 21 | end 22 | 23 | protected 24 | 25 | def config_to_less_parser_options(scope) 26 | paths = config_paths(scope) + scope.environment.paths 27 | {:filename => eval_file, :line => line, :paths => paths} 28 | end 29 | 30 | def config_to_css_options(scope) 31 | Hash[config_from_rails(scope).each.to_a].slice *TO_CSS_KEYS 32 | end 33 | 34 | def config_paths(scope) 35 | config_from_rails(scope)[:paths] 36 | end 37 | 38 | def config_from_rails(scope) 39 | scope.environment.context_class.less_config 40 | end 41 | 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/cases/railtie_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class RailtieSpec < Less::Rails::Spec 4 | 5 | describe 'config' do 6 | 7 | it 'must have a less ordered hash' do 8 | dummy_config.less.must_be_instance_of ActiveSupport::OrderedOptions 9 | end 10 | 11 | it 'must have an array for paths' do 12 | dummy_config.less.paths.must_be_kind_of Array 13 | end 14 | 15 | it 'must have an options hash passed down to the #to_css method' do 16 | basic_compressed_match = /#test-variable\{color:#4d926f;\}/ 17 | dummy_config.less.compress = true 18 | dummy_asset('basics').must_match basic_compressed_match 19 | reset_caches 20 | dummy_config.less.compress = false 21 | dummy_asset('basics').wont_match basic_compressed_match 22 | end 23 | 24 | end 25 | 26 | describe 'initialization' do 27 | 28 | it 'must register our template engine' do 29 | dummy_assets.engines['.less'].must_equal Less::Rails::LessTemplate 30 | end 31 | 32 | it 'must extend the context class with our config' do 33 | dummy_assets.context_class.must_respond_to :less_config 34 | dummy_assets.context_class.less_config.must_equal dummy_config.less 35 | end 36 | 37 | it 'must register our import pre processor' do 38 | dummy_assets.preprocessors['text/css'].must_include Less::Rails::ImportProcessor 39 | end 40 | 41 | it 'must include the asset pipelines stylesheet paths to less paths' do 42 | dummy_app.config.less.paths.must_include "#{dummy_app.root}/app/assets/stylesheets" 43 | end 44 | 45 | end 46 | 47 | 48 | end 49 | -------------------------------------------------------------------------------- /lib/less/rails/railtie.rb: -------------------------------------------------------------------------------- 1 | module Less 2 | module Rails 3 | class Railtie < ::Rails::Railtie 4 | 5 | module LessContext 6 | attr_accessor :less_config 7 | end 8 | 9 | config.less = ActiveSupport::OrderedOptions.new 10 | config.less.paths = [] 11 | config.less.compress = false 12 | config.app_generators.stylesheet_engine :less 13 | 14 | config.before_initialize do |app| 15 | require 'less' 16 | require 'less-rails' 17 | Sprockets::Engines #force autoloading 18 | Sprockets.register_engine '.less', Less::Rails::LessTemplate 19 | end 20 | 21 | initializer 'less-rails.before.load_config_initializers', :before => :load_config_initializers, :group => :all do |app| 22 | if ::Rails::VERSION::MAJOR < 4 23 | raise 'The less-rails plugin requires the asset pipeline to be enabled.' unless app.config.assets.enabled 24 | end 25 | (Sprockets.respond_to?('register_preprocessor') ? Sprockets : app.assets).register_preprocessor 'text/css', ImportProcessor 26 | end 27 | 28 | initializer 'less-rails.after.load_config_initializers', :after => :load_config_initializers, :group => :all do |app| 29 | app.assets.context_class.extend(LessContext) 30 | app.assets.context_class.less_config = app.config.less 31 | end 32 | 33 | initializer 'less-rails.after.append_assets_path', :after => :append_assets_path, :group => :all do |app| 34 | assets_stylesheet_paths = app.config.assets.paths.select { |p| p && p.to_s.ends_with?('stylesheets') } 35 | app.config.less.paths.unshift(*assets_stylesheet_paths) 36 | end 37 | 38 | initializer 'less-rails.setup_compression', :group => :all do |app| 39 | config.less.compress = app.config.assets.compress 40 | end 41 | 42 | end 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /test/cases/basics_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class BasicsSpec < Less::Rails::Spec 4 | 5 | it 'must render variables' do 6 | basics.must_match %r{#test-variable\{color:#4d926f;\}} 7 | end 8 | 9 | it 'must render mixins' do 10 | basics.must_match %r{#test-mixin span\{border:1px solid black;\}} 11 | end 12 | 13 | it 'must be able to use vendored less files' do 14 | basics.must_match %r{#test-vendored\{border-radius:10px;\}} 15 | end 16 | 17 | describe 'less import dependency hooks' do 18 | 19 | it 'must update when imported file changes' do 20 | basics.must_match %r{#test-radiused\{border-radius:5px;\}}, 'default is 5px' 21 | safely_edit(:mixins) do |data, asset| 22 | data.gsub! '5px', '10px' 23 | File.open(asset.pathname,'w') { |f| f.write(data) } 24 | basics.must_match %r{#test-radiused\{border-radius:10px;\}}, 'mixins.less should be a sprockets context dependency' 25 | end 26 | end 27 | 28 | it 'must update when an imported file of another imported file changes' do 29 | basics.must_match %r{#test-variable-colored\{color:#424242;\}}, 'default is #424242' 30 | safely_edit(:variables) do |data, asset| 31 | data.gsub! '424242', '666666' 32 | File.open(asset.pathname,'w') { |f| f.write(data) } 33 | basics.must_match %r{#test-variable-colored\{color:#666666;\}}, 'variables.less should be a sprockets context dependency' 34 | end 35 | end 36 | 37 | end 38 | 39 | protected 40 | 41 | def basics 42 | dummy_asset 'basics' 43 | end 44 | 45 | def mixins_asset 46 | dummy_assets['frameworks/bootstrap/mixins.less'] 47 | end 48 | 49 | def variables_asset 50 | dummy_assets['frameworks/bootstrap/variables.less'] 51 | end 52 | 53 | def safely_edit(name) 54 | asset = send :"#{name}_asset" 55 | data = File.read(asset.pathname) 56 | begin 57 | yield data.dup, asset 58 | ensure 59 | File.open(asset.pathname,'w') { |f| f.write(data) } 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 2.2.6 - 10/31/2012 5 | 6 | * Accidental release. Nothing new. 7 | 8 | 9 | 2.2.5 - 10/28/2012 10 | 11 | * Real Rails 4 compatability thanks to @yalab 12 | 13 | 14 | 2.2.4 - 10/20/2012 15 | 16 | * Rails 4 compatability with Sprockets vs app.assets. 17 | 18 | 19 | 2.2.3 - 05/27/2012 20 | 21 | * Add default_mime_type to template class since it does not inherit from Tilt's. 22 | Should fix https://github.com/metaskills/less-rails/issues/35 23 | 24 | 25 | 2.2.2 - 04/25/2012 26 | 27 | * Remove explicit dependency on therubyracer 28 | * Add jruby and jruby --1.9 to travis configuration 29 | * Officially support JRuby 30 | 31 | 32 | 2.2.1 - 04/15/2012 33 | 34 | * Make it usable with therubyrhino (and older versions of therubyracer as well). Fixes #36. 35 | 36 | 37 | 2.2.0 - 03/29/2012 38 | 39 | * Upgrade to therubyracer 0.10.x call semantics. Fixes #34. 40 | 41 | 42 | 2.1.8 - 03/15/2012 43 | 44 | * Work with edge rails/sprockets. Fixes #31. 45 | 46 | 47 | 2.1.7 - 03/07/2012 48 | 49 | * More defensive railtie when examing asset paths. Fixes #30. 50 | 51 | 52 | 2.1.6 - 02/16/2012 53 | 54 | * Nested imports recursively declare sprockets dependencies. Fixes #26. 55 | 56 | 57 | 2.1.4, 2.1.5 - 01/31/2012 58 | 59 | * More friendly import processor with missing files. Fixes #13. 60 | 61 | 62 | 2.1.3 - 01/23/2012 63 | 64 | * Make sure vendor/assets/stylesheets .less files work. 65 | 66 | 67 | 2.1.2 - 12/20/2011 68 | 69 | * No notes. 70 | 71 | 72 | 2.1.1 - 11/24/2011 73 | ------------------ 74 | 75 | * All app asset stylesheet paths are added to less paths. 76 | 77 | 78 | 2.1.0 - 11/18/2011 79 | ------------------ 80 | * Remove our basic CssCompressor since it can not handle real world general purpose JS 81 | compression. Instead set parse compression and recommend final YUI Compressor. Fixes #7. 82 | * Import preprocessor so @import'ed less files are automatic asset dependencies. Fixes #3. 83 | 84 | 85 | 2.0.3 - 11/13/2011 86 | ------------------ 87 | * Add generator support. Fixes #8. 88 | * Add a Less::Rails::CssCompressor if config.assets.compress is true. Fixes #7. 89 | 90 | 91 | 2.0.2 - 10/09/2011 92 | ------------------ 93 | * Extend LESS with Rails asset pipeline helpers. 94 | * New testing support with MiniTest::Spec and dummy Rails::Application. 95 | * New config.options hash passed down to the #to_css method. 96 | 97 | 98 | 2.0.1 - 09/28/2011 99 | ------------------ 100 | 101 | * Fix require of less/rails/railtie.rb. Thanks Benoit Bénézech (bbenezech). 102 | 103 | 104 | 2.0.0 - 09/25/2011 105 | ------------------ 106 | 107 | * Initial 2.0 release. Heavily inspired/copied code from sass-rails. 108 | 109 | 110 | 1.0.0 - 02/02/2010 111 | ------------------ 112 | 113 | * Original project at http://github.com/yeastymobs/less-rails 114 | -------------------------------------------------------------------------------- /gemfiles/rails32.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: /Users/kencollins/Repositories/less-rails 3 | specs: 4 | less-rails (2.2.6) 5 | actionpack (>= 3.1) 6 | less (~> 2.2.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actionmailer (3.2.12) 12 | actionpack (= 3.2.12) 13 | mail (~> 2.4.4) 14 | actionpack (3.2.12) 15 | activemodel (= 3.2.12) 16 | activesupport (= 3.2.12) 17 | builder (~> 3.0.0) 18 | erubis (~> 2.7.0) 19 | journey (~> 1.0.4) 20 | rack (~> 1.4.5) 21 | rack-cache (~> 1.2) 22 | rack-test (~> 0.6.1) 23 | sprockets (~> 2.2.1) 24 | activemodel (3.2.12) 25 | activesupport (= 3.2.12) 26 | builder (~> 3.0.0) 27 | activerecord (3.2.12) 28 | activemodel (= 3.2.12) 29 | activesupport (= 3.2.12) 30 | arel (~> 3.0.2) 31 | tzinfo (~> 0.3.29) 32 | activeresource (3.2.12) 33 | activemodel (= 3.2.12) 34 | activesupport (= 3.2.12) 35 | activesupport (3.2.12) 36 | i18n (~> 0.6) 37 | multi_json (~> 1.0) 38 | appraisal (0.5.1) 39 | bundler 40 | rake 41 | arel (3.0.2) 42 | builder (3.0.4) 43 | coderay (1.0.9) 44 | commonjs (0.2.6) 45 | erubis (2.7.0) 46 | guard (1.6.2) 47 | listen (>= 0.6.0) 48 | lumberjack (>= 1.0.2) 49 | pry (>= 0.9.10) 50 | terminal-table (>= 1.4.3) 51 | thor (>= 0.14.6) 52 | guard-minitest (0.5.0) 53 | guard (>= 0.4) 54 | hike (1.2.1) 55 | i18n (0.6.1) 56 | journey (1.0.4) 57 | json (1.7.7) 58 | less (2.2.2) 59 | commonjs (~> 0.2.6) 60 | libv8 (3.3.10.4) 61 | listen (0.7.3) 62 | lumberjack (1.0.2) 63 | mail (2.4.4) 64 | i18n (>= 0.4.0) 65 | mime-types (~> 1.16) 66 | treetop (~> 1.4.8) 67 | method_source (0.8.1) 68 | mime-types (1.21) 69 | minitest (4.6.1) 70 | multi_json (1.6.1) 71 | polyglot (0.3.3) 72 | pry (0.9.12) 73 | coderay (~> 1.0.5) 74 | method_source (~> 0.8) 75 | slop (~> 3.4) 76 | rack (1.4.5) 77 | rack-cache (1.2) 78 | rack (>= 0.4) 79 | rack-ssl (1.3.3) 80 | rack 81 | rack-test (0.6.2) 82 | rack (>= 1.0) 83 | rails (3.2.12) 84 | actionmailer (= 3.2.12) 85 | actionpack (= 3.2.12) 86 | activerecord (= 3.2.12) 87 | activeresource (= 3.2.12) 88 | activesupport (= 3.2.12) 89 | bundler (~> 1.0) 90 | railties (= 3.2.12) 91 | railties (3.2.12) 92 | actionpack (= 3.2.12) 93 | activesupport (= 3.2.12) 94 | rack-ssl (~> 1.3.2) 95 | rake (>= 0.8.7) 96 | rdoc (~> 3.4) 97 | thor (>= 0.14.6, < 2.0) 98 | rake (10.0.3) 99 | rdoc (3.12.1) 100 | json (~> 1.4) 101 | slop (3.4.3) 102 | sprockets (2.2.2) 103 | hike (~> 1.2) 104 | multi_json (~> 1.0) 105 | rack (~> 1.0) 106 | tilt (~> 1.1, != 1.3.0) 107 | terminal-table (1.4.5) 108 | therubyracer (0.10.2) 109 | libv8 (~> 3.3.10) 110 | thor (0.17.0) 111 | tilt (1.3.3) 112 | treetop (1.4.12) 113 | polyglot 114 | polyglot (>= 0.3.1) 115 | tzinfo (0.3.35) 116 | 117 | PLATFORMS 118 | ruby 119 | 120 | DEPENDENCIES 121 | appraisal 122 | guard-minitest 123 | less-rails! 124 | minitest 125 | rails (~> 3.2.0) 126 | therubyracer (~> 0.10.0) 127 | therubyrhino (~> 1.73.3) 128 | -------------------------------------------------------------------------------- /gemfiles/rails31.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: /Users/kencollins/Repositories/less-rails 3 | specs: 4 | less-rails (2.2.6) 5 | actionpack (>= 3.1) 6 | less (~> 2.2.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actionmailer (3.1.10) 12 | actionpack (= 3.1.10) 13 | mail (~> 2.3.3) 14 | actionpack (3.1.10) 15 | activemodel (= 3.1.10) 16 | activesupport (= 3.1.10) 17 | builder (~> 3.0.0) 18 | erubis (~> 2.7.0) 19 | i18n (~> 0.6) 20 | rack (~> 1.3.6) 21 | rack-cache (~> 1.2) 22 | rack-mount (~> 0.8.2) 23 | rack-test (~> 0.6.1) 24 | sprockets (~> 2.0.4) 25 | activemodel (3.1.10) 26 | activesupport (= 3.1.10) 27 | builder (~> 3.0.0) 28 | i18n (~> 0.6) 29 | activerecord (3.1.10) 30 | activemodel (= 3.1.10) 31 | activesupport (= 3.1.10) 32 | arel (~> 2.2.3) 33 | tzinfo (~> 0.3.29) 34 | activeresource (3.1.10) 35 | activemodel (= 3.1.10) 36 | activesupport (= 3.1.10) 37 | activesupport (3.1.10) 38 | multi_json (>= 1.0, < 1.3) 39 | appraisal (0.5.1) 40 | bundler 41 | rake 42 | arel (2.2.3) 43 | builder (3.0.4) 44 | coderay (1.0.9) 45 | commonjs (0.2.6) 46 | erubis (2.7.0) 47 | guard (1.6.2) 48 | listen (>= 0.6.0) 49 | lumberjack (>= 1.0.2) 50 | pry (>= 0.9.10) 51 | terminal-table (>= 1.4.3) 52 | thor (>= 0.14.6) 53 | guard-minitest (0.5.0) 54 | guard (>= 0.4) 55 | hike (1.2.1) 56 | i18n (0.6.1) 57 | json (1.7.7) 58 | less (2.2.2) 59 | commonjs (~> 0.2.6) 60 | libv8 (3.3.10.4) 61 | listen (0.7.3) 62 | lumberjack (1.0.2) 63 | mail (2.3.3) 64 | i18n (>= 0.4.0) 65 | mime-types (~> 1.16) 66 | treetop (~> 1.4.8) 67 | method_source (0.8.1) 68 | mime-types (1.21) 69 | minitest (4.6.1) 70 | multi_json (1.2.0) 71 | polyglot (0.3.3) 72 | pry (0.9.12) 73 | coderay (~> 1.0.5) 74 | method_source (~> 0.8) 75 | slop (~> 3.4) 76 | rack (1.3.9) 77 | rack-cache (1.2) 78 | rack (>= 0.4) 79 | rack-mount (0.8.3) 80 | rack (>= 1.0.0) 81 | rack-ssl (1.3.3) 82 | rack 83 | rack-test (0.6.2) 84 | rack (>= 1.0) 85 | rails (3.1.10) 86 | actionmailer (= 3.1.10) 87 | actionpack (= 3.1.10) 88 | activerecord (= 3.1.10) 89 | activeresource (= 3.1.10) 90 | activesupport (= 3.1.10) 91 | bundler (~> 1.0) 92 | railties (= 3.1.10) 93 | railties (3.1.10) 94 | actionpack (= 3.1.10) 95 | activesupport (= 3.1.10) 96 | rack-ssl (~> 1.3.2) 97 | rake (>= 0.8.7) 98 | rdoc (~> 3.4) 99 | thor (~> 0.14.6) 100 | rake (10.0.3) 101 | rdoc (3.12.1) 102 | json (~> 1.4) 103 | slop (3.4.3) 104 | sprockets (2.0.4) 105 | hike (~> 1.2) 106 | rack (~> 1.0) 107 | tilt (~> 1.1, != 1.3.0) 108 | terminal-table (1.4.5) 109 | therubyracer (0.10.2) 110 | libv8 (~> 3.3.10) 111 | thor (0.14.6) 112 | tilt (1.3.3) 113 | treetop (1.4.12) 114 | polyglot 115 | polyglot (>= 0.3.1) 116 | tzinfo (0.3.35) 117 | 118 | PLATFORMS 119 | ruby 120 | 121 | DEPENDENCIES 122 | appraisal 123 | guard-minitest 124 | less-rails! 125 | minitest 126 | rails (~> 3.1.0) 127 | therubyracer (~> 0.10.0) 128 | therubyrhino (~> 1.73.3) 129 | -------------------------------------------------------------------------------- /gemfiles/rails40.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/rails/rails.git 3 | revision: fb40358f4f3d0028bbcd1a8f1fc74b1453e2014a 4 | specs: 5 | actionmailer (4.0.0.beta) 6 | actionpack (= 4.0.0.beta) 7 | mail (~> 2.5.3) 8 | actionpack (4.0.0.beta) 9 | activesupport (= 4.0.0.beta) 10 | builder (~> 3.1.0) 11 | erubis (~> 2.7.0) 12 | rack (~> 1.5.2) 13 | rack-test (~> 0.6.2) 14 | activemodel (4.0.0.beta) 15 | activesupport (= 4.0.0.beta) 16 | builder (~> 3.1.0) 17 | activerecord (4.0.0.beta) 18 | activemodel (= 4.0.0.beta) 19 | activerecord-deprecated_finders (~> 0.0.3) 20 | activesupport (= 4.0.0.beta) 21 | arel (~> 3.0.2) 22 | activesupport (4.0.0.beta) 23 | i18n (~> 0.6) 24 | minitest (~> 4.2) 25 | multi_json (~> 1.3) 26 | thread_safe (~> 0.1) 27 | tzinfo (~> 0.3.33) 28 | rails (4.0.0.beta) 29 | actionmailer (= 4.0.0.beta) 30 | actionpack (= 4.0.0.beta) 31 | activerecord (= 4.0.0.beta) 32 | activesupport (= 4.0.0.beta) 33 | bundler (>= 1.2.2, < 2.0) 34 | railties (= 4.0.0.beta) 35 | sprockets-rails (~> 2.0.0.rc1) 36 | railties (4.0.0.beta) 37 | actionpack (= 4.0.0.beta) 38 | activesupport (= 4.0.0.beta) 39 | rake (>= 0.8.7) 40 | rdoc (~> 3.4) 41 | thor (>= 0.17.0, < 2.0) 42 | 43 | PATH 44 | remote: /Users/kencollins/Repositories/less-rails 45 | specs: 46 | less-rails (2.2.6) 47 | actionpack (>= 3.1) 48 | less (~> 2.2.0) 49 | 50 | GEM 51 | remote: https://rubygems.org/ 52 | specs: 53 | activerecord-deprecated_finders (0.0.3) 54 | appraisal (0.5.1) 55 | bundler 56 | rake 57 | arel (3.0.2) 58 | atomic (1.0.1) 59 | builder (3.1.4) 60 | coderay (1.0.9) 61 | commonjs (0.2.6) 62 | erubis (2.7.0) 63 | guard (1.6.2) 64 | listen (>= 0.6.0) 65 | lumberjack (>= 1.0.2) 66 | pry (>= 0.9.10) 67 | terminal-table (>= 1.4.3) 68 | thor (>= 0.14.6) 69 | guard-minitest (0.5.0) 70 | guard (>= 0.4) 71 | hike (1.2.1) 72 | i18n (0.6.1) 73 | json (1.7.7) 74 | less (2.2.2) 75 | commonjs (~> 0.2.6) 76 | libv8 (3.3.10.4) 77 | listen (0.7.3) 78 | lumberjack (1.0.2) 79 | mail (2.5.3) 80 | i18n (>= 0.4.0) 81 | mime-types (~> 1.16) 82 | treetop (~> 1.4.8) 83 | method_source (0.8.1) 84 | mime-types (1.21) 85 | minitest (4.6.1) 86 | multi_json (1.6.1) 87 | polyglot (0.3.3) 88 | pry (0.9.12) 89 | coderay (~> 1.0.5) 90 | method_source (~> 0.8) 91 | slop (~> 3.4) 92 | rack (1.5.2) 93 | rack-test (0.6.2) 94 | rack (>= 1.0) 95 | rake (10.0.3) 96 | rdoc (3.12.1) 97 | json (~> 1.4) 98 | slop (3.4.3) 99 | sprockets (2.8.2) 100 | hike (~> 1.2) 101 | multi_json (~> 1.0) 102 | rack (~> 1.0) 103 | tilt (~> 1.1, != 1.3.0) 104 | sprockets-rails (2.0.0.rc2) 105 | actionpack (>= 3.0) 106 | activesupport (>= 3.0) 107 | sprockets (~> 2.8) 108 | terminal-table (1.4.5) 109 | therubyracer (0.10.2) 110 | libv8 (~> 3.3.10) 111 | thor (0.17.0) 112 | thread_safe (0.1.0) 113 | atomic 114 | tilt (1.3.3) 115 | treetop (1.4.12) 116 | polyglot 117 | polyglot (>= 0.3.1) 118 | tzinfo (0.3.35) 119 | 120 | PLATFORMS 121 | ruby 122 | 123 | DEPENDENCIES 124 | appraisal 125 | guard-minitest 126 | less-rails! 127 | minitest 128 | rails! 129 | therubyracer (~> 0.10.0) 130 | therubyrhino (~> 1.73.3) 131 | -------------------------------------------------------------------------------- /test/cases/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class HelpersSpec < Less::Rails::Spec 4 | 5 | before { dummy_config.less.compress = false } 6 | 7 | let(:helpers) { dummy_asset('helpers') } 8 | 9 | it 'parse asset paths' do 10 | line_for_helper('asset-path').must_equal 'asset-path: "/assets/rails.png";' 11 | line_for_helper('asset-url').must_equal "asset-url: url(/assets/rails.png);" 12 | line_for_helper('image-path').must_equal 'image-path: "/assets/rails.png";' 13 | line_for_helper('image-url').must_equal "image-url: url(/assets/rails.png);" 14 | line_for_helper('video-path').must_equal 'video-path: "/videos/rails.mp4";' 15 | line_for_helper('video-url').must_equal "video-url: url(/videos/rails.mp4);" 16 | line_for_helper('audio-path').must_equal 'audio-path: "/audios/rails.mp3";' 17 | line_for_helper('audio-url').must_equal "audio-url: url(/audios/rails.mp3);" 18 | if Rails::VERSION::MAJOR < 4 19 | line_for_helper('javascript-path').must_equal 'javascript-path: "/assets/rails.js";' 20 | line_for_helper('javascript-url').must_equal "javascript-url: url(/assets/rails.js);" 21 | line_for_helper('stylesheet-path').must_equal 'stylesheet-path: "/assets/rails.css";' 22 | line_for_helper('stylesheet-url').must_equal "stylesheet-url: url(/assets/rails.css);" 23 | line_for_helper('font-path').must_equal 'font-path: "/assets/rails.ttf";' 24 | line_for_helper('font-url').must_equal "font-url: url(/assets/rails.ttf);" 25 | else 26 | line_for_helper('javascript-path').must_equal 'javascript-path: "/javascripts/rails.js";' 27 | line_for_helper('javascript-url').must_equal "javascript-url: url(/javascripts/rails.js);" 28 | line_for_helper('stylesheet-path').must_equal 'stylesheet-path: "/stylesheets/rails.css";' 29 | line_for_helper('stylesheet-url').must_equal "stylesheet-url: url(/stylesheets/rails.css);" 30 | line_for_helper('font-path').must_equal 'font-path: "/fonts/rails.ttf";' 31 | line_for_helper('font-url').must_equal "font-url: url(/fonts/rails.ttf);" 32 | end 33 | end 34 | 35 | it 'parses data urls ' do 36 | line = line_for_helper('asset-data-url') 37 | asset_data_url_regexp = %r{asset-data-url: url\((.*?)\)} 38 | line.must_match asset_data_url_regexp 39 | asset_data_url_match = line.match(asset_data_url_regexp)[1] 40 | asset_data_url_expected = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw%2FeHBhY2tldCBiZWdpbj0i77u%2FIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8%2BIDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCNzY5NDE1QkQ2NkMxMUUwOUUzM0E5Q0E2RTgyQUExQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5NDE1Q0Q2NkMxMUUwOUUzM0E5Q0E2RTgyQUExQiI%2BIDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE3MzcyNTQ2RDY2QjExRTA5RTMzQTlDQTZFODJBQTFCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3Njk0MTVBRDY2QzExRTA5RTMzQTlDQTZFODJBQTFCIi8%2BIDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY%2BIDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8%2B0HhJ9AAAABBJREFUeNpi%2BP%2F%2FPwNAgAEACPwC%2FtuiTRYAAAAASUVORK5CYII%3D" 41 | asset_data_url_match.must_equal asset_data_url_expected 42 | end 43 | 44 | 45 | private 46 | 47 | def line_for_helper(name) 48 | helpers.each_line.detect{ |line| line.include? name }.strip 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | Bundler.require 4 | require 'less/rails' 5 | require 'minitest/spec' 6 | require 'minitest/autorun' 7 | require 'dummy_app/init' 8 | require 'rails/generators' 9 | require 'fileutils' 10 | 11 | module Less 12 | module Rails 13 | 14 | class Spec < MiniTest::Spec 15 | 16 | include FileUtils 17 | 18 | class << self 19 | 20 | def dummy_app 21 | Dummy::Application 22 | end 23 | 24 | def dummy_tmp 25 | "#{dummy_app.root}/tmp" 26 | end 27 | 28 | end 29 | 30 | before do 31 | prepare_cache_dir 32 | reset_config_options 33 | reset_caches 34 | end 35 | 36 | protected 37 | 38 | delegate :dummy_app, :dummy_tmp, :to => :'self.class' 39 | 40 | def dummy_config 41 | dummy_app.config 42 | end 43 | 44 | def dummy_assets 45 | dummy_app.assets 46 | end 47 | 48 | def dummy_asset(name) 49 | dummy_assets[name].to_s.strip 50 | end 51 | 52 | def reset_config_options 53 | dummy_config.less.compress = true 54 | end 55 | 56 | def reset_caches 57 | dummy_assets.version = SecureRandom.hex(32) 58 | cache = dummy_assets.cache 59 | if cache.is_a? Sprockets::Cache::FileStore 60 | path = cache.instance_variable_get(:@root) 61 | cache = Sprockets::Cache::FileStore.new(path) 62 | else 63 | cache.clear 64 | end 65 | end 66 | 67 | def prepare_cache_dir 68 | mkdir_p "#{dummy_tmp}/cache/assets" 69 | end 70 | 71 | end 72 | 73 | # Heavily inspired by Rails::Generators::TestCase. 74 | class GeneratorSpec < Spec 75 | 76 | class_attribute :destination_root, :current_path, :generator_class, :default_arguments 77 | delegate :destination_root, :current_path, :generator_class, :default_arguments, :to => :'self.class' 78 | 79 | self.current_path = File.expand_path(Dir.pwd) 80 | self.default_arguments = [] 81 | self.destination_root = "#{dummy_tmp}/destination_root" 82 | 83 | before { ensure_current_path ; prepare_destination ; no_color! ; setup_generator_class } 84 | after { remove_destination ; ensure_current_path } 85 | 86 | protected 87 | 88 | def no_color! 89 | Thor::Base.shell = Thor::Shell::Basic 90 | end 91 | 92 | def ensure_current_path 93 | cd current_path 94 | end 95 | 96 | def prepare_destination 97 | remove_destination 98 | mkdir_p destination_root 99 | end 100 | 101 | def remove_destination 102 | rm_rf destination_root 103 | end 104 | 105 | def setup_generator_class 106 | self.class.generator_class = Less::Generators.const_get(self.class.name.sub(/Spec$/, '')) 107 | end 108 | 109 | def run_generator(args=default_arguments, config={}) 110 | capture(:stdout) { generator_class.start(args, config.reverse_merge(:destination_root => destination_root)) } 111 | end 112 | 113 | def generator(args=default_arguments, options={}, config={}) 114 | @generator ||= generator_class.new(args, options, config.reverse_merge(:destination_root => destination_root)) 115 | end 116 | 117 | def assert_file(relative, *contents) 118 | absolute = File.expand_path(relative, destination_root) 119 | assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not" 120 | read = File.read(absolute) if block_given? || !contents.empty? 121 | yield read if block_given? 122 | contents.each do |content| 123 | case content 124 | when String 125 | assert_equal content, read 126 | when Regexp 127 | assert_match content, read 128 | end 129 | end 130 | end 131 | alias :assert_directory :assert_file 132 | 133 | end 134 | 135 | end 136 | end 137 | 138 | -------------------------------------------------------------------------------- /lib/less/rails/helpers.rb: -------------------------------------------------------------------------------- 1 | module Less 2 | 3 | def self.less 4 | @less 5 | end 6 | 7 | def self.register_rails_helper(name, &block) 8 | tree = @loader.require('less/tree') 9 | tree.functions[name] = lambda do |*args| 10 | # args: (this, node) v8 >= 0.10, otherwise (node) 11 | raise ArgumentError, "missing node" if args.empty? 12 | tree[:Anonymous].new block.call(tree, args.last) 13 | end 14 | end 15 | 16 | module Rails 17 | module Helpers 18 | 19 | extend ActiveSupport::Concern 20 | 21 | included do 22 | Less.register_rails_helper('asset-path') { |tree, cxt| asset_path unquote(cxt.toCSS()) } 23 | Less.register_rails_helper('asset-url') { |tree, cxt| asset_url unquote(cxt.toCSS()) } 24 | Less.register_rails_helper('image-path') { |tree, cxt| image_path unquote(cxt.toCSS()) } 25 | Less.register_rails_helper('image-url') { |tree, cxt| image_url unquote(cxt.toCSS()) } 26 | Less.register_rails_helper('video-path') { |tree, cxt| video_path unquote(cxt.toCSS()) } 27 | Less.register_rails_helper('video-url') { |tree, cxt| video_url unquote(cxt.toCSS()) } 28 | Less.register_rails_helper('audio-path') { |tree, cxt| audio_path unquote(cxt.toCSS()) } 29 | Less.register_rails_helper('audio-url') { |tree, cxt| audio_url unquote(cxt.toCSS()) } 30 | Less.register_rails_helper('javascript-path') { |tree, cxt| javascript_path unquote(cxt.toCSS()) } 31 | Less.register_rails_helper('javascript-url') { |tree, cxt| javascript_url unquote(cxt.toCSS()) } 32 | Less.register_rails_helper('stylesheet-path') { |tree, cxt| stylesheet_path unquote(cxt.toCSS()) } 33 | Less.register_rails_helper('stylesheet-url') { |tree, cxt| stylesheet_url unquote(cxt.toCSS()) } 34 | Less.register_rails_helper('font-path') { |tree, cxt| font_path unquote(cxt.toCSS()) } 35 | Less.register_rails_helper('font-url') { |tree, cxt| font_url unquote(cxt.toCSS()) } 36 | Less.register_rails_helper('asset-data-url') { |tree, cxt| asset_data_url unquote(cxt.toCSS()) } 37 | end 38 | 39 | module ClassMethods 40 | 41 | def asset_data_url(path) 42 | "url(#{scope.asset_data_uri(path)})" 43 | end 44 | 45 | def asset_path(asset) 46 | public_path(asset).inspect 47 | end 48 | 49 | def asset_url(asset) 50 | "url(#{public_path(asset)})" 51 | end 52 | 53 | def image_path(img) 54 | scope.image_path(img).inspect 55 | end 56 | 57 | def image_url(img) 58 | "url(#{scope.image_path(img)})" 59 | end 60 | 61 | def video_path(video) 62 | scope.video_path(video).inspect 63 | end 64 | 65 | def video_url(video) 66 | "url(#{scope.video_path(video)})" 67 | end 68 | 69 | def audio_path(audio) 70 | scope.audio_path(audio).inspect 71 | end 72 | 73 | def audio_url(audio) 74 | "url(#{scope.audio_path(audio)})" 75 | end 76 | 77 | def javascript_path(javascript) 78 | scope.javascript_path(javascript).inspect 79 | end 80 | 81 | def javascript_url(javascript) 82 | "url(#{scope.javascript_path(javascript)})" 83 | end 84 | 85 | def stylesheet_path(stylesheet) 86 | scope.stylesheet_path(stylesheet).inspect 87 | end 88 | 89 | def stylesheet_url(stylesheet) 90 | "url(#{scope.stylesheet_path(stylesheet)})" 91 | end 92 | 93 | def font_path(font) 94 | if scope.respond_to?(:font_path) 95 | scope.font_path(font).inspect 96 | else 97 | asset_path(font) 98 | end 99 | end 100 | 101 | def font_url(font) 102 | if scope.respond_to?(:font_path) 103 | "url(#{scope.font_path(font)})" 104 | else 105 | asset_url(font) 106 | end 107 | end 108 | 109 | private 110 | 111 | def scope 112 | Less.Parser['scope'] 113 | end 114 | 115 | def public_path(asset) 116 | if scope.respond_to?(:asset_paths) 117 | scope.asset_paths.compute_public_path asset, ::Rails.application.config.assets.prefix 118 | else 119 | scope.path_to_asset(asset) 120 | end 121 | end 122 | 123 | def context_asset_data_uri(path) 124 | 125 | end 126 | 127 | def unquote(str) 128 | s = str.to_s.strip 129 | s =~ /^['"](.*?)['"]$/ ? $1 : s 130 | end 131 | 132 | end 133 | 134 | end 135 | end 136 | end 137 | 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The dynamic stylesheet language for the Rails asset pipeline. 2 | 3 | This gem provides integration for Rails projects using the Less stylesheet language in the asset pipeline. 4 | 5 | [![Build Status](https://secure.travis-ci.org/metaskills/minitest-spec-rails.png)](http://travis-ci.org/metaskills/less-rails) 6 | 7 | 8 | ## Installing 9 | 10 | Just bundle up less-rails in your Gemfile. This will pull in less as a runtime dependency too. 11 | 12 | ```ruby 13 | gem 'less-rails' 14 | ``` 15 | 16 | But be warned, less.rb relies on a JavaScript runtime gem too. Just like ExecJS, it will look for a gem that is appropriate to your system. Typically, this means you will need one of the following. 17 | 18 | ```ruby 19 | gem 'therubyracer' # Ruby 20 | gem 'therubyrhino' # JRuby 21 | ``` 22 | 23 | 24 | ## Configuration 25 | 26 | This gem was made for other gems to properly hook into one place to provide paths to the Less::Parser. For example, the less-rails-bootstrap project at http://github.com/metaskills/less-rails-bootstrap and each project should do the path configuration for you. If you need to, you can configure less-rails with additional paths. These paths have higher priority than those from your applications assets load paths. 27 | 28 | ```ruby 29 | MyProject::Application.configure do 30 | config.less.paths << "#{Rails.root}/lib/less/protractor/stylesheets" 31 | config.less.compress = true 32 | end 33 | ``` 34 | 35 | #### About Compression 36 | 37 | If `config.assets.compress` is set to true, we will set the `config.less.compress` to true as well. Less has real basic compression and it is recommended that you set the rails `config.assets.css_compressor` to something more stronger like `:yui` in your `config/environments/production.rb` file. Note, this requires the [yui-compressor](https://rubygems.org/gems/yui-compressor) gem but does an excellent job of compressing assets. 38 | 39 | 40 | 41 | ## Import Hooks 42 | 43 | Any `@import` to a `.less` file will automatically declare that file as a sprockets dependency to the file importing it. This means that you can edit imported framework files and see changes reflected in the parent during development. So this: 44 | 45 | ```css 46 | @import "frameworks/bootstrap/mixins"; 47 | 48 | #leftnav { .border-radius(5px); } 49 | ``` 50 | 51 | Will end up acting as if you had done this below: 52 | 53 | ```css 54 | /* 55 | *= depend_on "frameworks/bootstrap/mixins.less" 56 | */ 57 | 58 | @import "frameworks/bootstrap/mixins"; 59 | 60 | #leftnav { .border-radius(5px); } 61 | ``` 62 | 63 | 64 | 65 | ## Helpers 66 | 67 | When referencing assets use the following helpers in LESS. 68 | 69 | ```css 70 | asset-path(@relative-asset-path) /* Returns a string to the asset. */ 71 | asset-path("rails.png") /* Becomes: "/assets/rails.png" */ 72 | 73 | asset-url(@relative-asset-path) /* Returns url reference to the asset. */ 74 | asset-url("rails.png") /* Becomes: url(/assets/rails.png) */ 75 | ``` 76 | 77 | As a convenience, for each of the following asset classes there are corresponding `-path` and `-url` helpers image, font, video, audio, javascript and stylesheet. The following examples only show the `-url` variants since you get the idea of the `-path` ones above. 78 | 79 | ```css 80 | image-url("rails.png") /* Becomes: url(/assets/rails.png) */ 81 | font-url("rails.ttf") /* Becomes: url(/assets/rails.ttf) */ 82 | video-url("rails.mp4") /* Becomes: url(/videos/rails.mp4) */ 83 | audio-url("rails.mp3") /* Becomes: url(/audios/rails.mp3) */ 84 | javascript-url("rails.js") /* Becomes: url(/assets/rails.js) */ 85 | stylesheet-url("rails.css") /* Becomes: url(/assets/rails.css) */ 86 | ``` 87 | 88 | Lastly, we provide a data url method for base64 encoding assets. 89 | 90 | ```css 91 | asset-data-url("rails.png") /* Becomes: url(data:image/png;base64,iVBORw0K...) */ 92 | ``` 93 | 94 | Please note that these helpers are only available server-side, and something like ERB templates should be used if client-side rendering is desired. 95 | 96 | 97 | 98 | ## Generators 99 | 100 | Installation of the gem will set your applications stylesheet engine to use Less. It is possible to have many gems that set the stylesheet engine, for instance the sass-rails and/or stylus gems. In this case, you can resolve the ambiguity by setting the stylesheet engine in your `config/application.rb` file like so. Doing so would mean all generated assets will be in the a fresh `css.less` template. 101 | 102 | ```ruby 103 | config.app_generators.stylesheet_engine :less 104 | ``` 105 | 106 | We have generators for both assets and scaffold in the `less` namespace. For instance the following would generate a blank `app/assets/stylesheets/posts.css.less` template. 107 | 108 | ``` 109 | $ rails generate less:assets posts 110 | ``` 111 | 112 | We also have a generator for rails scaffold CSS. Just like the Sass gem, we simply parse the scaffold.css in the default rails generator and save it as a scaffolds.css.less file. This is done automatically during other scaffold generator actions. 113 | 114 | 115 | 116 | ## Contributing 117 | 118 | This gem is fully tested from Rails 3.1 to 4. We run our tests on [Travis CI](http://travis-ci.org/metaskills/less-rails) in both Ruby 1.8 and 1.9, 2.0, and jruby 1.8 and 1.9 mode. If you detect a problem, open up a github issue or fork the repo and help out. After you fork or clone the repository, the following commands will get you up and running on the test suite. 119 | 120 | ```shell 121 | $ bundle install 122 | $ bundle exec rake appraisal:setup 123 | $ bundle exec rake appraisal test 124 | ``` 125 | 126 | We use the [appraisal](https://github.com/thoughtbot/appraisal) gem from Thoughtbot to help us generate the individual gemfiles for each Rails version and to run the tests locally against each generated Gemfile. The `rake appraisal test` command actually runs our test suite against all Rails versions in our `Appraisal` file. If you want to run the tests for a specific Rails version, use `rake -T` for a list. For example, the following command will run the tests for Rails 3.2 only. 127 | 128 | ```shell 129 | $ bundle exec rake appraisal:rails32 test 130 | ``` 131 | 132 | Our current build status is: 133 | [![Build Status](https://secure.travis-ci.org/metaskills/minitest-spec-rails.png)](http://travis-ci.org/metaskills/less-rails) 134 | 135 | 136 | ## License 137 | 138 | Less::Rails is Copyright (c) 2011-2013 Ken Collins, and is distributed under the MIT license. 139 | 140 | --------------------------------------------------------------------------------