1
0
Fork 0
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:
Nathan Weizenbaum 2008-06-05 15:06:48 -07:00
parent 8bbea4fe72
commit 1de7e1e983
11 changed files with 176 additions and 8 deletions

View file

@ -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.

View file

@ -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

View file

@ -1,6 +1,6 @@
require 'sass/constant/literal'
module Sass::Constant # :nodoc:
module Sass::Constant
class Color < Literal # :nodoc:
HTML4_COLORS = {

View 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

View file

@ -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

View file

@ -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

View file

@ -1,6 +1,6 @@
require 'sass/constant/literal'
module Sass::Constant # :nodoc:
module Sass::Constant
class Number < Literal # :nodoc:
attr_reader :unit

View file

@ -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)

View file

@ -1,6 +1,6 @@
require 'sass/constant/literal'
module Sass::Constant # :nodoc:
module Sass::Constant
class String < Literal # :nodoc:
def parse(value)

View file

@ -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 = {})

View 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