├── gemfiles └── .keep ├── test ├── dummy_app │ ├── tmp │ │ ├── .gitkeep │ │ └── cache │ │ │ └── .gitkeep │ ├── app │ │ └── assets │ │ │ ├── stylesheets │ │ │ ├── frameworks │ │ │ │ └── bootstrap │ │ │ │ │ ├── variables.less │ │ │ │ │ ├── variables_via_relative_path.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 ├── .travis.yml ├── Guardfile ├── Rakefile ├── MIT-LICENSE ├── less-rails.gemspec ├── CHANGELOG.md └── README.md /gemfiles/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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.5.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /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/DV/less-rails/master/test/dummy_app/app/assets/images/1x1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | *.lock 4 | pkg/* 5 | *.log 6 | tmp/* 7 | test/dummy_app/tmp/cache/assets/* 8 | gemfiles/*.gemfile 9 | -------------------------------------------------------------------------------- /test/dummy_app/app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/less-rails/master/test/dummy_app/app/assets/images/rails.png -------------------------------------------------------------------------------- /test/dummy_app/app/assets/stylesheets/frameworks/bootstrap/variables_via_relative_path.less: -------------------------------------------------------------------------------- 1 | 2 | @variableRelativePathColor: #BADA55; 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | gem "therubyracer", "~> 0.10.0", :require => nil, :platforms => :ruby 5 | gem "therubyrhino", "~> 2.0.2", :require => nil, :platforms => :jruby 6 | -------------------------------------------------------------------------------- /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', '~> 4.0.0' 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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.3 3 | - 2.0.0 4 | - jruby-19mode 5 | before_install: 6 | - gem install bundler 7 | - bundle --version 8 | install: 9 | - bundle install 10 | before_script: 11 | - bundle exec rake appraisal:setup 12 | script: 13 | - bundle exec rake appraisal test 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/dummy_app/app/assets/stylesheets/frameworks/bootstrap/mixins.less: -------------------------------------------------------------------------------- 1 | 2 | @import "frameworks/bootstrap/variables"; 3 | @import "variables_via_relative_path"; 4 | 5 | .radiused(@radius: 5px) { 6 | border-radius: @radius; 7 | } 8 | 9 | .variableColored() { 10 | color: @variableColor; 11 | } 12 | 13 | .variableRelativePathColored() { 14 | color: @variableRelativePathColor; 15 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | #test-variable-relative-path-colored { .variableRelativePathColored; } 15 | 16 | // Vendored 17 | @import "vendored"; 18 | #test-vendored { .vendored; } 19 | -------------------------------------------------------------------------------- /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 if ::Rails::VERSION::MAJOR < 4 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 | PATHNAME_FINDER = Proc.new { |scope, path| 7 | begin 8 | scope.resolve(path) 9 | rescue Sprockets::FileNotFound 10 | nil 11 | end 12 | } 13 | 14 | def prepare 15 | end 16 | 17 | def evaluate(scope, locals, &block) 18 | depend_on scope, data 19 | data 20 | end 21 | 22 | def depend_on(scope, data, base=File.dirname(scope.logical_path)) 23 | import_paths = data.scan(IMPORT_SCANNER).flatten.compact.uniq 24 | import_paths.each do |path| 25 | pathname = PATHNAME_FINDER.call(scope,path) || PATHNAME_FINDER.call(scope, File.join(base, path)) 26 | scope.depend_on(pathname) if pathname && pathname.to_s.ends_with?('.less') 27 | depend_on scope, File.read(pathname), File.dirname(path) if pathname 28 | end 29 | data 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /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.license = 'MIT' 14 | gem.files = `git ls-files`.split("\n") 15 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 17 | gem.require_paths = ["lib"] 18 | gem.add_runtime_dependency 'less', '~> 2.5.0' 19 | gem.add_runtime_dependency 'actionpack', '>= 3.1' 20 | gem.add_development_dependency 'appraisal' 21 | gem.add_development_dependency 'minitest' 22 | gem.add_development_dependency 'guard-minitest' 23 | gem.add_development_dependency 'rails' 24 | end 25 | -------------------------------------------------------------------------------- /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 | local_path = scope.pathname.dirname 28 | paths += [local_path] unless paths.include? local_path 29 | {:filename => eval_file, :line => line, :paths => paths, :dumpLineNumbers => config_from_rails(scope).line_numbers} 30 | end 31 | 32 | def config_to_css_options(scope) 33 | Hash[config_from_rails(scope).each.to_a].slice *TO_CSS_KEYS 34 | end 35 | 36 | def config_paths(scope) 37 | config_from_rails(scope)[:paths] 38 | end 39 | 40 | def config_from_rails(scope) 41 | scope.environment.context_class.less_config 42 | end 43 | 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /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', LessTemplate 19 | end 20 | 21 | initializer 'less-rails.before.load_config_initializers', :before => :load_config_initializers, :group => :all do |app| 22 | (Sprockets.respond_to?('register_preprocessor') ? Sprockets : app.assets).register_preprocessor 'text/css', ImportProcessor 23 | app.assets.context_class.extend(LessContext) 24 | app.assets.context_class.less_config = app.config.less 25 | end 26 | 27 | initializer 'less-rails.after.append_assets_path', :after => :append_assets_path, :group => :all do |app| 28 | assets_stylesheet_paths = app.config.assets.paths.select { |p| p && p.to_s.ends_with?('stylesheets') } 29 | app.config.less.paths.unshift(*assets_stylesheet_paths) 30 | end 31 | 32 | initializer 'less-rails.setup_compression', :group => :all do |app| 33 | config.less.compress = app.config.assets.compress 34 | end 35 | 36 | end 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /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 | reset_caches 23 | dummy_config.less.line_numbers = 'mediaquery' 24 | dummy_asset('basics').wont_match basic_compressed_match 25 | basic_sourcemap_match = /@media -sass-debug-info{filename{font-family:file/ 26 | dummy_asset('basics').must_match basic_sourcemap_match 27 | end 28 | 29 | end 30 | 31 | describe 'initialization' do 32 | 33 | it 'must register our template engine' do 34 | dummy_assets.engines['.less'].must_equal Less::Rails::LessTemplate 35 | end 36 | 37 | it 'must extend the context class with our config' do 38 | dummy_assets.context_class.must_respond_to :less_config 39 | dummy_assets.context_class.less_config.must_equal dummy_config.less 40 | end 41 | 42 | it 'must register our import pre processor' do 43 | dummy_assets.preprocessors['text/css'].must_include Less::Rails::ImportProcessor 44 | end 45 | 46 | it 'must include the asset pipelines stylesheet paths to less paths' do 47 | dummy_app.config.less.paths.must_include "#{dummy_app.root}/app/assets/stylesheets" 48 | end 49 | 50 | end 51 | 52 | 53 | end 54 | -------------------------------------------------------------------------------- /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:#666\}}, 'variables.less should be a sprockets context dependency' 34 | end 35 | end 36 | 37 | it 'must update when an imported file of another imported file changes, and that file is imported via a relative path' do 38 | basics.must_match %r{#test-variable-relative-path-colored}i, 'default is #BADA55' 39 | safely_edit(:variables_via_relative_path) do |data, asset| 40 | data.gsub! 'BADA55', 'BA73A2' 41 | File.open(asset.pathname,'w') { |f| f.write(data) } 42 | basics.must_match %r{#test-variable-relative-path-colored}i, 'variables_via_relative_path.less should be a sprockets context dependency' 43 | end 44 | end 45 | 46 | end 47 | 48 | protected 49 | 50 | def basics 51 | dummy_asset 'basics' 52 | end 53 | 54 | def mixins_asset 55 | dummy_assets['frameworks/bootstrap/mixins.less'] 56 | end 57 | 58 | def variables_asset 59 | dummy_assets['frameworks/bootstrap/variables.less'] 60 | end 61 | 62 | def variables_via_relative_path_asset 63 | dummy_assets['frameworks/bootstrap/variables_via_relative_path.less'] 64 | end 65 | 66 | def safely_edit(name) 67 | asset = send :"#{name}_asset" 68 | data = File.read(asset.pathname) 69 | begin 70 | yield data.dup, asset 71 | ensure 72 | File.open(asset.pathname,'w') { |f| f.write(data) } 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ### 2.5.0 - 2014-03-11 4 | 5 | * Update to Less.rb v2.5 and follow version mirror. Fixes #84. 6 | 7 | 8 | ### 2.4.2 - 2013-09-07 9 | 10 | * Another stab at better initialization process. Fixes #68. Fixes #62. 11 | NOTE: If you were using `less-rails.after.load_config_initializers`, please 12 | change your initializer to use `less-rails.before.load_config_initializers`. 13 | 14 | ### 2.4.1 - 2013-09-07 15 | 16 | * Fix import hooks to allow relative paths to files. Fixes #72 and #64. 17 | * Use config.(before|after)_initialize for better railtie pattern. Fixed #68. 18 | 19 | ### 2.4.0 - 2013-09-04 20 | 21 | * Moving to less.rb 2.4.x which is current v### 1.4.2 of less. 22 | 23 | ### 2.3.3 - 2013-04-16 24 | 25 | * Implement dumpLineNumbers functionality. Thanks @matthew342. 26 | 27 | ### 2.3.2 - 2013-03-06 28 | 29 | * Rails 4 compatability. Thanks @zhengjia. 30 | 31 | ### 2.3.1 - 2013-03-06 32 | 33 | * Update to less gem ### 2.3.1 which really uses less v### 1.3.3 34 | 35 | ### 2.3.0 - 2013-03-06 36 | 37 | * Update to less gem ### 2.3.0 thanks @leifcr 38 | 39 | ### 2.2.6 - 2012-10-31 40 | 41 | * Accidental release. Nothing new. 42 | 43 | ### 2.2.5 - 2012-10-28 44 | 45 | * Real Rails 4 compatability thanks to @yalab 46 | 47 | ### 2.2.4 - 2012-10-20 48 | 49 | * Rails 4 compatability with Sprockets vs app.assets. 50 | 51 | ### 2.2.3 - 2012-05-27 52 | 53 | * Add default_mime_type to template class since it does not inherit from Tilt's. 54 | Should fix https://github.com/metaskills/less-rails/issues/35 55 | 56 | ### 2.2.2 - 2012-04-25 57 | 58 | * Remove explicit dependency on therubyracer 59 | * Add jruby and jruby --1.9 to travis configuration 60 | * Officially support JRuby 61 | 62 | ### 2.2.1 - 2012-04-15 63 | 64 | * Make it usable with therubyrhino (and older versions of therubyracer as well). Fixes #36. 65 | 66 | ### 2.2.0 - 2012-03-29 67 | 68 | * Upgrade to therubyracer 0.10.x call semantics. Fixes #34. 69 | 70 | ### 2.1.8 - 2012-03-15 71 | 72 | * Work with edge rails/sprockets. Fixes #31. 73 | 74 | ### 2.1.7 - 2012-03-07 75 | 76 | * More defensive railtie when examing asset paths. Fixes #30. 77 | 78 | ### 2.1.6 - 2012-02-16 79 | 80 | * Nested imports recursively declare sprockets dependencies. Fixes #26. 81 | 82 | ### 2.1.4, ### 2.1.5 - 2012-01-31 83 | 84 | * More friendly import processor with missing files. Fixes #13. 85 | 86 | ### 2.1.3 - 2012-01-23 87 | 88 | * Make sure vendor/assets/stylesheets .less files work. 89 | 90 | ### 2.1.2 - 2011-12-20 91 | 92 | * No notes. 93 | 94 | ### 2.1.1 - 2011-11-24 95 | 96 | * All app asset stylesheet paths are added to less paths. 97 | 98 | ### 2.1.0 - 2011-11-18 99 | 100 | * Remove our basic CssCompressor since it can not handle real world general purpose JS 101 | compression. Instead set parse compression and recommend final YUI Compressor. Fixes #7. 102 | * Import preprocessor so @import'ed less files are automatic asset dependencies. Fixes #3. 103 | 104 | ### 2.0.3 - 2011-11-13 105 | 106 | * Add generator support. Fixes #8. 107 | * Add a Less::Rails::CssCompressor if config.assets.compress is true. Fixes #7. 108 | 109 | ### 2.0.2 - 2011-10-09 110 | 111 | * Extend LESS with Rails asset pipeline helpers. 112 | * New testing support with MiniTest::Spec and dummy Rails::Application. 113 | * New config.options hash passed down to the #to_css method. 114 | 115 | ### 2.0.1 - 2011-09-28 116 | 117 | * Fix require of less/rails/railtie.rb. Thanks Benoit Bénézech (bbenezech). 118 | 119 | ### 2.0.0 - 2011-09-25 120 | 121 | * Initial 2.0 release. Heavily inspired/copied code from sass-rails. 122 | 123 | ### 1.0.0 - 2010-02-02 124 | 125 | * Original project at http://github.com/yeastymobs/less-rails 126 | -------------------------------------------------------------------------------- /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 'bundler' ; Bundler.require :development, :test 2 | require 'less-rails' 3 | require 'minitest/autorun' 4 | require 'dummy_app/init' 5 | require 'rails/generators' 6 | require 'fileutils' 7 | 8 | module Less 9 | module Rails 10 | class Spec < MiniTest::Spec 11 | 12 | include FileUtils 13 | 14 | class << self 15 | 16 | def dummy_app 17 | Dummy::Application 18 | end 19 | 20 | def dummy_tmp 21 | "#{dummy_app.root}/tmp" 22 | end 23 | 24 | end 25 | 26 | before do 27 | prepare_cache_dir 28 | reset_config_options 29 | reset_caches 30 | end 31 | 32 | protected 33 | 34 | delegate :dummy_app, :dummy_tmp, :to => :'self.class' 35 | 36 | def dummy_config 37 | dummy_app.config 38 | end 39 | 40 | def dummy_assets 41 | dummy_app.assets 42 | end 43 | 44 | def dummy_asset(name) 45 | dummy_assets[name].to_s.strip 46 | end 47 | 48 | def reset_config_options 49 | dummy_config.less.compress = true 50 | end 51 | 52 | def reset_caches 53 | version = SecureRandom.hex(32) 54 | dummy_config.assets.version = version 55 | dummy_assets.version = version 56 | cache = dummy_assets.cache 57 | cache.respond_to?(:clear) ? cache.clear : rm_r("#{dummy_tmp}/cache/assets") 58 | end 59 | 60 | def prepare_cache_dir 61 | mkdir_p "#{dummy_tmp}/cache/assets" 62 | end 63 | 64 | end 65 | 66 | # Heavily inspired by Rails::Generators::TestCase. 67 | class GeneratorSpec < Spec 68 | 69 | class_attribute :destination_root, :current_path, :generator_class, :default_arguments 70 | delegate :destination_root, :current_path, :generator_class, :default_arguments, :to => :'self.class' 71 | 72 | self.current_path = File.expand_path(Dir.pwd) 73 | self.default_arguments = [] 74 | self.destination_root = "#{dummy_tmp}/destination_root" 75 | 76 | before { ensure_current_path ; prepare_destination ; no_color! ; setup_generator_class } 77 | after { remove_destination ; ensure_current_path } 78 | 79 | protected 80 | 81 | def no_color! 82 | Thor::Base.shell = Thor::Shell::Basic 83 | end 84 | 85 | def ensure_current_path 86 | cd current_path 87 | end 88 | 89 | def prepare_destination 90 | remove_destination 91 | mkdir_p destination_root 92 | end 93 | 94 | def remove_destination 95 | rm_rf destination_root 96 | end 97 | 98 | def setup_generator_class 99 | self.class.generator_class = Less::Generators.const_get(self.class.name.sub(/Spec$/, '')) 100 | end 101 | 102 | def run_generator(args=default_arguments, config={}) 103 | capture(:stdout) { generator_class.start(args, config.reverse_merge(:destination_root => destination_root)) } 104 | end 105 | 106 | def generator(args=default_arguments, options={}, config={}) 107 | @generator ||= generator_class.new(args, options, config.reverse_merge(:destination_root => destination_root)) 108 | end 109 | 110 | def assert_file(relative, *contents) 111 | absolute = File.expand_path(relative, destination_root) 112 | assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not" 113 | read = File.read(absolute) if block_given? || !contents.empty? 114 | yield read if block_given? 115 | contents.each do |content| 116 | case content 117 | when String 118 | assert_equal content, read 119 | when Regexp 120 | assert_match content, read 121 | end 122 | end 123 | end 124 | alias :assert_directory :assert_file 125 | 126 | end 127 | 128 | end 129 | end 130 | 131 | -------------------------------------------------------------------------------- /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.9, 2.0, and jRuby 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 | --------------------------------------------------------------------------------