Skip to content
Snippets Groups Projects
Commit a9738572 authored by Martin's avatar Martin
Browse files

Import python-pyscss_1.3.5.orig.tar.gz

parent a2ad867f
No related branches found
No related tags found
No related merge requests found
Showing
with 454 additions and 187 deletions
......@@ -46,7 +46,7 @@ License and copyright
---------------------
Copyright © 2012 German M. Bravo (Kronuz), with additional heavy contributions
by Eevee (Alex Munroe). Licensed under the `MIT license`_.
by Eevee (Lexy Munroe). Licensed under the `MIT license`_.
.. _MIT license: http://www.opensource.org/licenses/mit-license.php
......@@ -67,6 +67,36 @@ working hours. Yelp does not claim copyright.
Changelog
---------
1.3.5 (June 8, 2016)
^^^^^^^^^^^^^^^^^^^^
* The new ``less2scss`` module attempts to convert Less syntax to SCSS.
* The ``*-exists`` functions from Sass 3.3 are now supported.
* The contents of a file ``@import``-ed in the middle of an input file now
appears in the expected place, not at the end of the output.
* Double-slashes within URLs, as often happens with base64-encoded data URLs,
are no longer stripped as comments.
* Nesting selectors that end with a combinator, e.g. ``div > { p { ... } }``,
now works correctly.
* ``invert()`` is now left alone when the argument is a number, indicating the
CSS filter rather than the Sass function.
* ``if()`` now evaluates its arguments lazily.
* ``str-slice()`` now silently corrects out-of-bounds indices.
* ``append()`` now defaults to returning a space-delimited list, when the given
list has fewer than two elements.
* ``-moz-calc`` and ``-webkit-calc`` are recognized as variants of the
``calc()`` CSS function.
* Filenames containing dots can now be imported.
* Properties with a computed value of ``null`` are now omitted from the output.
* The ``opacity`` token in IE's strange ``alpha(opacity=N)`` construct is now
recognized case-insensitively.
* The Compass gradient functions now recognize ``currentColor`` as a color.
* The fonts extension should now work under Python 3.
* Escaped astral plane characters no longer crash narrow Python 2 builds.
* The alpha value in ``rgba(...)`` is no longer truncated to only two decimal places.
* Some edge cases with float precision were fixed, so 742px - 40px is no longer
701.99999999px.
1.3.4 (Dec 15, 2014)
^^^^^^^^^^^^^^^^^^^^
......
......@@ -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
......@@ -532,3 +533,33 @@ class AlphaFunctionLiteral(Expression):
# TODO compress
contents = child.render()
return Function('opacity=' + contents, 'alpha', quotes=None)
class TernaryOp(Expression):
"""Sass implements this with a function:
prop: if(condition, true-value, false-value);
However, the second and third arguments are guaranteed not to be evaluated
unless necessary. Functions always receive evaluated arguments, so this is
a syntactic construct in disguise.
"""
def __repr__(self):
return '<%s(%r, %r, %r)>' % (
self.__class__.__name__,
self.condition,
self.true_expression,
self.false_expression,
)
def __init__(self, list_literal):
args = list_literal.items
if len(args) != 3:
raise SyntaxError("if() must have exactly 3 arguments")
self.condition, self.true_expression, self.false_expression = args
def evaluate(self, calculator, divide=False):
if self.condition.evaluate(calculator, divide=True):
return self.true_expression.evaluate(calculator, divide=True)
else:
return self.false_expression.evaluate(calculator, divide=True)
......@@ -182,6 +182,13 @@ class Compiler(object):
raise
def compile(self, *filenames):
# TODO this doesn't spit out the compilation itself, so if you want to
# get something out besides just the output, you have to copy this
# method. that sucks.
# TODO i think the right thing is to get all the constructors out of
# SourceFile, since it's really the compiler that knows the import
# paths and should be consulted about this. reconsider all this (but
# preserve it for now, SIGH) once importers are a thing
compilation = self.make_compilation()
for filename in filenames:
# TODO maybe SourceFile should not be exposed to the end user, and
......@@ -274,41 +281,29 @@ class Compilation(object):
return source
def run(self):
# this will compile and manage rule: child objects inside of a node
self.parse_children()
# Any @import will add the source file to self.sources and infect this
# list, so make a quick copy to insulate against that
# TODO maybe @import should just not do that?
for source_file in list(self.sources):
rule = SassRule(
source_file=source_file,
lineno=1,
unparsed_contents=source_file.contents,
namespace=self.root_namespace,
)
self.rules.append(rule)
self.manage_children(rule, scope=None)
self._warn_unused_imports(rule)
# this will manage @extends
# Run through all the rules and apply @extends in a separate pass
self.rules = self.apply_extends(self.rules)
rules_by_file, css_files = self.parse_properties()
all_rules = 0
all_selectors = 0
exceeded = ''
final_cont = ''
files = len(css_files)
for source_file in css_files:
rules = rules_by_file[source_file]
fcont, total_rules, total_selectors = self.create_css(rules)
all_rules += total_rules
all_selectors += total_selectors
# TODO i would love for the output of this function to be something
# useful for producing stats, so this stuff can live on the Scss
# class only
if not exceeded and all_selectors > 4095:
exceeded = " (IE exceeded!)"
log.error("Maximum number of supported selectors in Internet Explorer (4095) exceeded!")
if files > 1 and self.compiler.generate_source_map:
final_cont += "/* %s %s generated from '%s' add up to a total of %s %s accumulated%s */\n" % (
total_selectors,
'selector' if total_selectors == 1 else 'selectors',
source_file.path,
all_selectors,
'selector' if all_selectors == 1 else 'selectors',
exceeded)
final_cont += fcont
return final_cont
output, total_selectors = self.create_css(self.rules)
if total_selectors >= 4096:
log.error("Maximum number of supported selectors in Internet Explorer (4095) exceeded!")
return output
def parse_selectors(self, raw_selectors):
"""
......@@ -333,26 +328,6 @@ class Compilation(object):
return selectors, parents
# @print_timing(3)
def parse_children(self, scope=None):
children = []
root_namespace = self.root_namespace
for source_file in self.sources:
rule = SassRule(
source_file=source_file,
lineno=1,
unparsed_contents=source_file.contents,
namespace=root_namespace,
)
self.rules.append(rule)
children.append(rule)
for rule in children:
self.manage_children(rule, scope)
self._warn_unused_imports(self.rules[0])
def _warn_unused_imports(self, rule):
if not rule.legacy_compiler_options.get(
'warn_unused', self.compiler.warn_unused_imports):
......@@ -709,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
......@@ -1043,10 +1019,8 @@ class Compilation(object):
_rule = rule.copy()
_rule.unparsed_contents = block.unparsed_contents
_rule.namespace = rule.namespace
_rule.properties = {}
_rule.properties = []
self.manage_children(_rule, scope)
for name, value in _rule.properties.items():
rule.namespace.set_variable(name, value)
_at_vars = _at_variables
# @print_timing(10)
......@@ -1063,7 +1037,7 @@ class Compilation(object):
except IndexError:
is_var = False
if is_var:
warn_deprecated(rule, "Assignment with = is deprecated; use :.")
warn_deprecated(rule, "Assignment with = is deprecated; use : instead.")
calculator = self._make_calculator(rule.namespace)
prop = prop.strip()
prop = calculator.do_glob_math(prop)
......@@ -1121,6 +1095,8 @@ class Compilation(object):
# TODO kill this branch
pass
else:
if value.is_null:
return
style = rule.legacy_compiler_options.get(
'style', self.compiler.output_style)
compress = style == 'compressed'
......@@ -1268,7 +1244,7 @@ class Compilation(object):
# TODO implement !optional
warn_deprecated(
rule,
"Can't find any matching rules to extend {0!r} -- this"
"Can't find any matching rules to extend {0!r} -- this "
"will be fatal in 2.0, unless !optional is specified!"
.format(selector.render()))
continue
......@@ -1315,25 +1291,6 @@ class Compilation(object):
# Remove placeholder-only rules
return [rule for rule in rules if not rule.is_pure_placeholder]
# @print_timing(3)
def parse_properties(self):
css_files = []
seen_files = set()
rules_by_file = {}
for rule in self.rules:
source_file = rule.source_file
rules_by_file.setdefault(source_file, []).append(rule)
if rule.is_empty:
continue
if source_file not in seen_files:
seen_files.add(source_file)
css_files.append(source_file)
return rules_by_file, css_files
# @print_timing(3)
def create_css(self, rules):
"""
......@@ -1407,7 +1364,6 @@ class Compilation(object):
prev_ancestry_headers = []
total_rules = 0
total_selectors = 0
result = ''
......@@ -1488,7 +1444,6 @@ class Compilation(object):
header_string = header.render()
result += tb * (i + nesting) + header_string + sp + '{' + nl
total_rules += 1
if header.is_selector:
total_selectors += 1
......@@ -1507,7 +1462,7 @@ class Compilation(object):
if not result.endswith('\n'):
result += '\n'
return (result, total_rules, total_selectors)
return (result, total_selectors)
def _print_properties(self, properties, sc=True, sp=' ', tb='', nl='\n', lnl=' '):
result = ''
......
"""Constants and functions defined by the CSS specification, not specific to
Sass.
"""
from fractions import Fraction
from math import pi
import re
......@@ -166,15 +167,15 @@ BASE_UNIT_CONVERSIONS = {
# Lengths
'mm': (1, 'mm'),
'cm': (10, 'mm'),
'in': (25.4, 'mm'),
'px': (25.4 / 96, 'mm'),
'pt': (25.4 / 72, 'mm'),
'pc': (25.4 / 6, 'mm'),
'in': (Fraction(254, 10), 'mm'),
'px': (Fraction(254, 960), 'mm'),
'pt': (Fraction(254, 720), 'mm'),
'pc': (Fraction(254, 60), 'mm'),
# Angles
'deg': (1 / 360, 'turn'),
'grad': (1 / 400, 'turn'),
'rad': (pi / 2, 'turn'),
'deg': (Fraction(1, 360), 'turn'),
'grad': (Fraction(1, 400), 'turn'),
'rad': (Fraction.from_float(pi / 2), 'turn'),
'turn': (1, 'turn'),
# Times
......@@ -187,7 +188,7 @@ BASE_UNIT_CONVERSIONS = {
# Resolutions
'dpi': (1, 'dpi'),
'dpcm': (2.54, 'dpi'),
'dpcm': (Fraction(254 / 100), 'dpi'),
'dppx': (96, 'dpi'),
}
......@@ -252,7 +253,7 @@ def cancel_base_units(units, to_remove):
# Copy the dict since we're about to mutate it
to_remove = to_remove.copy()
remaining_units = []
total_factor = 1
total_factor = Fraction(1)
for unit in units:
factor, base_unit = get_conversion_factor(unit)
......@@ -448,7 +449,11 @@ escape_rx = re.compile(r"(?s)\\([0-9a-fA-F]{1,6})[\n\t ]?|\\(.)|\\\n")
def _unescape_one(match):
if match.group(1) is not None:
return six.unichr(int(match.group(1), 16))
try:
return six.unichr(int(match.group(1), 16))
except ValueError:
return (r'\U%08x' % int(match.group(1), 16)).decode(
'unicode-escape')
elif match.group(2) is not None:
return match.group(2)
else:
......@@ -480,6 +485,8 @@ _collapse_properties_space_re = re.compile(r'([:#])\s*{')
_variable_re = re.compile('^\\$[-a-zA-Z0-9_]+$')
_strings_re = re.compile(r'([\'"]).*?\1')
# TODO i know, this is clumsy and won't always work; it's better than nothing
_urls_re = re.compile(r'url[(].*?[)]')
_has_placeholder_re = re.compile(r'(?<!\w)([a-z]\w*)?%')
_prop_split_re = re.compile(r'[:=]')
......
......@@ -19,6 +19,11 @@ log = logging.getLogger(__name__)
ns = CompassExtension.namespace
def _is_color(value):
# currentColor is not a Sass color value, but /is/ a CSS color value
return isinstance(value, Color) or value == String('currentColor')
def __color_stops(percentages, *args):
if len(args) == 1:
if isinstance(args[0], (list, tuple, List)):
......@@ -42,7 +47,7 @@ def __color_stops(percentages, *args):
prev_color = False
for c in args:
for c in List.from_maybe(c):
if isinstance(c, Color):
if _is_color(c):
if prev_color:
stops.append(None)
colors.append(c)
......@@ -169,7 +174,7 @@ def _get_gradient_position_and_angle(args):
ret = None
skip = False
for a in arg:
if isinstance(a, Color):
if _is_color(a):
skip = True
break
elif isinstance(a, Number):
......@@ -205,7 +210,7 @@ def _get_gradient_color_stops(args):
color_stops = []
for arg in args:
for a in List.from_maybe(arg):
if isinstance(a, Color):
if _is_color(a):
color_stops.append(arg)
break
return color_stops or None
......
......@@ -34,12 +34,12 @@ class CoreExtension(Extension):
# import relative to the current file even if the current file isn't
# anywhere in the search path. is that right?
path = PurePosixPath(name)
if path.suffix:
search_exts = [path.suffix]
else:
search_exts = compilation.compiler.dynamic_extensions
basename = path.stem
search_exts = list(compilation.compiler.dynamic_extensions)
if path.suffix and path.suffix in search_exts:
basename = path.stem
else:
basename = path.name
relative_to = path.parent
search_path = [] # tuple of (origin, start_from)
if relative_to.is_absolute():
......@@ -378,6 +378,12 @@ def invert(color):
"""Returns the inverse (negative) of a color. The red, green, and blue
values are inverted, while the opacity is left alone.
"""
if isinstance(color, Number):
# invert(n) and invert(n%) are CSS3 filters and should be left
# intact
return String.unquoted("invert(%s)" % (color.render(),))
expect_type(color, Color)
r, g, b, a = color.rgba
return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a)
......@@ -576,9 +582,13 @@ def str_index(string, substring):
def str_slice(string, start_at, end_at=None):
expect_type(string, String)
expect_type(start_at, Number, unit=None)
py_start_at = start_at.to_python_index(len(string.value))
if end_at is None:
if int(start_at) == 0:
py_start_at = 0
else:
py_start_at = start_at.to_python_index(len(string.value))
if end_at is None or int(end_at) > len(string.value):
py_end_at = None
else:
expect_type(end_at, Number, unit=None)
......@@ -622,26 +632,6 @@ ns.set_function('floor', 1, Number.wrap_python_function(math.floor))
# ------------------------------------------------------------------------------
# List functions
def __parse_separator(separator, default_from=None):
if separator is None:
separator = 'auto'
separator = String.unquoted(separator).value
if separator == 'comma':
return True
elif separator == 'space':
return False
elif separator == 'auto':
if not default_from:
return True
elif len(default_from) < 2:
return True
else:
return default_from.use_comma
else:
raise ValueError('Separator must be auto, comma, or space')
# TODO get the compass bit outta here
@ns.declare_alias('-compass-list-size')
@ns.declare
......@@ -725,12 +715,26 @@ def max_(*lst):
@ns.declare
def append(lst, val, separator=None):
def append(lst, val, separator=String.unquoted('auto')):
expect_type(separator, String)
ret = []
ret.extend(List.from_maybe(lst))
ret.append(val)
use_comma = __parse_separator(separator, default_from=lst)
separator = separator.value
if separator == 'comma':
use_comma = True
elif separator == 'space':
use_comma = False
elif separator == 'auto':
if len(lst) < 2:
use_comma = False
else:
use_comma = lst.use_comma
else:
raise ValueError('Separator must be auto, comma, or space')
return List(ret, use_comma=use_comma)
......@@ -836,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
......@@ -873,16 +940,4 @@ 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()
# ------------------------------------------------------------------------------
# Miscellaneous
@ns.declare
def if_(condition, if_true, if_false=Null()):
return if_true if condition else if_false
# TODO call
......@@ -260,7 +260,7 @@ def font_sheet(g, **kwargs):
height = float(m.group(1))
else:
height = None
_glyph = tempfile.NamedTemporaryFile(delete=False, suffix=".svg")
_glyph = tempfile.NamedTemporaryFile(delete=False, suffix=".svg", mode='w')
_glyph.file.write(svgtext)
_glyph.file.close()
yield _glyph.name, width, height
......@@ -294,7 +294,7 @@ def font_sheet(g, **kwargs):
try:
if type_ == 'eot':
ttf_path = asset_paths['ttf']
with open(ttf_path) as ttf_fh:
with open(ttf_path, 'rb') as ttf_fh:
contents = ttf2eot(ttf_fh.read())
if contents is not None:
with open(asset_path, 'wb') as asset_fh:
......@@ -304,7 +304,7 @@ def font_sheet(g, **kwargs):
if type_ == 'ttf':
contents = None
if autohint:
with open(asset_path) as asset_fh:
with open(asset_path, 'rb') as asset_fh:
contents = ttfautohint(asset_fh.read())
if contents is not None:
with open(asset_path, 'wb') as asset_fh:
......@@ -330,7 +330,7 @@ def font_sheet(g, **kwargs):
contents = None
if type_ == 'eot':
ttf_path = asset_paths['ttf']
with open(ttf_path) as ttf_fh:
with open(ttf_path, 'rb') as ttf_fh:
contents = ttf2eot(ttf_fh.read())
if contents is None:
continue
......@@ -338,7 +338,7 @@ def font_sheet(g, **kwargs):
_tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.' + type_)
_tmp.file.close()
font.generate(_tmp.name)
with open(_tmp.name) as asset_fh:
with open(_tmp.name, 'rb') as asset_fh:
if autohint:
if type_ == 'ttf':
_contents = asset_fh.read()
......
......@@ -26,6 +26,7 @@ from scss.ast import MapLiteral
from scss.ast import ArgspecLiteral
from scss.ast import FunctionLiteral
from scss.ast import AlphaFunctionLiteral
from scss.ast import TernaryOp
from scss.cssdefs import unescape
from scss.types import Color
from scss.types import Function
......@@ -96,10 +97,11 @@ parser SassExpression:
token VAR: "\$[-a-zA-Z0-9_]+"
# Cheating, to make sure these only match function names.
# The last of these is the IE filter nonsense
token LITERAL_FUNCTION: "(calc|expression|progid:[\w.]+)(?=[(])"
token LITERAL_FUNCTION: "(-moz-calc|-webkit-calc|calc|expression|progid:[\w.]+)(?=[(])"
token ALPHA_FUNCTION: "alpha(?=[(])"
token OPACITY: "((?i)opacity)"
token URL_FUNCTION: "url(?=[(])"
token IF_FUNCTION: "if(?=[(])"
# This must come AFTER the above
token FNCT: "[-a-zA-Z_][-a-zA-Z0-9_]*(?=\()"
......@@ -244,10 +246,13 @@ parser SassExpression:
# filter syntax, where it appears as alpha(opacity=NN). Since = isn't
# normally valid Sass, we have to special-case it here
| ALPHA_FUNCTION LPAR (
"opacity" "=" atom RPAR
OPACITY "=" atom RPAR
{{ return AlphaFunctionLiteral(atom) }}
| argspec RPAR {{ return CallOp("alpha", argspec) }}
)
# This is a ternary operator, disguised as a function
| IF_FUNCTION LPAR expr_lst RPAR
{{ return TernaryOp(expr_lst) }}
| LITERAL_FUNCTION LPAR interpolated_function RPAR
{{ return Interpolation.maybe(interpolated_function, type=Function, function_name=LITERAL_FUNCTION) }}
| FNCT LPAR argspec RPAR {{ return CallOp(FNCT, argspec) }}
......
......@@ -26,6 +26,7 @@ from scss.ast import MapLiteral
from scss.ast import ArgspecLiteral
from scss.ast import FunctionLiteral
from scss.ast import AlphaFunctionLiteral
from scss.ast import TernaryOp
from scss.cssdefs import unescape
from scss.types import Color
from scss.types import Function
......@@ -42,7 +43,6 @@ class SassExpressionScanner(Scanner):
patterns = None
_patterns = [
('"="', '='),
('"opacity"', 'opacity'),
('":"', ':'),
('","', ','),
('SINGLE_STRING_GUTS', "([^'\\\\#]|[\\\\].|#(?![{]))*"),
......@@ -84,9 +84,11 @@ class SassExpressionScanner(Scanner):
('KWVAR', '\\$[-a-zA-Z0-9_]+(?=\\s*:)'),
('SLURPYVAR', '\\$[-a-zA-Z0-9_]+(?=[.][.][.])'),
('VAR', '\\$[-a-zA-Z0-9_]+'),
('LITERAL_FUNCTION', '(calc|expression|progid:[\\w.]+)(?=[(])'),
('LITERAL_FUNCTION', '(-moz-calc|-webkit-calc|calc|expression|progid:[\\w.]+)(?=[(])'),
('ALPHA_FUNCTION', 'alpha(?=[(])'),
('OPACITY', '((?i)opacity)'),
('URL_FUNCTION', 'url(?=[(])'),
('IF_FUNCTION', 'if(?=[(])'),
('FNCT', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\()'),
('BAREWORD', '(?!\\d)(\\\\[0-9a-fA-F]{1,6}|\\\\.|[-a-zA-Z0-9_])+'),
('BANG_IMPORTANT', '!\\s*important'),
......@@ -339,8 +341,8 @@ class SassExpression(Parser):
ALPHA_FUNCTION = self._scan('ALPHA_FUNCTION')
LPAR = self._scan('LPAR')
_token_ = self._peek(self.atom_rsts_)
if _token_ == '"opacity"':
self._scan('"opacity"')
if _token_ == 'OPACITY':
OPACITY = self._scan('OPACITY')
self._scan('"="')
atom = self.atom()
RPAR = self._scan('RPAR')
......@@ -349,6 +351,12 @@ class SassExpression(Parser):
argspec = self.argspec()
RPAR = self._scan('RPAR')
return CallOp("alpha", argspec)
elif _token_ == 'IF_FUNCTION':
IF_FUNCTION = self._scan('IF_FUNCTION')
LPAR = self._scan('LPAR')
expr_lst = self.expr_lst()
RPAR = self._scan('RPAR')
return TernaryOp(expr_lst)
elif _token_ == 'LITERAL_FUNCTION':
LITERAL_FUNCTION = self._scan('LITERAL_FUNCTION')
LPAR = self._scan('LPAR')
......@@ -537,9 +545,9 @@ class SassExpression(Parser):
return Interpolation.maybe(parts)
atom_chks_ = frozenset(['BAREWORD', 'INTERP_START'])
expr_map_or_list_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'RPAR', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE', '","'])
u_expr_chks = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BAREWORD', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'ALPHA_FUNCTION', 'VAR', 'NUM', 'FNCT', 'LITERAL_FUNCTION', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
m_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
expr_map_or_list_rsts__ = frozenset(['LPAR', 'RPAR', 'BANG_IMPORTANT', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'DOUBLE_QUOTE', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'IF_FUNCTION', 'SINGLE_QUOTE', '","'])
u_expr_chks = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'VAR', 'NUM', 'FNCT', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'SINGLE_QUOTE'])
m_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
interpolated_bare_url_rsts_ = frozenset(['RPAR', 'INTERP_START', 'BAREURL', 'SPACE'])
argspec_items_rsts = frozenset(['RPAR', 'END', '","'])
expr_slst_chks = frozenset(['INTERP_END', 'RPAR', 'END', '":"', '","'])
......@@ -547,39 +555,39 @@ class SassExpression(Parser):
goal_interpolated_literal_rsts = frozenset(['END', 'INTERP_START'])
expr_map_or_list_rsts = frozenset(['RPAR', '":"', '","'])
goal_interpolated_literal_with_vars_rsts = frozenset(['VAR', 'END', 'INTERP_START'])
argspec_item_chks = frozenset(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
argspec_item_chks = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'IF_FUNCTION', 'SINGLE_QUOTE'])
a_expr_chks = frozenset(['ADD', 'SUB'])
interpolated_function_parens_rsts = frozenset(['LPAR', 'RPAR', 'INTERP_START'])
expr_slst_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'END', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'FNCT', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'RPAR', '":"', 'NOT', 'INTERP_END', 'BANG_IMPORTANT', 'SINGLE_QUOTE', '","'])
interpolated_bareword_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SPACE', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
atom_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'VAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'UNITS', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
or_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'OR', 'NOT', 'SINGLE_QUOTE', '","'])
expr_slst_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'NOT', 'SINGLE_QUOTE', '","'])
interpolated_bareword_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'GT', 'END', 'SPACE', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
atom_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'VAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'UNITS', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
or_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'OR', 'NOT', 'SINGLE_QUOTE', '","'])
argspec_chks_ = frozenset(['END', 'RPAR'])
interpolated_string_single_rsts = frozenset(['SINGLE_QUOTE', 'INTERP_START'])
interpolated_bareword_rsts_ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'INTERP_END', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'BAREWORD', 'GT', 'END', 'SPACE', 'SIGN', 'LITERAL_FUNCTION', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
and_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'AND', 'OR', 'NOT', 'SINGLE_QUOTE', '","'])
comparison_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'ADD', 'FNCT', 'VAR', 'EQ', 'AND', 'GE', 'SINGLE_QUOTE', 'NOT', 'OR', '","'])
interpolated_bareword_rsts_ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'INTERP_END', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'BAREWORD', 'IF_FUNCTION', 'GT', 'END', 'SPACE', 'SIGN', 'LITERAL_FUNCTION', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
and_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'AND', 'SINGLE_QUOTE', 'NOT', 'OR', '","'])
comparison_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'ADD', 'FNCT', 'VAR', 'EQ', 'AND', 'GE', 'SINGLE_QUOTE', 'NOT', 'OR', '","'])
argspec_chks = frozenset(['DOTDOTDOT', 'SLURPYVAR'])
atom_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'SLURPYVAR', 'ALPHA_FUNCTION', 'RPAR', 'BANG_IMPORTANT', '"opacity"', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', 'BAREWORD', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'DOTDOTDOT', 'NOT', 'SINGLE_QUOTE'])
atom_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'SLURPYVAR', 'ALPHA_FUNCTION', 'RPAR', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'OPACITY', 'DOTDOTDOT', 'NOT', 'SINGLE_QUOTE'])
interpolated_string_double_rsts = frozenset(['DOUBLE_QUOTE', 'INTERP_START'])
atom_chks__ = frozenset(['COLOR', 'VAR'])
expr_map_or_list_rsts_ = frozenset(['RPAR', '","'])
u_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BAREWORD', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'ALPHA_FUNCTION', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'LITERAL_FUNCTION', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
u_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'SINGLE_QUOTE'])
interpolated_url_chks = frozenset(['INTERP_START_URL_HACK', 'BAREURL_HEAD_HACK'])
atom_chks = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'DOTDOTDOT', 'INTERP_START', 'RPAR', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'SIGN', 'SINGLE_QUOTE'])
interpolated_url_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'SINGLE_QUOTE', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'INTERP_START_URL_HACK', 'BANG_IMPORTANT', 'BAREURL_HEAD_HACK'])
atom_chks = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'SLURPYVAR', 'ALPHA_FUNCTION', 'RPAR', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'DOTDOTDOT', 'NOT', 'SINGLE_QUOTE'])
interpolated_url_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'SINGLE_QUOTE', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'INTERP_START_URL_HACK', 'IF_FUNCTION', 'BAREURL_HEAD_HACK'])
comparison_chks = frozenset(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ'])
argspec_items_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'END', 'SLURPYVAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'DOTDOTDOT', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'RPAR', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
a_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","'])
argspec_items_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'SLURPYVAR', 'ALPHA_FUNCTION', 'RPAR', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'DOTDOTDOT', 'NOT', 'SINGLE_QUOTE'])
a_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","'])
interpolated_string_rsts = frozenset(['DOUBLE_QUOTE', 'SINGLE_QUOTE'])
interpolated_bareword_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
interpolated_bareword_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'IF_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","'])
m_expr_chks = frozenset(['MUL', 'DIV', 'MOD'])
goal_interpolated_literal_with_vars_rsts_ = frozenset(['VAR', 'INTERP_START'])
interpolated_bare_url_rsts = frozenset(['RPAR', 'INTERP_START'])
argspec_items_chks = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
argspec_rsts = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'DOTDOTDOT', 'INTERP_START', 'RPAR', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'SIGN', 'SINGLE_QUOTE'])
atom_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'RPAR', 'SINGLE_QUOTE'])
argspec_items_rsts__ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'SLURPYVAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'DOTDOTDOT', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE'])
argspec_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'END', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'RPAR', 'SINGLE_QUOTE'])
argspec_items_chks = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'IF_FUNCTION', 'SINGLE_QUOTE'])
argspec_rsts = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'SLURPYVAR', 'ALPHA_FUNCTION', 'RPAR', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', 'BAREWORD', 'IF_FUNCTION', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'DOTDOTDOT', 'NOT', 'SINGLE_QUOTE'])
atom_rsts = frozenset(['BANG_IMPORTANT', 'LPAR', 'DOUBLE_QUOTE', 'IF_FUNCTION', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'RPAR', 'SINGLE_QUOTE'])
argspec_items_rsts__ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'BANG_IMPORTANT', 'SLURPYVAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'DOTDOTDOT', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'IF_FUNCTION', 'SINGLE_QUOTE'])
argspec_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'IF_FUNCTION', 'END', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'BANG_IMPORTANT', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'VAR', 'FNCT', 'NOT', 'RPAR', 'SINGLE_QUOTE'])
......@@ -3,7 +3,9 @@ from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division
import os
from pathlib import Path
from collections import namedtuple
import six
......@@ -43,6 +45,8 @@ _default_scss_vars = {
'$--curlybracketclosed': String.unquoted('}'),
}
SourceFileTuple = namedtuple('SourceFileTuple', ('parent_dir', 'filename'))
# TODO using this should spew an actual deprecation warning
class Scss(object):
......@@ -182,7 +186,9 @@ class Scss(object):
source = SourceFile.from_string(contents, relpath=name)
compilation.add_source(source)
return compiler.call_and_catch_errors(compilation.run)
compiled = compiler.call_and_catch_errors(compilation.run)
self.source_files = list(SourceFileTuple(*os.path.split(s.path)) for s in compilation.source_index.values())
return compiled
# Old, old alias
Compilation = compile
......
# -*- coding: utf-8 -*-
"""
Tool for converting Less files to Scss
Usage: python -m scss.less2scss [file]
"""
# http://stackoverflow.com/questions/14970224/anyone-know-of-a-good-way-to-convert-from-less-to-sass
from __future__ import unicode_literals, absolute_import, print_function
import re
import os
import sys
class Less2Scss(object):
at_re = re.compile(r'@(?!(media|import|mixin|font-face)(\s|\())')
mixin_re = re.compile(r'\.([\w\-]*)\s*\((.*)\)\s*\{')
include_re = re.compile(r'(\s|^)\.([\w\-]*\(?.*\)?;)')
functions_map = {
'spin': 'adjust-hue',
}
functions_re = re.compile(r'(%s)\(' % '|'.join(functions_map))
def convert(self, content):
content = self.convertVariables(content)
content = self.convertMixins(content)
content = self.includeMixins(content)
content = self.convertFunctions(content)
return content
def convertVariables(self, content):
# Matches any @ that doesn't have 'media ' or 'import ' after it.
content = self.at_re.sub('$', content)
return content
def convertMixins(self, content):
content = self.mixin_re.sub('@mixin \1(\2) {', content)
return content
def includeMixins(self, content):
content = self.mixin_re.sub('\1@include \2', content)
return content
def convertFunctions(self, content):
content = self.functions_re.sub(lambda m: '%s(' % self.functions_map[m.group(0)], content)
return content
def less2scss(options, args):
if not args:
args = ['-']
less2scss = Less2Scss()
for source_path in args:
if source_path == '-':
source = sys.stdin
destiny = sys.stdout
else:
try:
source = open(source_path)
destiny_path, ext = os.path.splitext(source_path)
destiny_path += '.scss'
if not options.force and os.path.exists(destiny_path):
raise IOError("File already exists: %s" % destiny_path)
destiny = open(destiny_path, 'w')
except Exception as e:
error = "%s" % e
if destiny_path in error:
ignoring = "Ignoring"
else:
ignoring = "Ignoring %s" % destiny_path
print("WARNING -- %s. %s" % (ignoring, error), file=sys.stderr)
continue
content = source.read()
content = less2scss.convert(content)
destiny.write(content)
def main():
from optparse import OptionParser, SUPPRESS_HELP
parser = OptionParser(usage="Usage: %prog [file]",
description="Converts Less files to Scss.",
add_help_option=False)
parser.add_option("-f", "--force", action="store_true",
dest="force", default=False,
help="Forces overwriting output file if it already exists")
parser.add_option("-?", action="help", help=SUPPRESS_HELP)
parser.add_option("-h", "--help", action="help",
help="Show this message and exit")
parser.add_option("-v", "--version", action="store_true",
help="Print version and exit")
options, args = parser.parse_args()
if options.version:
from scss.tool import print_version
print_version()
else:
less2scss(options, args)
if __name__ == "__main__":
main()
......@@ -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:
......
......@@ -46,8 +46,8 @@ from __future__ import unicode_literals
import sys
VERSION_INFO = (1, 3, 4)
DATE_INFO = (2014, 12, 15) # YEAR, MONTH, DAY
VERSION_INFO = (1, 3, 5)
DATE_INFO = (2016, 6, 8) # YEAR, MONTH, DAY
VERSION = '.'.join(str(i) for i in VERSION_INFO)
REVISION = '%04d%02d%02d' % DATE_INFO
BUILD_INFO = "pyScss v" + VERSION + " (" + REVISION + ")"
......
......@@ -91,6 +91,15 @@ class SimpleSelector(object):
For lack of a better name, each of the individual parts is merely called a
"token".
Note that it's possible to have zero tokens. This isn't legal CSS, but
it's perfectly legal Sass, since you might nest blocks like so:
body > {
div {
...
}
}
"""
def __init__(self, combinator, tokens):
self.combinator = combinator
......@@ -250,10 +259,12 @@ class SimpleSelector(object):
def render(self):
# TODO fail if there are no tokens, or if one is a placeholder?
rendered = ''.join(self.tokens)
if self.combinator != ' ':
rendered = ' '.join((self.combinator, rendered))
return rendered
if self.combinator == ' ':
return rendered
elif rendered:
return self.combinator + ' ' + rendered
else:
return self.combinator
class Selector(object):
......@@ -287,6 +298,10 @@ class Selector(object):
def promote_selector():
promote_simple()
if pending['combinator'] != ' ':
pending['simples'].append(
SimpleSelector(pending['combinator'], []))
pending['combinator'] = ' '
if pending['simples']:
ret.append(cls(pending['simples']))
pending['simples'] = []
......@@ -310,7 +325,6 @@ class Selector(object):
if token == ',':
# End current selector
# TODO what about "+ ,"? what do i even do with that
promote_selector()
elif token in ' +>~':
# End current simple selector
......
......@@ -13,7 +13,7 @@ import six
from scss.cssdefs import (
_ml_comment_re, _sl_comment_re,
_collapse_properties_space_re,
_strings_re,
_strings_re, _urls_re,
)
from scss.cssdefs import determine_encoding
......@@ -356,6 +356,10 @@ class SourceFile(object):
lambda m: _reverse_safe_strings_re.sub(
lambda n: _reverse_safe_strings[n.group(0)], m.group(0)),
codestr)
codestr = _urls_re.sub(
lambda m: _reverse_safe_strings_re.sub(
lambda n: _reverse_safe_strings[n.group(0)], m.group(0)),
codestr)
# removes multiple line comments
codestr = _ml_comment_re.sub('', codestr)
......
......@@ -152,6 +152,11 @@ def test_invert():
assert calc('invert(yellow)') == Color.from_rgb(0., 0., 1.)
def test_invert_css_filter():
# invert(number) is a CSS filter and should be left alone
assert calc('invert(50%)') == String("invert(50%)")
# ------------------------------------------------------------------------------
# Opacity functions
......@@ -277,6 +282,9 @@ def test_str_slice():
assert calc('str-slice("abcd", 2)') == calc('"bcd"')
assert calc('str-slice("abcd", -3, -2)') == calc('"bc"')
assert calc('str-slice("abcd", 2, -2)') == calc('"bc"')
assert calc('str-slice("abcd", 0, 3)') == calc('"abc"')
assert calc('str-slice("abcd", 1, 3)') == calc('"abc"')
assert calc('str-slice("abcd", 1, 30)') == calc('"abcd"')
def test_to_upper_case():
......
p {
a: 1;
b: 1 2;
c: 1, 2, 3;
}
// Default when the first list has < 2 elements is space
$a: append((), 1);
$b: append(append((), 1, comma), 2);
$c: append((1, 2), 3);
p {
a: $a;
b: $b;
c: $c;
}
.logo {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC);
}
// Two slashes inside a URL were being stripped as a comment, leaving bogus SCSS
// https://github.com/Kronuz/pyScss/issues/350
.logo {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment