New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If with delayed evaluation #934

Merged
merged 2 commits into from Oct 10, 2013
Jump to file or symbol
Failed to load files and symbols.
+97 −18
Diff settings

Always

Just for now

@@ -286,6 +286,9 @@ maps instead.
* There is a new {Sass::Script::Value::Helpers convenience API} for creating
Sass values from within ruby extensions.
* The `if()` function now only evaluates the argument corresponding to
the value of the first argument.
### Backwards Incompatibilities -- Must Read!
* Sass will now throw an error when `@extend` is used to extend a selector
View
@@ -1842,17 +1842,26 @@ Usage Example:
position: relative; left: $x; top: $y;
}
## Control Directives
## Control Directives & Expressions
SassScript supports basic control directives
for including styles only under some conditions
and expressions for including styles only under some conditions
or including the same style several times with variations.
**Note that control directives are an advanced feature,
and are not recommended in the course of day-to-day styling**.
They exist mainly for use in [mixins](#mixins),
particularly those that are part of libraries like [Compass](http://compass-style.org),
and so require substantial flexibility.
**Note:** Control directives are an advanced feature, and are uncommon
in day-to-day styling. They exist mainly for use in [mixins](#mixins),
particularly those that are part of libraries like
[Compass](http://compass-style.org), and so require substantial
flexibility.
### `if()`
The builtin `if()` function allows you to branch on a condition and
returns only one of two possible outcomes. It can be used in any script
context. The `if` function only evaluates the argument corresponding to
the one that it will return -- this allows you to refer to variables
that may not be defined or to have calculations that would otherwise
cause an error (E.g. divide by zero).
### `@if`
@@ -298,10 +298,12 @@ module Functions
# A class representing a Sass function signature.
#
# @attr args [Array<Symbol>] The names of the arguments to the function.
# @attr args [Array<String>] The names of the arguments to the function.
# @attr delayed_args [Array<String>] The names of the arguments whose evaluation should be
# delayed.
# @attr var_args [Boolean] Whether the function takes a variable number of arguments.
# @attr var_kwargs [Boolean] Whether the function takes an arbitrary set of keyword arguments.
Signature = Struct.new(:args, :var_args, :var_kwargs)
Signature = Struct.new(:args, :delayed_args, :var_args, :var_kwargs)
# Declare a Sass signature for a Ruby-defined function.
# This includes the names of the arguments,
@@ -338,9 +340,23 @@ module Functions
# In addition, if this is true and `:var_args` is not,
# Sass will ensure that the last argument passed is a hash.
def self.declare(method_name, args, options = {})
delayed_args = []
args = args.map do |a|
a = a.to_s
if a[0] == ?&
a = a[1..-1]
delayed_args << a
end
a
end
# We don't expose this functionality except to certain builtin methods.
if delayed_args.any? && method_name != :if
raise ArgumentError.new("Delayed arguments are not allowed for method #{method_name}")
end
@signatures[method_name] ||= []
@signatures[method_name] << Signature.new(
args.map {|s| s.to_s},
args,
delayed_args,
options[:var_args],
options[:var_kwargs])
end
@@ -478,6 +494,24 @@ def assert_integer(number, name = nil)
raise ArgumentError.new("Expected #{number} to be an integer")
end
end
# Performs a node that has been delayed for execution.
#
# @private
# @param node [Sass::Script::Tree::Node,
# Sass::Script::Value::Base] When this is a tree node, it's
# performed in the caller's environment. When it's a value
# (which can happen when the value had to be performed already
# -- like for a splat), it's returned as-is.
# @param env [Sass::Environment] The environment within which to perform the node.
# Defaults to the (read-only) environment of the caller.
def perform(node, env = environment.caller)
if node.is_a?(Sass::Script::Value::Base)
node
else
node.perform(env)
end
end
end
class << self
@@ -1963,17 +1997,17 @@ def keywords(args)
# @overload if($condition, $if-true, $if-false)
# @param $condition [Sass::Script::Value::Base] Whether the `$if-true` or
# `$if-false` will be returned
# @param $if-true [Sass::Script::Value::Base]
# @param $if-false [Sass::Script::Value::Base]
# @param $if-true [Sass::Script::Tree::Node]
# @param $if-false [Sass::Script::Tree::Node]
# @return [Sass::Script::Value::Base] `$if-true` or `$if-false`
def if(condition, if_true, if_false)
if condition.to_bool
if_true
perform(if_true)
else
if_false
perform(if_false)
end
end
declare :if, [:condition, :if_true, :if_false]
declare :if, [:condition, :"&if_true", :"&if_false"]
# Returns a unique CSS identifier. The identifier is returned as an unquoted
# string. The identifier returned is only guaranteed to be unique within the
@@ -119,9 +119,12 @@ def deep_copy
# @return [Sass::Script::Value] The SassScript object that is the value of the function call
# @raise [Sass::SyntaxError] if the function call raises an ArgumentError
def _perform(environment)
args = @args.map {|a| a.perform(environment)}
args = Sass::Util.enum_with_index(@args).
map {|a, i| perform_arg(a, environment, signature && signature.args[i])}
splat = Sass::Tree::Visitors::Perform.perform_splat(@splat, @kwarg_splat, environment)
keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
keywords = Sass::Util.map_hash(@keywords) do |k, v|
[k, perform_arg(v, environment, k.tr('-', '_'))]
end
if (fn = environment.function(@name))
return perform_sass_fn(fn, args, keywords, splat, environment)
end
@@ -203,6 +206,15 @@ def to_value(args)
private
def perform_arg(argument, environment, name)
return argument if signature && signature.delayed_args.include?(name)
argument.perform(environment)
end
def signature
@signature ||= Sass::Script::Functions.signature(name.to_sym, @args.size, @keywords.size)
end
def construct_ruby_args(name, args, keywords, splat, environment)
args += splat.to_a if splat
@@ -1202,6 +1202,28 @@ def test_if
assert_equal("1px", evaluate("if(true, 1px, 2px)"))
assert_equal("2px", evaluate("if(false, 1px, 2px)"))
assert_equal("2px", evaluate("if(null, 1px, 2px)"))
assert_equal("1px", evaluate("if(true, 1px, $broken)"))
assert_equal("1px", evaluate("if(false, $broken, 1px)"))
assert_equal("1px", evaluate("if(false, $if-true: $broken, $if-false: 1px)"))
assert_equal("1px", evaluate("if(true, $if-true: 1px, $if-false: $broken)"))
assert_equal(<<CSS, render(<<SCSS))
.if {
result: yay; }
CSS
.if {
$something: yay;
result: if(true, $if-true: $something, $if-false: $broken);
}
SCSS
assert_equal(<<CSS, render(<<SCSS))
.if {
result: 1px; }
CSS
.if {
$splat: 1px, 2px;
result: if(true, $splat...);
}
SCSS
end
def test_counter
@@ -1582,5 +1604,4 @@ def assert_error_message(message, value)
rescue Sass::SyntaxError => e
assert_equal(message, e.message)
end
end