mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
Add support for Sass functions.
This commit is contained in:
parent
8bbea4fe72
commit
1de7e1e983
11 changed files with 176 additions and 8 deletions
15
lib/sass.rb
15
lib/sass.rb
|
@ -440,6 +440,21 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|||
# #main h6 {
|
||||
# font: italic small-caps bold 1.1em sans-serif; }
|
||||
#
|
||||
# === Functions
|
||||
#
|
||||
# Functions can be performed on Sass constants using the familiar CSS function syntax.
|
||||
# For example:
|
||||
#
|
||||
# #main
|
||||
# :color = hsl(0, 100%, 50%)
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# color: #ff0000; }
|
||||
#
|
||||
# Available functions are defined in Sass::Constant::Functions.
|
||||
#
|
||||
# == Directives
|
||||
#
|
||||
# Directives allow the author to directly issue instructions to the Sass compiler.
|
||||
|
|
|
@ -2,7 +2,10 @@ require 'sass/constant/operation'
|
|||
require 'sass/constant/literal'
|
||||
|
||||
module Sass
|
||||
module Constant # :nodoc:
|
||||
# This module contains various constant-script related functionality.
|
||||
module Constant
|
||||
# :stopdoc:
|
||||
|
||||
# The character that begins a constant.
|
||||
CONSTANT_CHAR = ?!
|
||||
|
||||
|
@ -254,5 +257,6 @@ module Sass
|
|||
to_return
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
module Sass::Constant
|
||||
class Color < Literal # :nodoc:
|
||||
|
||||
HTML4_COLORS = {
|
||||
|
|
55
lib/sass/constant/functions.rb
Normal file
55
lib/sass/constant/functions.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
module Sass::Constant
|
||||
# Methods in this module are accessible from the Sass constant context.
|
||||
# For example, you can write
|
||||
#
|
||||
# color = hsl(120, 100%, 50%)
|
||||
#
|
||||
# and it will call Sass::Constant::Functions#hsl.
|
||||
#
|
||||
# You can add your own functions to this module,
|
||||
# but there are a few things to keep in mind.
|
||||
# First of all, the arguments passed are (currently undocumented) Sass::Constant::Literal objects,
|
||||
# Literal objects are also the expected return values.
|
||||
#
|
||||
# Second, making Ruby functions accessible from Sass introduces the temptation
|
||||
# to do things like database access within stylesheets.
|
||||
# This temptation must be resisted.
|
||||
# Keep in mind that Sass stylesheets are only compiled once
|
||||
# at a somewhat indeterminate time
|
||||
# and then left as static CSS files.
|
||||
# Any dynamic CSS should be left in <style> tags in the HTML.
|
||||
module Functions
|
||||
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
||||
extend self
|
||||
|
||||
# Creates a Sass::Constant::Color object from hue, saturation, and lightness.
|
||||
# As per the CSS3 spec (http://www.w3.org/TR/css3-color/#hsl-color),
|
||||
# the units of hue are taken to be degrees,
|
||||
# and the units of saturation and lightness are taken to be percentages.
|
||||
# THis is not validated, though.
|
||||
def hsl(h, s, l)
|
||||
# This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
|
||||
h, s, l = [h, s, l].map { |a| a.value }
|
||||
h = (h % 360) / 360.0
|
||||
s /= 100.0
|
||||
l /= 100.0
|
||||
|
||||
m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
|
||||
m1 = l * 2 - m2
|
||||
Color.new([hue_to_rgb(m1, m2, h + 1.0/3),
|
||||
hue_to_rgb(m1, m2, h),
|
||||
hue_to_rgb(m1, m2, h - 1.0/3)].map { |c| (c * 0xff).round })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hue_to_rgb(m1, m2, h)
|
||||
h += 1 if h < 0
|
||||
h -= 1 if h > 1
|
||||
return m1 + (m2 - m1) * h * 6 if h * 6 < 1
|
||||
return m2 if h * 2 < 1
|
||||
return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
|
||||
return m1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,7 @@
|
|||
# :stopdoc:
|
||||
# Let the subclasses see the superclass
|
||||
module Sass::Constant; class Literal; end; end; # :nodoc:
|
||||
module Sass::Constant; class Literal; end; end;
|
||||
# :startdoc:
|
||||
|
||||
require 'sass/constant/string'
|
||||
require 'sass/constant/number'
|
||||
|
@ -27,7 +29,11 @@ class Sass::Constant::Literal # :nodoc:
|
|||
end
|
||||
|
||||
def initialize(value = nil)
|
||||
self.parse(value) if value
|
||||
if value.is_a?(String)
|
||||
self.parse(value)
|
||||
else
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
|
||||
def perform
|
||||
|
@ -46,6 +52,10 @@ class Sass::Constant::Literal # :nodoc:
|
|||
value.inspect
|
||||
end
|
||||
|
||||
def to_arglist
|
||||
[self]
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
protected
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
module Sass::Constant
|
||||
class Nil < Literal # :nodoc:
|
||||
def to_s
|
||||
''
|
||||
end
|
||||
|
||||
def to_arglist
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
module Sass::Constant
|
||||
class Number < Literal # :nodoc:
|
||||
|
||||
attr_reader :unit
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
require 'sass/constant/string'
|
||||
require 'sass/constant/number'
|
||||
require 'sass/constant/color'
|
||||
require 'sass/constant/functions'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
module Sass::Constant
|
||||
class Operation # :nodoc:
|
||||
def initialize(operand1, operand2, operator)
|
||||
@operand1 = operand1
|
||||
|
@ -18,10 +19,24 @@ module Sass::Constant # :nodoc:
|
|||
"(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})"
|
||||
end
|
||||
|
||||
def to_arglist
|
||||
return [self] unless @operator == :comma
|
||||
@operand1.to_arglist + @operand2.to_arglist
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def perform
|
||||
literal1 = @operand1.perform
|
||||
if @operator == :funcall && Functions.public_instance_methods.include?(literal1.to_s) && literal1.to_s !~ /^__/
|
||||
begin
|
||||
return Functions.send(literal1.to_s, *@operand2.to_arglist)
|
||||
rescue ArgumentError => e
|
||||
raise e unless e.backtrace.first =~ /:in `#{literal1.to_s}'$/
|
||||
raise Sass::SyntaxError.new("#{e.message} for `#{literal1.to_s}'")
|
||||
end
|
||||
end
|
||||
|
||||
literal2 = @operand2.perform
|
||||
begin
|
||||
literal1.send(@operator, literal2)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
module Sass::Constant
|
||||
class String < Literal # :nodoc:
|
||||
|
||||
def parse(value)
|
||||
|
|
|
@ -272,6 +272,20 @@ END
|
|||
render("foo\n +\n bar\n a: b\n baz\n c: d"))
|
||||
end
|
||||
|
||||
def test_functions
|
||||
assert_equal("a {\n b: #80ff80; }\n", render("a\n b = hsl(120, 100%, 75%)"))
|
||||
assert_equal("a {\n b: #81ff81; }\n", render("a\n b = hsl(120, 100%, 75%) + #010001"))
|
||||
end
|
||||
|
||||
def test_argument_error
|
||||
assert_raise(Sass::SyntaxError) { render("a\n b = hsl(1)") }
|
||||
end
|
||||
|
||||
def test_inaccessible_functions
|
||||
assert_equal("a {\n b: send(to_s); }\n", render("a\n b = send(to_s)"))
|
||||
assert_equal("a {\n b: public_instance_methods(); }\n", render("a\n b = public_instance_methods()"))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render(sass, options = {})
|
||||
|
|
51
test/sass/functions_test.rb
Normal file
51
test/sass/functions_test.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../../lib/sass'
|
||||
require 'sass/constant'
|
||||
|
||||
class SassFunctionTest < Test::Unit::TestCase
|
||||
def test_hsl
|
||||
# These tests adapted from the w3c browser tests
|
||||
# http://www.w3.org/Style/CSS/Test/CSS3/Color/20070927/html4/t040204-hsl-h-rotating-b.htm
|
||||
red = [255, 0, 0]
|
||||
assert_rgb_hsl(red, [0, '100%', '50%'])
|
||||
assert_rgb_hsl(red, [-360, '100%', '50%'])
|
||||
assert_rgb_hsl(red, [360, '100%', '50%'])
|
||||
assert_rgb_hsl(red, [6120, '100%', '50%'])
|
||||
|
||||
yellow = [255, 255, 0]
|
||||
assert_rgb_hsl(yellow, [60, '100%', '50%'])
|
||||
assert_rgb_hsl(yellow, [-300, '100%', '50%'])
|
||||
assert_rgb_hsl(yellow, [420, '100%', '50%'])
|
||||
assert_rgb_hsl(yellow, [-9660, '100%', '50%'])
|
||||
|
||||
green = [0, 255, 0]
|
||||
assert_rgb_hsl(green, [120, '100%', '50%'])
|
||||
assert_rgb_hsl(green, [-240, '100%', '50%'])
|
||||
assert_rgb_hsl(green, [480, '100%', '50%'])
|
||||
assert_rgb_hsl(green, [99840, '100%', '50%'])
|
||||
|
||||
cyan = [0, 255, 255]
|
||||
assert_rgb_hsl(cyan, [180, '100%', '50%'])
|
||||
assert_rgb_hsl(cyan, [-180, '100%', '50%'])
|
||||
assert_rgb_hsl(cyan, [540, '100%', '50%'])
|
||||
assert_rgb_hsl(cyan, [-900, '100%', '50%'])
|
||||
|
||||
blue = [0, 0, 255]
|
||||
assert_rgb_hsl(blue, [240, '100%', '50%'])
|
||||
assert_rgb_hsl(blue, [-120, '100%', '50%'])
|
||||
assert_rgb_hsl(blue, [600, '100%', '50%'])
|
||||
assert_rgb_hsl(blue, [-104880, '100%', '50%'])
|
||||
|
||||
purple = [255, 0, 255]
|
||||
assert_rgb_hsl(purple, [300, '100%', '50%'])
|
||||
assert_rgb_hsl(purple, [-60, '100%', '50%'])
|
||||
assert_rgb_hsl(purple, [660, '100%', '50%'])
|
||||
assert_rgb_hsl(purple, [2820, '100%', '50%'])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_rgb_hsl(rgb, hsl)
|
||||
assert_equal(rgb, Sass::Constant::Functions.hsl(*hsl.map(&Sass::Constant::Number.method(:new))).value)
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue