From 79905482bf7db11ed4a391f0b173d19214014118 Mon Sep 17 00:00:00 2001 From: grosser Date: Thu, 11 Feb 2010 23:27:34 +0100 Subject: [PATCH] basically a rewrite without all the caching logic, simply generate css for all updated less files, rake tasks is now more:generate --- README.markdown | 30 +++--- lib/controller_extension.rb | 2 +- lib/less/more.rb | 192 +++++++++--------------------------- tasks/more_tasks.rake | 10 +- test/more_test.rb | 149 ++++++++++++---------------- test/test_helper.rb | 30 +----- 6 files changed, 130 insertions(+), 283 deletions(-) diff --git a/README.markdown b/README.markdown index aba362d..23dcf1d 100644 --- a/README.markdown +++ b/README.markdown @@ -73,19 +73,19 @@ Any `.css` file placed in `app/stylesheets` will be copied into `public/styleshe Configuration ============= -To set the source path (the location of your LESS files): +Source path: the location of your LESS files (default: app/stylesheets) - Less::More.source_path = "/path/to/less/files" + Less::More.source_path = "public/stylesheets/less" -You can also set the destination path. Be careful with the formatting here, since this is in fact a route, and not a regular path. +Destination Path: where the css goes (public/destination_path) (default: stylesheets) Less::More.destination_path = "css" -More can compress your files by removing extra line breaks. This is enabled by default in the `production` environment. To change this setting, set: +More can compress your files by removing extra line breaks (default: true) - Less::More.compression = true + Less::More.compression = false -More inserts headers in the generated CSS files, letting people know that the file is in fact generated and shouldn't be edited directly. This is by default only enabled in development mode. You can disable this behavior if you want to. +More inserts headers in the generated CSS files, letting people know that the file is in fact generated and shouldn't be edited directly. (default: true) Less::More.header = false @@ -93,12 +93,6 @@ To configure More for a specific environment, add configuration options into the If you wish to apply the configuration to all environments, place them in `config/environment.rb`. -Heroku -====== - -The plugin works out-of-the-box on Heroku. - -Heroku has a read-only file system, which means caching the generated CSS with page caching is not an option. Heroku supports caching with Varnish, though, which the plugin will leverage by setting Cache-Control headers so that generated CSS is cached for one month. Tasks ===== @@ -107,7 +101,7 @@ More provides a set of Rake tasks to help manage your CSS files. To parse all LESS files and save the resulting CSS files to the destination path, run: - $ rake more:parse + $ rake more:generate To delete all generated CSS files, run: @@ -116,13 +110,10 @@ To delete all generated CSS files, run: This task will not delete any CSS files from the destination path, that does not have a corresponding LESS file in the source path. -Git -=== - -If you are using git to version control your code and LESS for all your stylesheets, you can add this entry to your `.gitignore` file: - - public/stylesheets +Git / SVN +========= +Check in all the generated css(destination path), they are only generated in development Documentation ============= @@ -134,5 +125,6 @@ Contributors ============ * August Lilleaas ([http://github.com/augustl](http://github.com/augustl)) * Logan Raarup ([http://github.com/logandk](http://github.com/logandk)) +* Michael Grosser ([http://github.com/grosser](http://github.com/grosser)) LESS is maintained by Alexis Sellier [http://github.com/cloudhead](http://github.com/cloudhead) diff --git a/lib/controller_extension.rb b/lib/controller_extension.rb index 9608670..b3ccf75 100644 --- a/lib/controller_extension.rb +++ b/lib/controller_extension.rb @@ -1,6 +1,6 @@ class ActionController::Base def process_with_less(*args) - Less::More.parse + Less::More.generate_all process_without_less(*args) end diff --git a/lib/less/more.rb b/lib/less/more.rb index 0a9e2ee..e5bdf16 100644 --- a/lib/less/more.rb +++ b/lib/less/more.rb @@ -15,187 +15,85 @@ end class Less::More - DEFAULTS = { - "production" => { - :compression => true, - :header => false, - :destination_path => "stylesheets" - }, - "development" => { - :compression => false, - :header => true, - :destination_path => "stylesheets" - } - } - HEADER = %{/*\n\n\n\n\n\tThis file was auto generated by Less (http://lesscss.org). To change the contents of this file, edit %s instead.\n\n\n\n\n*/} class << self - attr_writer :compression, :header, :page_cache, :destination_path - - # Returns true if compression is enabled. By default, compression is enabled in the production environment - # and disabled in the development and test environments. This value can be changed using: - # - # Less::More.compression = true - # - # You can put this line into config/environments/development.rb to enable compression for the development environments - def compression? - get_cvar(:compression) - end + # Less::More.compression = true/false --- compress generated css ? (default: false) + # Less::More.header = true/false --- insert editing warning into css ? (default: true) + # Less::More.destination_path = 'css' --- put css into public/??? (default: stylesheets) + # Less::More.source_path = 'public/stylesheets/less' --- where do less files live? (default: app/stylesheets) + attr_writer :compression, :header, :destination_path, :source_path - # Check wether or not we should page cache the generated CSS - def page_cache? - (not heroku?) && page_cache_enabled_in_environment_configuration? - end - - # For easy mocking. - def page_cache_enabled_in_environment_configuration? - Rails.configuration.action_controller.perform_caching - end - - # Tells the plugin to prepend HEADER to all generated CSS, informing users - # opening raw .css files that the file is auto-generated and that the - # .less file should be edited instead. - # - # Less::More.header = false - def header? - get_cvar(:header) + def header + @header.nil? ? true : @header end - - # The path, or route, where you want your .css files to live. + def destination_path - get_cvar(:destination_path) + @destination_path || 'stylesheets' end - - # Gets user set values or DEFAULTS. User set values gets precedence. - def get_cvar(cvar) - instance_variable_get("@#{cvar}") || (DEFAULTS[Rails.env] || DEFAULTS["production"])[cvar] - end - - # Returns true if the app is running on Heroku. When +heroku?+ is true, - # +page_cache?+ will always be false. - def heroku? - ENV.any? {|key, value| key =~ /^heroku/i } - end - - # Returns the LESS source path, see `source_path=` + def source_path - @source_path || Rails.root.join("app", "stylesheets") - end - - # Sets the source path for LESS files. This directory will be scanned recursively for all *.less files. Files prefixed - # with an underscore is considered to be partials and are not parsed directly. These files can be included using `@import` - # statements. *Example partial filename: _form.less* - # - # Default value is app/stylesheets - # - # Examples: - # Less::More.source_path = "/path/to/less/files" - # Less::More.source_path = Pathname.new("/other/path") - def source_path=(path) - @source_path = Pathname.new(path.to_s) - end - - # Checks if a .less or .lss file exists in Less::More.source_path matching - # the given parameters. - # - # Less::More.exists?(["screen"]) - # Less::More.exists?(["subdirectories", "here", "homepage"]) - def exists?(path_as_array) - return false if path_as_array[-1].starts_with?("_") - - pathname = pathname_from_array(path_as_array) - pathname && pathname.exist? + @source_path || 'app/stylesheets' end - - def cache_path - File.join(Rails.root, 'tmp', 'less-cache') + + def compression + @compression end - + # Generates the .css from a .less or .lss file in Less::More.source_path matching # the given parameters. # - # Less::More.generate(["screen"]) - # Less::More.generate(["subdirectories", "here", "homepage"]) - # - # Returns the CSS as a string. - def generate(path_as_array) - source = pathname_from_array(path_as_array) - - # put together our destination dir and path (need dir so we can create subdirectories) - destination_dir = source.dirname.to_s.gsub(source_path, cache_path) - destination = File.join(destination_dir, source.basename.to_s.gsub('.less', '.css').gsub('.lss', '.css')) + # Less::More.generate("screen.less") + # Less::More.generate("subdirectories/here/homepage.less") + def generate(source) + generated = File.join(Rails.root, 'public', destination_path, source.sub(/\.le?ss$/, '.css')) + path_to_source = File.join(Rails.root, source_path, source) # check if the destination file exists, and compare the modified times to see if it needs to be written - if File.exists?(destination) and File.new(destination).mtime >= File.new(source).mtime - # cached destination file is the same as the source, just return the cached file - css = File.read(destination) + if File.exists?(generated) and File.mtime(generated) >= File.mtime(path_to_source) + # up to date, nothing to do! else - # cached file doesn't exist or it's out of date - if source.extname == ".css" - # vanilla css - css = File.read(source) + # css file does not exist or is out of date + css = if File.extname(source) == ".css" + # vanilla css nothing to do! + File.read(path_to_source) else # less or lss file, compile it - engine = File.open(source) {|f| Less::Engine.new(f) } + engine = File.open(path_to_source){|f| Less::Engine.new(f) } css = engine.to_css - css.delete!("\n") if self.compression? - css = (HEADER % [source.to_s.sub(File.expand_path('.'), '')]) << css if self.header? + css.delete!("\n") if compression # TODO: use real compression ! + css = (HEADER % [File.join(source_path, source)]) << css if header + css end - # make sure the appropriate cache directory exists - FileUtils.mkdir_p destination_dir - # write the css to our cache directory - File.open(destination, "w") {|f| - f.puts css - } - end - # return the css - css + # write the css + FileUtils.mkdir_p File.dirname(generated) + File.open(generated, "w"){|f| f.write css } + end end - # Generates all the .css files. - def parse + # Generates all the .css files + def generate_all Less::More.all_less_files.each do |path| - # Get path - relative_path = path.relative_path_from(Less::More.source_path) - path_as_array = relative_path.to_s.split(File::SEPARATOR) - path_as_array[-1] = File.basename(path_as_array[-1], File.extname(path_as_array[-1])) - - # Generate CSS - css = Less::More.generate(path_as_array) - - # Store CSS - path_as_array[-1] = path_as_array[-1] + ".css" - destination = Pathname.new(File.join(Rails.root, "public", Less::More.destination_path)).join(*path_as_array) - destination.dirname.mkpath - - File.open(destination, "w") {|f| - f.puts css - } + relative_path = path.sub(File.join(Rails.root, source_path),'')[1..-1] + Less::More.generate(relative_path) end end # Removes all generated css files. - def clean + def remove_all_generated all_less_files.each do |path| - relative_path = path.relative_path_from(Less::More.source_path) + relative_path = path.sub(File.join(Rails.root, source_path), '') css_path = relative_path.to_s.sub(/(le?|c)ss$/, "css") - css_file = File.join(Rails.root, "public", Less::More.destination_path, css_path) + css_file = File.join(Rails.root, "public", destination_path, css_path) File.delete(css_file) if File.file?(css_file) end end - # Array of Pathname instances for all the less source files. + # Array of paths of less source files. def all_less_files - Dir[Less::More.source_path.join("**", "*.{css,less,lss}").to_s].map! {|f| Pathname.new(f) } - end - - # Converts ["foo", "bar"] into a `Pathname` based on Less::More.source_path. - def pathname_from_array(array) - path_spec = array.dup - path_spec[-1] = path_spec[-1] + ".{css,less,lss}" - Pathname.glob(File.join(self.source_path.to_s, *path_spec))[0] + all = Dir[File.join(Rails.root, source_path, "**", "*.{css,less,lss}")] + all.reject{|path| File.basename(path) =~ /^_/ } end end -end +end \ No newline at end of file diff --git a/tasks/more_tasks.rake b/tasks/more_tasks.rake index 101eb1e..5afb2a8 100644 --- a/tasks/more_tasks.rake +++ b/tasks/more_tasks.rake @@ -1,16 +1,16 @@ namespace :more do desc "Generate CSS files from LESS files" - task :parse => :environment do - puts "Parsing files from #{Less::More.source_path}." - Less::More.parse + task :generate => :environment do + puts "Generating css from less files in #{Less::More.source_path}." + Less::More.generate_all puts "Done." end desc "Remove generated CSS files" task :clean => :environment do - puts "Deleting files.." - Less::More.clean + puts "Deleting all generated css files in #{Less::More.destination_path}" + Less::More.remove_all_generated puts "Done." end end \ No newline at end of file diff --git a/test/more_test.rb b/test/more_test.rb index aa3de14..c2575c0 100644 --- a/test/more_test.rb +++ b/test/more_test.rb @@ -2,116 +2,95 @@ class MoreTest < Test::Unit::TestCase def setup - Less::More.class_eval do - ["@compression", "@header"].each {|v| - remove_instance_variable(v) if instance_variable_defined?(v) - } + [:compression, :header, :destination_path, :source_path].each do |variable| + Less::More.send("#{variable}=", nil) end end - def cleanup - `rm -rf #{Rails.root}/tmp/less-cache` - end - - - def test_getting_config_from_current_environment_or_defaults_to_production - Less::More::DEFAULTS["development"]["foo"] = 5 - Less::More::DEFAULTS["production"]["foo"] = 10 + def prepare_for_generate + Less::More.source_path = 'less_files' + Less::More.destination_path = 'css' - Rails.expects(:env).returns("development") - assert_equal 5, Less::More.get_cvar("foo") - - Rails.expects(:env).returns("production") - assert_equal 10, Less::More.get_cvar("foo") - - Rails.expects(:env).returns("staging") - assert_equal 10, Less::More.get_cvar("foo") + css = "#{Rails.root}/public/css" + `rm -rf #{css}` + `mkdir -p #{css}` + css end - def test_user_settings_wins_over_defaults - Less::More::DEFAULTS["development"][:compression] = true - assert_equal true, Less::More.compression? - - Less::More::DEFAULTS["development"][:compression] = false - assert_equal false, Less::More.compression? - - Less::More.compression = true - assert_equal true, Less::More.compression? + def test_default_for_header + assert_equal Less::More.header, true end - def test_page_cache_is_read_from_environment_configs - Less::More.expects(:heroku?).returns(false).times(2) - - Less::More.expects(:page_cache_enabled_in_environment_configuration?).returns(true) - assert_equal true, Less::More.page_cache? - - Less::More.expects(:page_cache_enabled_in_environment_configuration?).returns(false) - assert_equal false, Less::More.page_cache? + def test_header_can_be_overwritten + Less::More.header = false + assert_equal false, Less::More.header end - def test_page_cache_off_on_heroku - Less::More.page_cache = true - Less::More::DEFAULTS["development"][:page_cache] = true - - # The party pooper - Less::More.expects(:heroku?).returns(true) - - assert_equal false, Less::More.page_cache? + def test_default_for_source_path + assert_equal 'app/stylesheets', Less::More.source_path end - def test_compression - Less::More.compression = true - assert_equal Less::More.compression?, true - - Less::More.compression = false - assert_equal Less::More.compression?, false + def test_source_path_can_be_overwritten + Less::More.source_path = 'xxx' + assert_equal 'xxx', Less::More.source_path end - def test_source_path - Less::More.source_path = "/path/to/flaf" - assert_equal Pathname.new("/path/to/flaf"), Less::More.source_path + def test_default_for_destination_path + assert_equal 'stylesheets', Less::More.destination_path end - def test_exists - Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') - - assert Less::More.exists?(["test"]) - assert Less::More.exists?(["short"]) - assert Less::More.exists?(["sub", "test2"]) - - # Partials does not exist - assert !Less::More.exists?(["_global"]) - assert !Less::More.exists?(["shared", "_form"]) + def test_destination_path_can_be_overwritten + Less::More.destination_path = 'xxx' + assert_equal 'xxx', Less::More.destination_path end - def test_generate - cleanup + def test_default_for_compression + assert_equal nil, Less::More.compression + end - Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') + def test_compression_can_be_overwritten Less::More.compression = true - - assert Less::More.generate(["test"]).include?(".allforms { font-size: 110%; }body { color: #222222; }form { font-size: 110%; color: #ffffff;}") + assert_equal true, Less::More.compression end - - def test_header - cleanup - Less::More.expects(:header?).returns(false) - Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') - assert !Less::More.generate(["test"]).starts_with?("/*") + def test_generate_with_partials + css_path = prepare_for_generate + Less::More.generate_all + css = File.read(File.join(css_path, 'test.css')) + assert css.include?(".allforms { font-size: 110%; } +body { color: #222222; } +form { + font-size: 110%; + color: #ffffff; +}") + end - cleanup + def test_generate_does_not_parse_css + css_path = prepare_for_generate + Less::More.generate_all + original_css = File.read(File.join(css_path, 'plain.css')) + assert_equal File.read(File.join(Rails.root,'less_files', 'plain.css')), original_css + end - Less::More.expects(:header?).returns(true) - Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') - assert Less::More.generate(["test"]).starts_with?("/*") + def test_generate_uses_header_when_set + css_path = prepare_for_generate + Less::More.header = true + Less::More.generate_all + css = File.read(File.join(css_path, 'test.css')) + assert_match /^\/\*/, css # starts with comment -> header end - def test_pathname_from_array - Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') + def test_generate_uses_no_header_when_not_set + css_path = prepare_for_generate + Less::More.header = false + Less::More.generate_all + css = File.read(File.join(css_path, 'test.css')) + assert_match /^\.allforms/, css + end - assert Less::More.pathname_from_array(["test"]).exist? - assert Less::More.pathname_from_array(["short"]).exist? - assert Less::More.pathname_from_array(["sub", "test2"]).exist? + def test_generate_does_not_generate_partials + css_path = prepare_for_generate + Less::More.generate_all + assert !File.exist?(File.join(css_path, '_global.css')) end -end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index e40fb14..554b494 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,36 +1,14 @@ require 'test/unit' require 'rubygems' -require 'mocha' require 'active_support' -require 'action_controller' -ActionController::Base.session_store = nil - -module Rails - extend self - - def env - "development" - end - - def root - Pathname.new("/tmp") - end - - def backtrace_cleaner - ActiveSupport::BacktraceCleaner.new +class Rails + def self.root + File.expand_path(File.dirname(__FILE__)) end end -class ApplicationController < ActionController::Base -end - -begin - require 'less' -rescue LoadError => e - e.message << " (You may need to install the less gem)" - raise e -end +$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib') require 'less/more' \ No newline at end of file