Permalink
Browse files

Extend LESS with Rails asset pipeline helpers. Fixes #1.

* Changed gemspec for actionpack 3.1.1 since this fixes asset helpers.
* Improved Dummy::Application's initializer for testing.
  • Loading branch information...
metaskills committed Oct 9, 2011
1 parent 5d5e7f1 commit 4bbfac54f5cfd534c5a53b6394e84a92fad2debe
View
@@ -3,6 +3,7 @@ CHANGELOG
2.0.2 (unreleased)
+* Extend LESS with Rails asset pipeline helpers.
* New testing support with MiniTest::Spec and dummy Rails::Application.
* New config.options hash passed down to the #to_css method.
View
@@ -1,5 +1,7 @@
guard 'minitest' do
- watch(%r|^test/(.*)_spec\.rb|)
- watch(%r|^lib/(.*)\.rb|) { |m| "spec/#{m[1]}_spec.rb" }
- watch(%r|^test/spec_helper\.rb|) { "spec" }
+ watch(%r|^lib/less/rails/(.*)\.rb|) { |m| "test/cases/#{m[1]}_spec.rb" }
+ # watch(%r|^lib/less/rails\.rb|) { "test/cases" }
+ # watch(%r|^test/dummy_app/.*|) { "test/cases" }
+ watch(%r|^test/spec_helper\.rb|) { "test/cases" }
+ watch(%r|^test/cases/(.*)_spec\.rb|)
end
View
@@ -21,6 +21,39 @@ MyProject::Application.configure do
end
```
+
+## Helpers
+
+When referencing assets use the following helpers in LESS.
+
+```css
+asset-path(@relative-asset-path) /* Returns a string to the asset. */
+asset-path("rails.png") /* Becomes: "/assets/rails.png" */
+
+asset-url(@relative-asset-path) /* Returns url reference to the asset. */
+asset-url("rails.png") /* Becomes: url(/assets/rails.png) */
+```
+
+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.
+
+```css
+image-url("rails.png") /* Becomes: url(/assets/rails.png) */
+font-url("rails.ttf") /* Becomes: url(/assets/rails.ttf) */
+video-url("rails.ttf") /* Becomes: url(/videos/rails.mp4) */
+audio-url("rails.ttf") /* Becomes: url(/audios/rails.mp3) */
+javascript-url("rails.js") /* Becomes: url(/assets/rails.js) */
+stylesheet-url("rails.css") /* Becomes: url(/assets/rails.css) */
+```
+
+Lastly, we provide a data url method for base64 encoding assets.
+
+```css
+asset-data-url("rails.png") /* Becomes: url(data:image/png;base64,iVBORw0K...) */
+```
+
+Please note that these helpers are only available server-side, and something like ERB templates should be used if client-side rendering is desired.
+
+
## License
Less::Rails is Copyright (c) 2011 Ken Collins, <ken@metaskills.net> and is distributed under the MIT license.
View
@@ -15,11 +15,8 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.add_runtime_dependency 'less', '~> 2.0.7'
- s.add_runtime_dependency 'railties', '~> 3.1.0'
- s.add_runtime_dependency 'actionpack', '~> 3.1.0'
- s.add_runtime_dependency 'sprockets', '~> 2.0.0'
- s.add_runtime_dependency 'tilt', '~> 1.3.2'
+ s.add_runtime_dependency 'actionpack', '~> 3.1.1'
s.add_development_dependency 'minitest'
s.add_development_dependency 'guard-minitest'
- s.add_development_dependency 'rails', '~> 3.1.0'
+ s.add_development_dependency 'rails', '~> 3.1.1'
end
View
@@ -1,2 +1,2 @@
-require "less/rails"
+require 'less/rails'
View
@@ -3,11 +3,13 @@ module Rails
end
end
+require 'less'
require 'rails'
require 'tilt'
require 'sprockets'
require 'sprockets/railtie'
require 'less/rails/version'
+require 'less/rails/helpers'
require 'less/rails/template_handlers'
require 'less/rails/railtie'
View
@@ -0,0 +1,116 @@
+module Less
+
+ def self.less
+ @less
+ end
+
+ def self.register_rails_helper(name, &block)
+ tree = @loader.require('less/tree')
+ tree.functions[name] = lambda do |node|
+ tree[:Anonymous].new block.call(tree, node)
+ end
+ end
+
+ module Rails
+ module Helpers
+
+ extend ActiveSupport::Concern
+
+ included do
+ Less.register_rails_helper('asset-path') { |tree, cxt| asset_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('asset-url') { |tree, cxt| asset_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('image-path') { |tree, cxt| image_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('image-url') { |tree, cxt| image_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('video-path') { |tree, cxt| video_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('video-url') { |tree, cxt| video_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('audio-path') { |tree, cxt| audio_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('audio-url') { |tree, cxt| audio_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('javascript-path') { |tree, cxt| javascript_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('javascript-url') { |tree, cxt| javascript_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('stylesheet-path') { |tree, cxt| stylesheet_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('stylesheet-url') { |tree, cxt| stylesheet_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('font-path') { |tree, cxt| asset_path unquote(cxt.toCSS()) }
+ Less.register_rails_helper('font-url') { |tree, cxt| asset_url unquote(cxt.toCSS()) }
+ Less.register_rails_helper('asset-data-url') { |tree, cxt| asset_data_url unquote(cxt.toCSS()) }
+ end
+
+ module ClassMethods
+
+ def asset_data_url(path)
+ "url(#{scope.asset_data_uri(path)})"
+ end
+
+ def asset_path(asset)
+ public_path(asset).inspect
+ end
+
+ def asset_url(asset)
+ "url(#{public_path(asset)})"
+ end
+
+ def image_path(img)
+ scope.image_path(img).inspect
+ end
+
+ def image_url(img)
+ "url(#{scope.image_path(img)})"
+ end
+
+ def video_path(video)
+ scope.video_path(video).inspect
+ end
+
+ def video_url(video)
+ "url(#{scope.video_path(video)})"
+ end
+
+ def audio_path(audio)
+ scope.audio_path(audio).inspect
+ end
+
+ def audio_url(audio)
+ "url(#{scope.audio_path(audio)})"
+ end
+
+ def javascript_path(javascript)
+ scope.javascript_path(javascript).inspect
+ end
+
+ def javascript_url(javascript)
+ "url(#{scope.javascript_path(javascript)})"
+ end
+
+ def stylesheet_path(stylesheet)
+ scope.stylesheet_path(stylesheet).inspect
+ end
+
+ def stylesheet_url(stylesheet)
+ "url(#{scope.stylesheet_path(stylesheet)})"
+ end
+
+
+ private
+
+ def scope
+ Less.Parser['scope']
+ end
+
+ def public_path(asset)
+ scope.asset_paths.compute_public_path asset, ::Rails.application.config.assets.prefix
+ end
+
+ def context_asset_data_uri(path)
+
+ end
+
+ def unquote(str)
+ s = str.to_s.strip
+ s =~ /^['"](.*?)['"]$/ ? $1 : s
+ end
+
+ end
+
+ end
+ end
+end
+
@@ -20,7 +20,7 @@ module LessContext
Sprockets.register_engine '.less', Less::Rails::LessTemplate
end
- initializer 'less-rails.setup' do |app|
+ initializer 'less-rails.environment' do |app|
app.assets.context_class.extend(LessContext)
app.assets.context_class.less_config = app.config.less
end
@@ -1,41 +1,42 @@
module Less
- module Rails
-
+ module Rails
class LessTemplate < Tilt::LessTemplate
+ include Helpers
+
TO_CSS_KEYS = [:compress, :optimization, :silent, :color]
def prepare
end
def evaluate(scope, locals, &block)
@output ||= begin
- parser = ::Less::Parser.new less_parser_options(scope)
+ Less.Parser['scope'] = scope
+ parser = ::Less::Parser.new config_to_less_parser_options(scope)
engine = parser.parse(data)
engine.to_css config_to_css_options(scope)
end
end
protected
- def config_paths(scope)
- config_from_rails(scope)[:paths]
+ def config_to_less_parser_options(scope)
+ paths = config_paths(scope) + scope.environment.paths
+ {:filename => eval_file, :line => line, :paths => paths}
end
def config_to_css_options(scope)
Hash[config_from_rails(scope).each.to_a].slice *TO_CSS_KEYS
end
+ def config_paths(scope)
+ config_from_rails(scope)[:paths]
+ end
+
def config_from_rails(scope)
scope.environment.context_class.less_config
end
-
- def less_parser_options(scope)
- paths = config_paths(scope) + scope.environment.paths
- {:filename => eval_file, :line => line, :paths => paths}
- end
- end
-
+ end
end
end
View
@@ -2,11 +2,41 @@
class HelpersSpec < Less::Rails::Spec
+ before { dummy_config.less.compress = false }
+
let(:helpers) { dummy_asset('helpers') }
it 'parse asset paths' do
-
+ line_for_helper('asset-path').must_equal 'asset-path: "/assets/rails.png";'
+ line_for_helper('asset-url').must_equal "asset-url: url(/assets/rails.png);"
+ line_for_helper('image-path').must_equal 'image-path: "/assets/rails.png";'
+ line_for_helper('image-url').must_equal "image-url: url(/assets/rails.png);"
+ line_for_helper('video-path').must_equal 'video-path: "/videos/rails.mp4";'
+ line_for_helper('video-url').must_equal "video-url: url(/videos/rails.mp4);"
+ line_for_helper('audio-path').must_equal 'audio-path: "/audios/rails.mp3";'
+ line_for_helper('audio-url').must_equal "audio-url: url(/audios/rails.mp3);"
+ line_for_helper('javascript-path').must_equal 'javascript-path: "/assets/rails.js";'
+ line_for_helper('javascript-url').must_equal "javascript-url: url(/assets/rails.js);"
+ line_for_helper('stylesheet-path').must_equal 'stylesheet-path: "/assets/rails.css";'
+ line_for_helper('stylesheet-url').must_equal "stylesheet-url: url(/assets/rails.css);"
+ line_for_helper('font-path').must_equal 'font-path: "/assets/rails.ttf";'
+ line_for_helper('font-url').must_equal "font-url: url(/assets/rails.ttf);"
+ end
+
+ it 'parses data urls ' do
+ line = line_for_helper('asset-data-url')
+ asset_data_url_regexp = %r{asset-data-url: url\((.*?)\)}
+ line.must_match asset_data_url_regexp
+ asset_data_url_match = line.match(asset_data_url_regexp)[1]
+ asset_data_url_expected = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw%2FeHBhY2tldCBiZWdpbj0i77u%2FIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8%2BIDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCNzY5NDE1QkQ2NkMxMUUwOUUzM0E5Q0E2RTgyQUExQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5NDE1Q0Q2NkMxMUUwOUUzM0E5Q0E2RTgyQUExQiI%2BIDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE3MzcyNTQ2RDY2QjExRTA5RTMzQTlDQTZFODJBQTFCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3Njk0MTVBRDY2QzExRTA5RTMzQTlDQTZFODJBQTFCIi8%2BIDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY%2BIDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8%2B0HhJ9AAAABBJREFUeNpi%2BP%2F%2FPwNAgAEACPwC%2FtuiTRYAAAAASUVORK5CYII%3D"
+ asset_data_url_match.must_equal asset_data_url_expected
end
+ private
+
+ def line_for_helper(name)
+ helpers.each_line.detect{ |line| line.include? name }.strip
+ end
+
end
@@ -1,7 +1,7 @@
.rails {
- asset-path: asset-path("rails.png", image);
- asset-url: asset-url("rails.png", image);
+ asset-path: asset-path("rails.png");
+ asset-url: asset-url("rails.png");
image-path: image-path("rails.png");
image-url: image-url("rails.png");
video-path: video-path("rails.mp4");
View
@@ -1,14 +1,16 @@
+require 'sprockets/railtie'
require 'action_controller/railtie'
require 'action_view/railtie'
-require 'sprockets/railtie'
+require 'action_view/base'
+require 'action_controller/base'
module Dummy
class Application < ::Rails::Application
config.root = File.join __FILE__, '..'
config.assets.enabled = true
+ config.active_support.deprecation = :stderr
-
end
end
View
@@ -5,7 +5,6 @@
require 'minitest/spec'
require 'minitest/autorun'
require 'dummy_app/init'
-require 'sprockets/helpers/rails_helper'
module Less
module Rails
@@ -41,7 +40,7 @@ def reset_config_options
end
def reset_assets_cache
- dummy_assets.version = ActiveSupport::SecureRandom.hex(32)
+ dummy_assets.version = SecureRandom.hex(32)
dummy_assets.cache = ActiveSupport::Cache::MemoryStore.new
end

0 comments on commit 4bbfac5

Please sign in to comment.