summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Lexy Munroe) <eevee.git@veekun.com>2016-06-08 17:24:31 -0700
committerEevee (Lexy Munroe) <eevee.git@veekun.com>2016-06-08 17:24:31 -0700
commit3edf81a3ebfbc913c1dceeba7add454e7ce6c1c9 (patch)
tree41b0653ff9db0c346057e735e12fd827f8249b22
parent466a68ce0278105828d41c36d2341e18aed89348 (diff)
downloadpyscss-3edf81a3ebfbc913c1dceeba7add454e7ce6c1c9.tar.gz
Add the Sass 3.4 *-exists functions. Fixes #226, #229, #353
-rw-r--r--scss/ast.py11
-rw-r--r--scss/compiler.py1
-rw-r--r--scss/extension/core.py71
-rw-r--r--scss/namespace.py14
-rw-r--r--scss/tests/files/general/global-variable-exists.css5
-rw-r--r--scss/tests/files/general/global-variable-exists.scss27
6 files changed, 116 insertions, 13 deletions
diff --git a/scss/ast.py b/scss/ast.py
index a8ab69b..122fbf2 100644
--- a/scss/ast.py
+++ b/scss/ast.py
@@ -212,10 +212,6 @@ class CallOp(Expression):
funct = None
try:
funct = calculator.namespace.function(func_name, argspec_len)
- # @functions take a ns as first arg. TODO: Python functions possibly
- # should too
- if getattr(funct, '__name__', None) == '__call':
- funct = partial(funct, calculator.namespace)
except KeyError:
try:
# DEVIATION: Fall back to single parameter
@@ -226,7 +222,12 @@ class CallOp(Expression):
log.error("Function not found: %s:%s", func_name, argspec_len, extra={'stack': True})
if funct:
- ret = funct(*args, **kwargs)
+ if getattr(funct, '_pyscss_needs_namespace', False):
+ # @functions and some Python functions take the namespace as an
+ # extra first argument
+ ret = funct(calculator.namespace, *args, **kwargs)
+ else:
+ ret = funct(*args, **kwargs)
if not isinstance(ret, Value):
raise TypeError("Expected Sass type as return value, got %r" % (ret,))
return ret
diff --git a/scss/compiler.py b/scss/compiler.py
index e0ca8d5..08c5638 100644
--- a/scss/compiler.py
+++ b/scss/compiler.py
@@ -684,6 +684,7 @@ class Compilation(object):
return e.retval
else:
return Null()
+ __call._pyscss_needs_namespace = True
return __call
_mixin = _call(mixin)
_mixin.mixin = mixin
diff --git a/scss/extension/core.py b/scss/extension/core.py
index 7967429..b939d1c 100644
--- a/scss/extension/core.py
+++ b/scss/extension/core.py
@@ -840,8 +840,71 @@ def map_merge_deep(*maps):
return Map(pairs)
+@ns.declare
+def keywords(value):
+ """Extract named arguments, as a map, from an argument list."""
+ expect_type(value, Arglist)
+ return value.extract_keywords()
+
+
# ------------------------------------------------------------------------------
-# Meta functions
+# Introspection functions
+
+# TODO feature-exists
+
+@ns.declare_internal
+def variable_exists(namespace, name):
+ expect_type(name, String)
+ try:
+ namespace.variable('$' + name.value)
+ except KeyError:
+ return Boolean(False)
+ else:
+ return Boolean(True)
+
+
+@ns.declare_internal
+def global_variable_exists(namespace, name):
+ expect_type(name, String)
+
+ # TODO this is... imperfect and invasive, but should be a good
+ # approximation
+ scope = namespace._variables
+ while len(scope.maps) > 1:
+ scope = scope.maps[-1]
+
+ try:
+ scope['$' + name.value]
+ except KeyError:
+ return Boolean(False)
+ else:
+ return Boolean(True)
+
+
+@ns.declare_internal
+def function_exists(namespace, name):
+ expect_type(name, String)
+ # TODO invasive, but there's no other way to ask for this at the moment
+ for fname, arity in namespace._functions.keys():
+ if name.value == fname:
+ return Boolean(True)
+ return Boolean(False)
+
+
+@ns.declare_internal
+def mixin_exists(namespace, name):
+ expect_type(name, String)
+ # TODO invasive, but there's no other way to ask for this at the moment
+ for fname, arity in namespace._mixins.keys():
+ if name.value == fname:
+ return Boolean(True)
+ return Boolean(False)
+
+
+@ns.declare
+def inspect(value):
+ return String.unquoted(value.render())
+
@ns.declare
def type_of(obj): # -> bool, number, string, color, list
@@ -877,11 +940,7 @@ def comparable(number1, number2):
and left.unit_denom == right.unit_denom)
-@ns.declare
-def keywords(value):
- """Extract named arguments, as a map, from an argument list."""
- expect_type(value, Arglist)
- return value.extract_keywords()
+# TODO call
# ------------------------------------------------------------------------------
diff --git a/scss/namespace.py b/scss/namespace.py
index 8076bca..939cde5 100644
--- a/scss/namespace.py
+++ b/scss/namespace.py
@@ -157,7 +157,17 @@ class Namespace(object):
return decorator
- def _auto_register_function(self, function, name):
+ def declare_internal(self, function):
+ """Like declare(), but the registered function will also receive the
+ current namespace as its first argument. Useful for functions that
+ inspect the state of the compilation, like ``variable-exists()``.
+ Probably not so useful for anything else.
+ """
+ function._pyscss_needs_namespace = True
+ self._auto_register_function(function, function.__name__, 1)
+ return function
+
+ def _auto_register_function(self, function, name, ignore_args=0):
name = name.replace('_', '-').rstrip('-')
argspec = inspect.getargspec(function)
@@ -170,7 +180,7 @@ class Namespace(object):
num_optional = len(argspec.defaults)
else:
num_optional = 0
- num_args = len(argspec.args)
+ num_args = len(argspec.args) - ignore_args
arities = range(num_args - num_optional, num_args + 1)
for arity in arities:
diff --git a/scss/tests/files/general/global-variable-exists.css b/scss/tests/files/general/global-variable-exists.css
new file mode 100644
index 0000000..a614018
--- /dev/null
+++ b/scss/tests/files/general/global-variable-exists.css
@@ -0,0 +1,5 @@
+blockquote {
+ background: lime;
+ color: red;
+ float: right;
+}
diff --git a/scss/tests/files/general/global-variable-exists.scss b/scss/tests/files/general/global-variable-exists.scss
new file mode 100644
index 0000000..4404ed0
--- /dev/null
+++ b/scss/tests/files/general/global-variable-exists.scss
@@ -0,0 +1,27 @@
+@mixin myblockquote {
+ blockquote {
+ @if global-variable-exists(blockquote-color) {
+ background: $blockquote-color;
+ }
+ @else {
+ background: lime;
+ }
+
+ @if mixin-exists(myblockquote) {
+ color: red;
+ }
+ @else {
+ color: blue;
+ }
+
+ @if variable-exists(foo) {
+ float: left;
+ }
+ $foo: 1;
+ @if variable-exists(foo) {
+ float: right;
+ }
+ }
+}
+
+@include myblockquote;