diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..7e56959 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2009 Logan Raarup, August Lilleaas + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README deleted file mode 100644 index e69de29..0000000 diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..d612512 --- /dev/null +++ b/README.markdown @@ -0,0 +1,107 @@ +`more`: LESS in Rails apps +========================== + +**LESS** is a great gem that extends the standard CSS syntax with lots of good stuff. If you are not already using it, take +a look at [http://lesscss.org](http://lesscss.org). + +By default, **LESS** does not include any Rails specific functionality, which is why you need this Rails plugin. The usage of +the plugin is very simple and you don't even need to change any of your code. The plugin does the following: + +* Recursively looks for **LESS** (.less) files in `app/stylesheets` +* Ignores partials (prefixed with underscore: `_partial.less`) - these can be included with `@import` in your **LESS** files +* Saves the resulting CSS files to `public/stylesheets` using the same directory structure as `app/stylesheets` + +Of course all this can be configured, see *Configuration* below. + + +Installation +============ + +As a Rails Plugin +----------------- + +Use this to install as a plugin in a Ruby on Rails app: + + $ script/plugin install git://github.com/cloudhead/more.git + + +As a Rails Plugin (using git submodules) +---------------------------------------- + +Use this if you prefer the idea of being able to easily switch between using edge or a tagged version: + + $ git submodule add git://github.com/cloudhead/more.git vendor/plugins/more + $ script/runner vendor/plugins/more/install.rb + + +Usage +===== + +When you have installed this plugin, a new directory will be created in `app/stylesheets`. Put your **LESS** files in +this directory and they will automatically be parsed on the next request. + +Any **LESS** file in `app/stylesheets` will be parsed to an equivalent **CSS** file in `public/stylesheets`. Example: + + app/stylesheets/clients/screen.less => public/stylesheets/clients/screen.css + +If you prefix a file with an underscore, it is considered to be a partial, and will not be parsed unless included in another +file. Example: + + + @text_dark: #222; + + + @import "partials/_form"; + + input { color: @text_dark; } + + +Configuration +------------- + +If you want to configure `more` in a specific environment, put these configuration into the environment file, such +as `config/environments/development.rb`. If you wish to apply the configuration to all environments, put them in `config/environment.rb`. + +To set the source path (the location of your **LESS** files) + + Less::More.source_path = "/path/to/less/files" + +To set the destination path (the location of the parsed **CSS** files) + + Less::More.destination_path = "/path/to/css/files" + +`more` can compress your files by removing extra line breaks. This is enabled by default in the `production` environment. To change +this setting, set + + Less::More.compression = true + + +Tasks +===== + +`more` provides a couple 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 + +To delete all generated CSS files, run + + $ rake more:clean + +This task will not delete any CSS files from the destination path, that does not have a corresponding LESS file in +the source path + + +Documentation +============= + +To view the full RDoc documentation, go to [http://rdoc.info/projects/cloudhead/more](http://rdoc.info/projects/cloudhead/more) + +For more information about LESS, see [http://lesscss.org](http://lesscss.org) + + +Contributors +============ +* August Lilleaas +* Logan Raarup \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..aee8009 --- /dev/null +++ b/Rakefile @@ -0,0 +1,23 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the more plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the more plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'More' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.markdown') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/init.rb b/init.rb new file mode 100644 index 0000000..de4eff7 --- /dev/null +++ b/init.rb @@ -0,0 +1,14 @@ +begin + require 'less' +rescue LoadError => e + e.message << " (You may need to install the less gem)" + raise e +end + +require File.join(File.dirname(__FILE__), 'lib', 'more') + +if Rails.env == "production" + config.after_initialize { Less::More.parse } +else + ActionController::Base.before_filter { Less::More.parse } +end \ No newline at end of file diff --git a/install.rb b/install.rb new file mode 100644 index 0000000..243033f --- /dev/null +++ b/install.rb @@ -0,0 +1,4 @@ +require "fileutils" +include FileUtils::Verbose + +mkdir_p File.join(Rails.root, "app", "stylesheets") \ No newline at end of file diff --git a/lib/more.rb b/lib/more.rb new file mode 100644 index 0000000..c897d05 --- /dev/null +++ b/lib/more.rb @@ -0,0 +1,115 @@ +# Less::More provides methods for parsing LESS files in a rails application to CSS target files. +# +# When Less::More.parse is called, all files in Less::More.source_path will be parsed using LESS +# and saved as CSS files in Less::More.destination_path. If Less::More.compression is set to true, +# extra line breaks will be removed to compress the CSS files. +# +# By default, Less::More.parse will be called for each request in `development` environment and on +# application initialization in `production` environment. + +class Less::More + class << self + + attr_accessor_with_default :compression, (Rails.env == "production" ? true : false) + + # 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? + self.compression + 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 + + # Returns the destination path for the parsed CSS files, see `destination_path=` + def destination_path + @destination_path || Rails.root.join("public", "stylesheets") + end + + # Sets the destination path for the parsed CSS files. The directory structure from the source path will be retained, and + # all files, except partials prefixed with underscore, will be available with a .css extension. + # + # Default value is public/stylesheets + # + # Examples: + # Less::More.destination_path = "/path/to/css/files" + # Less::More.destination_path = Pathname.new("/other/path") + def destination_path=(path) + @destination_path = Pathname.new(path.to_s) + end + + # Returns a collection of matching files in the source path, which will be parsed. + # + # Example: + # Less::More.map => [{ :source => #, :destination => # }, ...] + def map + files = Pathname.glob(self.source_path.join("**", "*.less")).reject { |f| f.basename.to_s.starts_with? "_" } + + files.collect! do |file| + relative_path = file.relative_path_from(self.source_path) + { :source => file, :destination => self.destination_path.join(relative_path.dirname, relative_path.basename(relative_path.extname).to_s + ".css") } + end + end + + # Performs the core functionality of passing the LESS files from the source path through LESS and outputting CSS files to + # the destination path. The LESS files will only be parsed if the CSS file is not present, or if the modification time of + # the LESS file or any of its included files are newer than the CSS files. Imported files are recursively checked for updated + # modification times. + # + # If `compression` is enabled, extra line breaks will be removed. + # + # Example: + # Less::More.parse + def parse + self.map.each do |file| + file[:destination].dirname.mkpath unless file[:destination].dirname.exist? + + modified = self.read_imports(file[:source]).push(file[:source]).max { |x, y| x.mtime <=> y.mtime }.mtime + + if !file[:destination].exist? || modified > file[:destination].mtime + css = Less::Engine.new(file[:source].open).to_css + css = css.delete " \n" if self.compression? + + file[:destination].open("w") { |f| f.write css } + end + end + end + + # Recusively reads import statement from less files and returns an array of `Pathname`. It also checks that + # the files are present in the filesystem. + # + # Example: + # p = Pathname.new("/path/to/file") + # Less::More.read_imports(p) => [#, #, ...] + def read_imports(file) + imports = file.read.scan(/@import\s+(['"])(.*?)\1/i) + + imports.collect! do |v| + path = file.dirname.join(v.last + ".less") + path.exist? ? self.read_imports(path).push(path) : nil + end + + imports.flatten.compact.uniq + end + + end +end \ No newline at end of file diff --git a/tasks/more_tasks.rake b/tasks/more_tasks.rake new file mode 100644 index 0000000..dcfc890 --- /dev/null +++ b/tasks/more_tasks.rake @@ -0,0 +1,36 @@ +require 'less' +require File.join(File.dirname(__FILE__), '..', 'lib', 'more') + +namespace :more do + desc "Generate CSS files from LESS files" + task :parse => :environment do + files = Less::More.parse + + puts "Successfully parsed files:" + + files.each do |f| + puts " - " + f[:source].relative_path_from(Less::More.source_path).to_s + end + + puts "\nSaved to: " + Less::More.destination_path.to_s + end + + desc "Remove generated CSS files" + task :clean => :environment do + files = Less::More.map + + puts "Deleting files:" + + files.each do |f| + if f[:destination].exist? + puts " - " + f[:destination] + f[:destination].delete + + if f[:destination].parent.children.empty? and f[:destination].parent != Less::More.destination_path + puts " - " + f[:destination].parent + f[:destination].parent.delete + end + end + end + end +end \ No newline at end of file diff --git a/test/less_files/_global.less b/test/less_files/_global.less new file mode 100644 index 0000000..c99942a --- /dev/null +++ b/test/less_files/_global.less @@ -0,0 +1,2 @@ +@text_light: #ffffff; +@text_dark: #222222; \ No newline at end of file diff --git a/test/less_files/shared/_form.less b/test/less_files/shared/_form.less new file mode 100644 index 0000000..3e0ceb1 --- /dev/null +++ b/test/less_files/shared/_form.less @@ -0,0 +1,3 @@ +.allforms { + font-size: 110%; +} \ No newline at end of file diff --git a/test/less_files/test.less b/test/less_files/test.less new file mode 100644 index 0000000..008212f --- /dev/null +++ b/test/less_files/test.less @@ -0,0 +1,11 @@ +@import "_global"; +@import "shared/_form"; + +body { + color: @text_dark; +} + +form { + .allforms; + color: @text_light; +} \ No newline at end of file diff --git a/test/more_test.rb b/test/more_test.rb new file mode 100644 index 0000000..67c8ed6 --- /dev/null +++ b/test/more_test.rb @@ -0,0 +1,40 @@ +require 'test_helper' + +class MoreTest < Test::Unit::TestCase + def test_compression + Less::More.compression = true + assert_equal Less::More.compression?, true + + Less::More.compression = false + assert_equal Less::More.compression?, false + end + + def test_source_path + Less::More.source_path = "/path/to/flaf" + assert_equal Less::More.source_path, Pathname.new("/path/to/flaf") + end + + def test_destination_path + Less::More.destination_path = "/path/to/flaf" + assert_equal Less::More.destination_path, Pathname.new("/path/to/flaf") + end + + def test_map + Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') + Less::More.destination_path = File.join(File.dirname(__FILE__), 'css_files') + + assert_equal Less::More.map, [{ :source => Less::More.source_path.join("test.less"), :destination => Less::More.destination_path.join("test.css") }] + end + + def test_parse + Less::More.source_path = File.join(File.dirname(__FILE__), 'less_files') + Less::More.destination_path = File.join(File.dirname(__FILE__), 'css_files') + Less::More.compression = true + + Less::More.parse + + assert_equal Less::More.destination_path.join("test.css").read, ".allforms{font-size:110%;}body{color:#222222;}form{font-size:110%;color:#ffffff;}" + + Less::More.destination_path.join("test.css").delete + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..a51cd1d --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,17 @@ +require 'test/unit' + +begin + require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'config', 'environment') +rescue LoadError => e + puts "Please run these tests within a Rails app" + exit +end + +begin + require 'less' +rescue LoadError => e + e.message << " (You may need to install the less gem)" + raise e +end + +require 'more' \ No newline at end of file