From 3852fc720dd5494efa1176341824e844b531385e Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 5 Dec 2014 15:13:40 +0100 Subject: [PATCH] Remove scss lib --- couchpotato/core/_base/clientscript.py | 96 +- .../style/{main_old.scss => main_old.css} | 0 libs/scss/__init__.py | 1617 ----------------- libs/scss/__main__.py | 3 - libs/scss/_native.py | 262 --- libs/scss/config.py | 36 - libs/scss/cssdefs.py | 384 ---- libs/scss/errors.py | 169 -- libs/scss/expression.py | 905 --------- libs/scss/functions/__init__.py | 25 - libs/scss/functions/compass/__init__.py | 2 - libs/scss/functions/compass/gradients.py | 400 ---- libs/scss/functions/compass/helpers.py | 654 ------- libs/scss/functions/compass/images.py | 285 --- libs/scss/functions/compass/layouts.py | 346 ---- libs/scss/functions/compass/sprites.py | 524 ------ libs/scss/functions/core.py | 784 -------- libs/scss/functions/extra.py | 471 ----- libs/scss/functions/library.py | 81 - libs/scss/rule.py | 524 ------ libs/scss/scss_meta.py | 67 - libs/scss/selector.py | 607 ------- libs/scss/setup.py | 14 - libs/scss/src/_speedups.c | 554 ------ libs/scss/src/block_locator.c | 547 ------ libs/scss/src/block_locator.h | 52 - libs/scss/src/scanner.c | 463 ----- libs/scss/src/scanner.h | 68 - libs/scss/src/utils.h | 98 - libs/scss/tool.py | 403 ---- libs/scss/types.py | 1128 ------------ libs/scss/util.py | 146 -- 32 files changed, 17 insertions(+), 11698 deletions(-) rename couchpotato/static/style/{main_old.scss => main_old.css} (100%) delete mode 100644 libs/scss/__init__.py delete mode 100644 libs/scss/__main__.py delete mode 100644 libs/scss/_native.py delete mode 100644 libs/scss/config.py delete mode 100644 libs/scss/cssdefs.py delete mode 100644 libs/scss/errors.py delete mode 100644 libs/scss/expression.py delete mode 100644 libs/scss/functions/__init__.py delete mode 100644 libs/scss/functions/compass/__init__.py delete mode 100644 libs/scss/functions/compass/gradients.py delete mode 100644 libs/scss/functions/compass/helpers.py delete mode 100644 libs/scss/functions/compass/images.py delete mode 100644 libs/scss/functions/compass/layouts.py delete mode 100644 libs/scss/functions/compass/sprites.py delete mode 100644 libs/scss/functions/core.py delete mode 100644 libs/scss/functions/extra.py delete mode 100644 libs/scss/functions/library.py delete mode 100644 libs/scss/rule.py delete mode 100644 libs/scss/scss_meta.py delete mode 100644 libs/scss/selector.py delete mode 100644 libs/scss/setup.py delete mode 100644 libs/scss/src/_speedups.c delete mode 100644 libs/scss/src/block_locator.c delete mode 100644 libs/scss/src/block_locator.h delete mode 100644 libs/scss/src/scanner.c delete mode 100644 libs/scss/src/scanner.h delete mode 100644 libs/scss/src/utils.h delete mode 100644 libs/scss/tool.py delete mode 100644 libs/scss/types.py delete mode 100644 libs/scss/util.py diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index df637b47..2074ad2c 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -1,14 +1,12 @@ import os import re -import time -from couchpotato.core.event import addEvent, fireEvent +from couchpotato.core.event import addEvent from couchpotato.core.helpers.encoding import ss from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env -from scss import Scss from tornado.web import StaticFileHandler @@ -21,27 +19,24 @@ class ClientScript(Plugin): core_static = { 'style': [ - 'style/main.scss', - 'style/uniform.generic.css', - 'style/uniform.css', - 'style/settings.css', + 'style/combined.min.css', ], 'script': [ - 'scripts/library/mootools.js', - 'scripts/library/mootools_more.js', + 'scripts/vendor/mootools.js', + 'scripts/vendor/mootools_more.js', + 'scripts/vendor/form_replacement/form_check.js', + 'scripts/vendor/form_replacement/form_radio.js', + 'scripts/vendor/form_replacement/form_dropdown.js', + 'scripts/vendor/form_replacement/form_selectoption.js', + 'scripts/vendor/spin.js', + 'scripts/vendor/Array.stableSort.js', + 'scripts/vendor/async.js', + 'scripts/vendor/history.js', 'scripts/library/uniform.js', - 'scripts/library/form_replacement/form_check.js', - 'scripts/library/form_replacement/form_radio.js', - 'scripts/library/form_replacement/form_dropdown.js', - 'scripts/library/form_replacement/form_selectoption.js', 'scripts/library/question.js', 'scripts/library/scrollspy.js', - 'scripts/library/spin.js', - 'scripts/library/Array.stableSort.js', - 'scripts/library/async.js', 'scripts/couchpotato.js', 'scripts/api.js', - 'scripts/library/history.js', 'scripts/page.js', 'scripts/block.js', 'scripts/block/navigation.js', @@ -74,16 +69,10 @@ class ClientScript(Plugin): addEvent('clientscript.get_styles', self.getStyles) addEvent('clientscript.get_scripts', self.getScripts) - addEvent('app.load', self.livereload, priority = 1) addEvent('app.load', self.compile) self.addCore() - def livereload(self): - - if Env.get('dev'): - fireEvent('schedule.interval', 'livereload.watcher', self.watcher, seconds = .5) - def addCore(self): for static_type in self.core_static: @@ -96,39 +85,6 @@ class ClientScript(Plugin): else: self.registerStyle(core_url, file_path, position = 'front') - def watcher(self): - - changed = [] - - for file_path in self.watches: - info = self.watches[file_path] - old_time = info['file_time'] - file_time = os.path.getmtime(file_path) - if file_time > old_time: - changed.append(info['api_path']) - - if info['compiled_path']: - compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)]) - f = open(file_path, 'r').read() - f = compiler.compile(f) - - self.createFile(info['compiled_path'], f.strip()) - - - # Add file to watchlist again, with current filetime - self.watches[file_path]['file_time'] = file_time - - # Notify fronted with changes - if changed: - fireEvent('notify.frontend', type = 'watcher.changed', data = changed) - - def watchFile(self, file_path, api_path, compiled_path = False): - self.watches[file_path] = { - 'file_time': os.path.getmtime(file_path), - 'api_path': api_path, - 'compiled_path': compiled_path, - } - def compile(self): # Create cache dir @@ -160,25 +116,6 @@ class ClientScript(Plugin): f = open(file_path, 'r').read() - # Compile scss - if file_path[-5:] == '.scss': - - # Compile to css - compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)]) - f = compiler.compile(f) - - # Reload watcher - if Env.get('dev'): - - compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0] - compiled_file_path = os.path.join(minified_dir, compiled_file_name) - self.createFile(compiled_file_path, f.strip()) - - # Remove scss path - x = (file_path, 'minified/%s?%s' % (compiled_file_name, tryInt(time.time()))) - - self.watchFile(file_path, x[1], compiled_path = compiled_file_path) - if not Env.get('dev'): if file_type == 'script': @@ -212,8 +149,11 @@ class ClientScript(Plugin): return self.get('script', *args, **kwargs) def get(self, type, location = 'head'): - paths = self.paths[type][location] - return [x[1] for x in paths] + if type in self.paths and location in self.paths[type]: + paths = self.paths[type][location] + return [x[1] for x in paths] + + return [] def registerStyle(self, api_path, file_path, position = 'head'): self.register(api_path, file_path, 'style', position) @@ -223,8 +163,6 @@ class ClientScript(Plugin): def register(self, api_path, file_path, type, location): - self.watchFile(file_path, api_path) - api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) if not self.original_paths[type].get(location): diff --git a/couchpotato/static/style/main_old.scss b/couchpotato/static/style/main_old.css similarity index 100% rename from couchpotato/static/style/main_old.scss rename to couchpotato/static/style/main_old.css diff --git a/libs/scss/__init__.py b/libs/scss/__init__.py deleted file mode 100644 index 00a366fa..00000000 --- a/libs/scss/__init__.py +++ /dev/null @@ -1,1617 +0,0 @@ -#!/usr/bin/env python -#-*- coding: utf-8 -*- -""" -pyScss, a Scss compiler for Python - -@author German M. Bravo (Kronuz) -@version 1.2.0 alpha -@see https://github.com/Kronuz/pyScss -@copyright (c) 2012-2013 German M. Bravo (Kronuz) -@license MIT License - http://www.opensource.org/licenses/mit-license.php - -pyScss compiles Scss, a superset of CSS that is more powerful, elegant and -easier to maintain than plain-vanilla CSS. The library acts as a CSS source code -preprocesor which allows you to use variables, nested rules, mixins, andhave -inheritance of rules, all with a CSS-compatible syntax which the preprocessor -then compiles to standard CSS. - -Scss, as an extension of CSS, helps keep large stylesheets well-organized. It -borrows concepts and functionality from projects such as OOCSS and other similar -frameworks like as Sass. It's build on top of the original PHP xCSS codebase -structure but it's been completely rewritten, many bugs have been fixed and it -has been extensively extended to support almost the full range of Sass' Scss -syntax and functionality. - -Bits of code in pyScss come from various projects: -Compass: - (c) 2009 Christopher M. Eppstein - http://compass-style.org/ -Sass: - (c) 2006-2009 Hampton Catlin and Nathan Weizenbaum - http://sass-lang.com/ -xCSS: - (c) 2010 Anton Pawlik - http://xcss.antpaw.org/docs/ - -""" -from __future__ import absolute_import -from __future__ import print_function - -from scss.scss_meta import BUILD_INFO, PROJECT, VERSION, REVISION, URL, AUTHOR, AUTHOR_EMAIL, LICENSE - -__project__ = PROJECT -__version__ = VERSION -__author__ = AUTHOR + ' <' + AUTHOR_EMAIL + '>' -__license__ = LICENSE - - -from collections import defaultdict -import glob -from itertools import product -import logging -import os.path -import re -import sys - -import six - -from scss import config -from scss.cssdefs import ( - SEPARATOR, - _ml_comment_re, _sl_comment_re, - _escape_chars_re, - _spaces_re, _expand_rules_space_re, _collapse_properties_space_re, - _strings_re, _prop_split_re, -) -from scss.errors import SassError -from scss.expression import Calculator -from scss.functions import ALL_BUILTINS_LIBRARY -from scss.functions.compass.sprites import sprite_map -from scss.rule import Namespace, SassRule, UnparsedBlock -from scss.types import Boolean, List, Null, Number, String, Undefined -from scss.util import dequote, normalize_var, print_timing # profile - -log = logging.getLogger(__name__) - -################################################################################ -# Load C acceleration modules -locate_blocks = None -try: - from scss._speedups import locate_blocks -except ImportError: - import warnings - warnings.warn( - "Scanning acceleration disabled (_speedups not found)!", - RuntimeWarning - ) - from scss._native import locate_blocks - -################################################################################ - - -_safe_strings = { - '^doubleslash^': '//', - '^bigcopen^': '/*', - '^bigcclose^': '*/', - '^doubledot^': ':', - '^semicolon^': ';', - '^curlybracketopen^': '{', - '^curlybracketclosed^': '}', -} -_reverse_safe_strings = dict((v, k) for k, v in _safe_strings.items()) -_safe_strings_re = re.compile('|'.join(map(re.escape, _safe_strings))) -_reverse_safe_strings_re = re.compile('|'.join(map(re.escape, _reverse_safe_strings))) - -_default_scss_vars = { - '$BUILD-INFO': String.unquoted(BUILD_INFO), - '$PROJECT': String.unquoted(PROJECT), - '$VERSION': String.unquoted(VERSION), - '$REVISION': String.unquoted(REVISION), - '$URL': String.unquoted(URL), - '$AUTHOR': String.unquoted(AUTHOR), - '$AUTHOR-EMAIL': String.unquoted(AUTHOR_EMAIL), - '$LICENSE': String.unquoted(LICENSE), - - # unsafe chars will be hidden as vars - '$--doubleslash': String.unquoted('//'), - '$--bigcopen': String.unquoted('/*'), - '$--bigcclose': String.unquoted('*/'), - '$--doubledot': String.unquoted(':'), - '$--semicolon': String.unquoted(';'), - '$--curlybracketopen': String.unquoted('{'), - '$--curlybracketclosed': String.unquoted('}'), - - # shortcuts (it's "a hidden feature" for now) - 'bg:': String.unquoted('background:'), - 'bgc:': String.unquoted('background-color:'), -} - - -################################################################################ - - -class SourceFile(object): - def __init__(self, filename, contents, parent_dir='.', is_string=False, is_sass=None, line_numbers=True, line_strip=True): - self.filename = filename - self.sass = filename.endswith('.sass') if is_sass is None else is_sass - self.line_numbers = line_numbers - self.line_strip = line_strip - self.contents = self.prepare_source(contents) - self.parent_dir = os.path.realpath(parent_dir) - self.is_string = is_string - - def __repr__(self): - return "" % ( - self.filename, - id(self), - ) - - @classmethod - def from_filename(cls, fn, filename=None, is_sass=None, line_numbers=True): - if filename is None: - _, filename = os.path.split(fn) - - with open(fn) as f: - contents = f.read() - - return cls(filename, contents, is_sass=is_sass, line_numbers=line_numbers) - - @classmethod - def from_string(cls, string, filename=None, is_sass=None, line_numbers=True): - if filename is None: - filename = "" % string[:50] - - return cls(filename, string, is_string=True, is_sass=is_sass, line_numbers=line_numbers) - - def parse_scss_line(self, line_no, line, state): - ret = '' - - if line is None: - line = '' - - line = state['line_buffer'] + line.rstrip() # remove EOL character - - if line and line[-1] == '\\': - state['line_buffer'] = line[:-1] - return '' - else: - state['line_buffer'] = '' - - output = state['prev_line'] - if self.line_strip: - output = output.strip() - output_line_no = state['prev_line_no'] - - state['prev_line'] = line - state['prev_line_no'] = line_no - - if output: - if self.line_numbers: - output = str(output_line_no + 1) + SEPARATOR + output - output += '\n' - ret += output - - return ret - - def parse_sass_line(self, line_no, line, state): - ret = '' - - if line is None: - line = '' - - line = state['line_buffer'] + line.rstrip() # remove EOL character - - if line and line[-1] == '\\': - state['line_buffer'] = line[:-1] - return ret - else: - state['line_buffer'] = '' - - indent = len(line) - len(line.lstrip()) - - # make sure we support multi-space indent as long as indent is consistent - if indent and not state['indent_marker']: - state['indent_marker'] = indent - - if state['indent_marker']: - indent /= state['indent_marker'] - - if indent == state['prev_indent']: - # same indentation as previous line - if state['prev_line']: - state['prev_line'] += ';' - elif indent > state['prev_indent']: - # new indentation is greater than previous, we just entered a new block - state['prev_line'] += ' {' - state['nested_blocks'] += 1 - else: - # indentation is reset, we exited a block - block_diff = state['prev_indent'] - indent - if state['prev_line']: - state['prev_line'] += ';' - state['prev_line'] += ' }' * block_diff - state['nested_blocks'] -= block_diff - - output = state['prev_line'] - if self.line_strip: - output = output.strip() - output_line_no = state['prev_line_no'] - - state['prev_indent'] = indent - state['prev_line'] = line - state['prev_line_no'] = line_no - - if output: - if self.line_numbers: - output = str(output_line_no + 1) + SEPARATOR + output - output += '\n' - ret += output - return ret - - def prepare_source(self, codestr, sass=False): - # Decorate lines with their line numbers and a delimiting NUL and remove empty lines - state = { - 'line_buffer': '', - 'prev_line': '', - 'prev_line_no': 0, - 'prev_indent': 0, - 'nested_blocks': 0, - 'indent_marker': 0, - } - if self.sass: - parse_line = self.parse_sass_line - else: - parse_line = self.parse_scss_line - _codestr = codestr - codestr = '' - for line_no, line in enumerate(_codestr.splitlines()): - codestr += parse_line(line_no, line, state) - codestr += parse_line(None, None, state) # parse the last line stored in prev_line buffer - - # protects codestr: "..." strings - codestr = _strings_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) - - # removes inline comments, but not :// (protocol) - codestr = _sl_comment_re.sub('', codestr) - - codestr = _safe_strings_re.sub(lambda m: _safe_strings[m.group(0)], codestr) - - # expand the space in rules - codestr = _expand_rules_space_re.sub(' {', codestr) - - # collapse the space in properties blocks - codestr = _collapse_properties_space_re.sub(r'\1{', codestr) - - return codestr - - -class Scss(object): - def __init__(self, - scss_vars=None, scss_opts=None, scss_files=None, super_selector=None, - live_errors=False, library=ALL_BUILTINS_LIBRARY, func_registry=None, search_paths=None): - - if super_selector: - self.super_selector = super_selector + ' ' - else: - self.super_selector = '' - - self._scss_vars = {} - if scss_vars: - calculator = Calculator() - for var_name, value in scss_vars.items(): - if isinstance(value, six.string_types): - scss_value = calculator.evaluate_expression(value) - if scss_value is None: - # TODO warning? - scss_value = String.unquoted(value) - else: - scss_value = value - self._scss_vars[var_name] = scss_value - - self._scss_opts = scss_opts - self._scss_files = scss_files - # NOTE: func_registry is backwards-compatibility for only one user and - # has never existed in a real release - self._library = func_registry or library - self._search_paths = search_paths - - # If true, swallow compile errors and embed them in the output instead - self.live_errors = live_errors - - self.reset() - - def get_scss_constants(self): - scss_vars = self.root_namespace.variables - return dict((k, v) for k, v in scss_vars.items() if k and (not k.startswith('$') or k.startswith('$') and k[1].isupper())) - - def get_scss_vars(self): - scss_vars = self.root_namespace.variables - return dict((k, v) for k, v in scss_vars.items() if k and not (not k.startswith('$') or k.startswith('$') and k[1].isupper())) - - def reset(self, input_scss=None): - # Initialize - self.scss_vars = _default_scss_vars.copy() - if self._scss_vars is not None: - self.scss_vars.update(self._scss_vars) - - self.scss_opts = self._scss_opts.copy() if self._scss_opts else {} - - self.root_namespace = Namespace(variables=self.scss_vars, functions=self._library) - - # Figure out search paths. Fall back from provided explicitly to - # defined globally to just searching the current directory - self.search_paths = ['.'] - if self._search_paths is not None: - assert not isinstance(self._search_paths, six.string_types), \ - "`search_paths` should be an iterable, not a string" - self.search_paths.extend(self._search_paths) - else: - if config.LOAD_PATHS: - if isinstance(config.LOAD_PATHS, six.string_types): - # Back-compat: allow comma-delimited - self.search_paths.extend(config.LOAD_PATHS.split(',')) - else: - self.search_paths.extend(config.LOAD_PATHS) - - self.search_paths.extend(self.scss_opts.get('load_paths', [])) - - self.source_files = [] - self.source_file_index = {} - if self._scss_files is not None: - for name, contents in list(self._scss_files.items()): - if name in self.source_file_index: - raise KeyError("Duplicate filename %r" % name) - source_file = SourceFile(name, contents) - self.source_files.append(source_file) - self.source_file_index[name] = source_file - - self.rules = [] - - #@profile - #@print_timing(2) - def Compilation(self, scss_string=None, scss_file=None, super_selector=None, filename=None, is_sass=None, line_numbers=True): - if super_selector: - self.super_selector = super_selector + ' ' - self.reset() - - source_file = None - if scss_string is not None: - source_file = SourceFile.from_string(scss_string, filename, is_sass, line_numbers) - elif scss_file is not None: - source_file = SourceFile.from_filename(scss_file, filename, is_sass, line_numbers) - - if source_file is not None: - # Clear the existing list of files - self.source_files = [] - self.source_file_index = dict() - - self.source_files.append(source_file) - self.source_file_index[source_file.filename] = source_file - - # this will compile and manage rule: child objects inside of a node - self.parse_children() - - # this will manage @extends - self.apply_extends() - - 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 - 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.scss_opts.get('debug_info', False): - if source_file.is_string: - final_cont += "/* %s %s generated add up to a total of %s %s accumulated%s */\n" % ( - total_selectors, - 'selector' if total_selectors == 1 else 'selectors', - all_selectors, - 'selector' if all_selectors == 1 else 'selectors', - exceeded) - else: - 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.filename, - all_selectors, - 'selector' if all_selectors == 1 else 'selectors', - exceeded) - final_cont += fcont - - return final_cont - - def compile(self, *args, **kwargs): - try: - return self.Compilation(*args, **kwargs) - except SassError as e: - if self.live_errors: - # TODO should this setting also capture and display warnings? - return e.to_css() - else: - raise - - def parse_selectors(self, raw_selectors): - """ - Parses out the old xCSS "foo extends bar" syntax. - - Returns a 2-tuple: a set of selectors, and a set of extended selectors. - """ - # Fix tabs and spaces in selectors - raw_selectors = _spaces_re.sub(' ', raw_selectors) - - import re - - from scss.selector import Selector - - parts = re.split(r'\s+extends\s+', raw_selectors, 1) - if len(parts) > 1: - unparsed_selectors, unsplit_parents = parts - # Multiple `extends` are delimited by `&` - unparsed_parents = unsplit_parents.split('&') - else: - unparsed_selectors, = parts - unparsed_parents = () - - selectors = Selector.parse_many(unparsed_selectors) - parents = [Selector.parse_one(parent) for parent in unparsed_parents] - - return selectors, parents - - @print_timing(3) - def parse_children(self, scope=None): - children = [] - root_namespace = self.root_namespace - for source_file in self.source_files: - rule = SassRule( - source_file=source_file, - - unparsed_contents=source_file.contents, - namespace=root_namespace, - options=self.scss_opts, - ) - self.rules.append(rule) - children.append(rule) - - for rule in children: - self.manage_children(rule, scope) - - if self.scss_opts.get('warn_unused'): - for name, file_and_line in root_namespace.unused_imports(): - log.warn("Unused @import: '%s' (%s)", name, file_and_line) - - @print_timing(4) - def manage_children(self, rule, scope): - try: - self._manage_children_impl(rule, scope) - except SassReturn: - raise - except SassError as e: - e.add_rule(rule) - raise - except Exception as e: - raise SassError(e, rule=rule) - - def _manage_children_impl(self, rule, scope): - calculator = Calculator(rule.namespace) - - for c_lineno, c_property, c_codestr in locate_blocks(rule.unparsed_contents): - block = UnparsedBlock(rule, c_lineno, c_property, c_codestr) - - if block.is_atrule: - code = block.directive - code = code.lower() - if code == '@warn': - value = calculator.calculate(block.argument) - log.warn(repr(value)) - elif code == '@print': - value = calculator.calculate(block.argument) - sys.stderr.write("%s\n" % value) - elif code == '@raw': - value = calculator.calculate(block.argument) - sys.stderr.write("%s\n" % repr(value)) - elif code == '@dump_context': - sys.stderr.write("%s\n" % repr(rule.namespace._variables)) - elif code == '@dump_functions': - sys.stderr.write("%s\n" % repr(rule.namespace._functions)) - elif code == '@dump_mixins': - sys.stderr.write("%s\n" % repr(rule.namespace._mixins)) - elif code == '@dump_imports': - sys.stderr.write("%s\n" % repr(rule.namespace._imports)) - elif code == '@dump_options': - sys.stderr.write("%s\n" % repr(rule.options)) - elif code == '@debug': - setting = block.argument.strip() - if setting.lower() in ('1', 'true', 't', 'yes', 'y', 'on'): - setting = True - elif setting.lower() in ('0', 'false', 'f', 'no', 'n', 'off', 'undefined'): - setting = False - config.DEBUG = setting - log.info("Debug mode is %s", 'On' if config.DEBUG else 'Off') - elif code == '@option': - self._settle_options(rule, scope, block) - elif code == '@content': - self._do_content(rule, scope, block) - elif code == '@import': - self._do_import(rule, scope, block) - elif code == '@extend': - from scss.selector import Selector - selectors = calculator.apply_vars(block.argument) - # XXX this no longer handles `&`, which is from xcss - rule.extends_selectors.extend(Selector.parse_many(selectors)) - #rule.extends_selectors.update(p.strip() for p in selectors.replace(',', '&').split('&')) - #rule.extends_selectors.discard('') - elif code == '@return': - # TODO should assert this only happens within a @function - ret = calculator.calculate(block.argument) - raise SassReturn(ret) - elif code == '@include': - self._do_include(rule, scope, block) - elif code in ('@mixin', '@function'): - self._do_functions(rule, scope, block) - elif code in ('@if', '@else if'): - self._do_if(rule, scope, block) - elif code == '@else': - self._do_else(rule, scope, block) - elif code == '@for': - self._do_for(rule, scope, block) - elif code == '@each': - self._do_each(rule, scope, block) - elif code == '@while': - self._do_while(rule, scope, block) - elif code in ('@variables', '@vars'): - self._get_variables(rule, scope, block) - elif block.unparsed_contents is None: - rule.properties.append((block.prop, None)) - elif scope is None: # needs to have no scope to crawl down the nested rules - self._nest_at_rules(rule, scope, block) - #################################################################### - # Properties - elif block.unparsed_contents is None: - self._get_properties(rule, scope, block) - # Nested properties - elif block.is_scope: - if block.header.unscoped_value: - # Possibly deal with default unscoped value - self._get_properties(rule, scope, block) - - rule.unparsed_contents = block.unparsed_contents - subscope = (scope or '') + block.header.scope + '-' - self.manage_children(rule, subscope) - #################################################################### - # Nested rules - elif scope is None: # needs to have no scope to crawl down the nested rules - self._nest_rules(rule, scope, block) - - @print_timing(10) - def _settle_options(self, rule, scope, block): - for option in block.argument.split(','): - option, value = (option.split(':', 1) + [''])[:2] - option = option.strip().lower() - value = value.strip() - if option: - if value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'): - value = True - elif value.lower() in ('0', 'false', 'f', 'no', 'n', 'off', 'undefined'): - value = False - option = option.replace('-', '_') - if option == 'compress': - option = 'style' - log.warn("The option 'compress' is deprecated. Please use 'style' instead.") - rule.options[option] = value - - def _get_funct_def(self, rule, calculator, argument): - funct, lpar, argstr = argument.partition('(') - funct = calculator.do_glob_math(funct) - funct = normalize_var(funct.strip()) - argstr = argstr.strip() - - # Parse arguments with the argspec rule - if lpar: - if not argstr.endswith(')'): - raise SyntaxError("Expected ')', found end of line for %s (%s)" % (funct, rule.file_and_line)) - argstr = argstr[:-1].strip() - else: - # Whoops, no parens at all. That's like calling with no arguments. - argstr = '' - - argstr = calculator.do_glob_math(argstr) - argspec_node = calculator.parse_expression(argstr, target='goal_argspec') - return funct, argspec_node - - def _populate_namespace_from_call(self, name, callee_namespace, mixin, args, kwargs): - # Mutation protection - args = list(args) - kwargs = dict(kwargs) - - #m_params = mixin[0] - #m_defaults = mixin[1] - #m_codestr = mixin[2] - pristine_callee_namespace = mixin[3] - callee_argspec = mixin[4] - import_key = mixin[5] - - callee_calculator = Calculator(callee_namespace) - - # Populate the mixin/function's namespace with its arguments - for var_name, node in callee_argspec.iter_def_argspec(): - if args: - # If there are positional arguments left, use the first - value = args.pop(0) - elif var_name in kwargs: - # Try keyword arguments - value = kwargs.pop(var_name) - elif node is not None: - # OK, there's a default argument; try that - # DEVIATION: this allows argument defaults to refer to earlier - # argument values - value = node.evaluate(callee_calculator, divide=True) - else: - # TODO this should raise - value = Undefined() - - callee_namespace.set_variable(var_name, value, local_only=True) - - if callee_argspec.slurp: - # Slurpy var gets whatever is left - callee_namespace.set_variable( - callee_argspec.slurp.name, - List(args, use_comma=True)) - args = [] - elif callee_argspec.inject: - # Callee namespace gets all the extra kwargs whether declared or - # not - for var_name, value in kwargs.items(): - callee_namespace.set_variable(var_name, value, local_only=True) - kwargs = {} - - # TODO would be nice to say where the mixin/function came from - if kwargs: - raise NameError("%s has no such argument %s" % (name, kwargs.keys()[0])) - - if args: - raise NameError("%s received extra arguments: %r" % (name, args)) - - pristine_callee_namespace.use_import(import_key) - return callee_namespace - - @print_timing(10) - def _do_functions(self, rule, scope, block): - """ - Implements @mixin and @function - """ - if not block.argument: - raise SyntaxError("%s requires a function name (%s)" % (block.directive, rule.file_and_line)) - - calculator = Calculator(rule.namespace) - funct, argspec_node = self._get_funct_def(rule, calculator, block.argument) - - defaults = {} - new_params = [] - - for var_name, default in argspec_node.iter_def_argspec(): - new_params.append(var_name) - if default is not None: - defaults[var_name] = default - - mixin = [rule.source_file, block.lineno, block.unparsed_contents, rule.namespace, argspec_node, rule.import_key] - if block.directive == '@function': - def _call(mixin): - def __call(namespace, *args, **kwargs): - source_file = mixin[0] - lineno = mixin[1] - m_codestr = mixin[2] - pristine_callee_namespace = mixin[3] - callee_namespace = pristine_callee_namespace.derive() - - # TODO CallOp converts Sass names to Python names, so we - # have to convert them back to Sass names. would be nice - # to avoid this back-and-forth somehow - kwargs = dict( - (normalize_var('$' + key), value) - for (key, value) in kwargs.items()) - - self._populate_namespace_from_call( - "Function {0}".format(funct), - callee_namespace, mixin, args, kwargs) - - _rule = SassRule( - source_file=source_file, - lineno=lineno, - unparsed_contents=m_codestr, - namespace=callee_namespace, - - # rule - import_key=rule.import_key, - options=rule.options, - properties=rule.properties, - extends_selectors=rule.extends_selectors, - ancestry=rule.ancestry, - nested=rule.nested, - ) - try: - self.manage_children(_rule, scope) - except SassReturn as e: - return e.retval - else: - return Null() - return __call - _mixin = _call(mixin) - _mixin.mixin = mixin - mixin = _mixin - - if block.directive == '@mixin': - add = rule.namespace.set_mixin - elif block.directive == '@function': - add = rule.namespace.set_function - - # Register the mixin for every possible arity it takes - if argspec_node.slurp or argspec_node.inject: - add(funct, None, mixin) - else: - while len(new_params): - add(funct, len(new_params), mixin) - param = new_params.pop() - if param not in defaults: - break - if not new_params: - add(funct, 0, mixin) - - @print_timing(10) - def _do_include(self, rule, scope, block): - """ - Implements @include, for @mixins - """ - caller_namespace = rule.namespace - caller_calculator = Calculator(caller_namespace) - funct, caller_argspec = self._get_funct_def(rule, caller_calculator, block.argument) - - # Render the passed arguments, using the caller's namespace - args, kwargs = caller_argspec.evaluate_call_args(caller_calculator) - - argc = len(args) + len(kwargs) - try: - mixin = caller_namespace.mixin(funct, argc) - except KeyError: - try: - # TODO maybe? don't do this, once '...' works - # Fallback to single parameter: - mixin = caller_namespace.mixin(funct, 1) - except KeyError: - log.error("Mixin not found: %s:%d (%s)", funct, argc, rule.file_and_line, extra={'stack': True}) - return - else: - args = [List(args, use_comma=True)] - # TODO what happens to kwargs? - - source_file = mixin[0] - lineno = mixin[1] - m_codestr = mixin[2] - pristine_callee_namespace = mixin[3] - callee_argspec = mixin[4] - if caller_argspec.inject and callee_argspec.inject: - # DEVIATION: Pass the ENTIRE local namespace to the mixin (yikes) - callee_namespace = Namespace.derive_from( - caller_namespace, - pristine_callee_namespace) - else: - callee_namespace = pristine_callee_namespace.derive() - - self._populate_namespace_from_call( - "Mixin {0}".format(funct), - callee_namespace, mixin, args, kwargs) - - _rule = SassRule( - source_file=source_file, - lineno=lineno, - unparsed_contents=m_codestr, - namespace=callee_namespace, - - # rule - import_key=rule.import_key, - options=rule.options, - properties=rule.properties, - extends_selectors=rule.extends_selectors, - ancestry=rule.ancestry, - nested=rule.nested, - ) - - _rule.options['@content'] = block.unparsed_contents - self.manage_children(_rule, scope) - - @print_timing(10) - def _do_content(self, rule, scope, block): - """ - Implements @content - """ - if '@content' not in rule.options: - log.error("Content string not found for @content (%s)", rule.file_and_line) - rule.unparsed_contents = rule.options.pop('@content', '') - self.manage_children(rule, scope) - - @print_timing(10) - def _do_import(self, rule, scope, block): - """ - Implements @import - Load and import mixins and functions and rules - """ - # Protect against going to prohibited places... - if any(scary_token in block.argument for scary_token in ('..', '://', 'url(')): - rule.properties.append((block.prop, None)) - return - - full_filename = None - names = block.argument.split(',') - for name in names: - name = dequote(name.strip()) - - source_file = None - full_filename, seen_paths = self._find_import(rule, name) - - if full_filename is None: - i_codestr = self._do_magic_import(rule, scope, block) - - if i_codestr is not None: - source_file = SourceFile.from_string(i_codestr) - - elif full_filename in self.source_file_index: - source_file = self.source_file_index[full_filename] - - else: - with open(full_filename) as f: - source = f.read() - source_file = SourceFile( - full_filename, - source, - parent_dir=os.path.realpath(os.path.dirname(full_filename)), - ) - - self.source_files.append(source_file) - self.source_file_index[full_filename] = source_file - - if source_file is None: - load_paths_msg = "\nLoad paths:\n\t%s" % "\n\t".join(seen_paths) - log.warn("File to import not found or unreadable: '%s' (%s)%s", name, rule.file_and_line, load_paths_msg) - continue - - import_key = (name, source_file.parent_dir) - if rule.namespace.has_import(import_key): - # If already imported in this scope, skip - continue - - _rule = SassRule( - source_file=source_file, - lineno=block.lineno, - import_key=import_key, - unparsed_contents=source_file.contents, - - # rule - options=rule.options, - properties=rule.properties, - extends_selectors=rule.extends_selectors, - ancestry=rule.ancestry, - namespace=rule.namespace, - ) - rule.namespace.add_import(import_key, rule.import_key, rule.file_and_line) - self.manage_children(_rule, scope) - - def _find_import(self, rule, name): - """Find the file referred to by an @import. - - Takes a name from an @import and returns an absolute path, or None. - """ - name, ext = os.path.splitext(name) - if ext: - search_exts = [ext] - else: - search_exts = ['.scss', '.sass'] - - dirname, name = os.path.split(name) - - seen_paths = [] - - for path in self.search_paths: - for basepath in [rule.source_file.parent_dir, '.']: - full_path = os.path.realpath(os.path.join(basepath, path, dirname)) - - if full_path in seen_paths: - continue - seen_paths.append(full_path) - - for prefix, suffix in product(('_', ''), search_exts): - full_filename = os.path.join(full_path, prefix + name + suffix) - if os.path.exists(full_filename): - return full_filename, seen_paths - - return None, seen_paths - - @print_timing(10) - def _do_magic_import(self, rule, scope, block): - """ - Implements @import for sprite-maps - Imports magic sprite map directories - """ - if callable(config.STATIC_ROOT): - files = sorted(config.STATIC_ROOT(block.argument)) - else: - glob_path = os.path.join(config.STATIC_ROOT, block.argument) - files = glob.glob(glob_path) - files = sorted((file[len(config.STATIC_ROOT):], None) for file in files) - - if not files: - return - - # Build magic context - map_name = os.path.normpath(os.path.dirname(block.argument)).replace('\\', '_').replace('/', '_') - kwargs = {} - - calculator = Calculator(rule.namespace) - - def setdefault(var, val): - _var = '$' + map_name + '-' + var - if _var in rule.context: - kwargs[var] = calculator.interpolate(rule.context[_var], rule, self._library) - else: - rule.context[_var] = val - kwargs[var] = calculator.interpolate(val, rule, self._library) - return rule.context[_var] - - setdefault('sprite-base-class', String('.' + map_name + '-sprite', quotes=None)) - setdefault('sprite-dimensions', Boolean(False)) - position = setdefault('position', Number(0, '%')) - spacing = setdefault('spacing', Number(0)) - repeat = setdefault('repeat', String('no-repeat', quotes=None)) - names = tuple(os.path.splitext(os.path.basename(file))[0] for file, storage in files) - for n in names: - setdefault(n + '-position', position) - setdefault(n + '-spacing', spacing) - setdefault(n + '-repeat', repeat) - rule.context['$' + map_name + '-' + 'sprites'] = sprite_map(block.argument, **kwargs) - ret = ''' - @import "compass/utilities/sprites/base"; - - // All sprites should extend this class - // The %(map_name)s-sprite mixin will do so for you. - #{$%(map_name)s-sprite-base-class} { - background: $%(map_name)s-sprites; - } - - // Use this to set the dimensions of an element - // based on the size of the original image. - @mixin %(map_name)s-sprite-dimensions($name) { - @include sprite-dimensions($%(map_name)s-sprites, $name); - } - - // Move the background position to display the sprite. - @mixin %(map_name)s-sprite-position($name, $offset-x: 0, $offset-y: 0) { - @include sprite-position($%(map_name)s-sprites, $name, $offset-x, $offset-y); - } - - // Extends the sprite base class and set the background position for the desired sprite. - // It will also apply the image dimensions if $dimensions is true. - @mixin %(map_name)s-sprite($name, $dimensions: $%(map_name)s-sprite-dimensions, $offset-x: 0, $offset-y: 0) { - @extend #{$%(map_name)s-sprite-base-class}; - @include sprite($%(map_name)s-sprites, $name, $dimensions, $offset-x, $offset-y); - } - - @mixin %(map_name)s-sprites($sprite-names, $dimensions: $%(map_name)s-sprite-dimensions) { - @include sprites($%(map_name)s-sprites, $sprite-names, $%(map_name)s-sprite-base-class, $dimensions); - } - - // Generates a class for each sprited image. - @mixin all-%(map_name)s-sprites($dimensions: $%(map_name)s-sprite-dimensions) { - @include %(map_name)s-sprites(%(sprites)s, $dimensions); - } - ''' % {'map_name': map_name, 'sprites': ' '.join(names)} - return ret - - @print_timing(10) - def _do_if(self, rule, scope, block): - """ - Implements @if and @else if - """ - # "@if" indicates whether any kind of `if` since the last `@else` has - # succeeded, in which case `@else if` should be skipped - if block.directive != '@if': - if '@if' not in rule.options: - raise SyntaxError("@else with no @if (%s)" % (rule.file_and_line,)) - if rule.options['@if']: - # Last @if succeeded; stop here - return - - calculator = Calculator(rule.namespace) - condition = calculator.calculate(block.argument) - if condition: - inner_rule = rule.copy() - inner_rule.unparsed_contents = block.unparsed_contents - if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation - # DEVIATION: Allow not creating a new namespace - inner_rule.namespace = rule.namespace - self.manage_children(inner_rule, scope) - rule.options['@if'] = condition - - @print_timing(10) - def _do_else(self, rule, scope, block): - """ - Implements @else - """ - if '@if' not in rule.options: - log.error("@else with no @if (%s)", rule.file_and_line) - val = rule.options.pop('@if', True) - if not val: - inner_rule = rule.copy() - inner_rule.unparsed_contents = block.unparsed_contents - inner_rule.namespace = rule.namespace # DEVIATION: Commenting this line gives the Sass bahavior - inner_rule.unparsed_contents = block.unparsed_contents - self.manage_children(inner_rule, scope) - - @print_timing(10) - def _do_for(self, rule, scope, block): - """ - Implements @for - """ - var, _, name = block.argument.partition(' from ') - frm, _, through = name.partition(' through ') - if not through: - frm, _, through = frm.partition(' to ') - calculator = Calculator(rule.namespace) - frm = calculator.calculate(frm) - through = calculator.calculate(through) - try: - frm = int(float(frm)) - through = int(float(through)) - except ValueError: - return - - if frm > through: - # DEVIATION: allow reversed '@for .. from .. through' (same as enumerate() and range()) - frm, through = through, frm - rev = reversed - else: - rev = lambda x: x - var = var.strip() - var = calculator.do_glob_math(var) - var = normalize_var(var) - - inner_rule = rule.copy() - inner_rule.unparsed_contents = block.unparsed_contents - if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation - # DEVIATION: Allow not creating a new namespace - inner_rule.namespace = rule.namespace - - for i in rev(range(frm, through + 1)): - inner_rule.namespace.set_variable(var, Number(i)) - self.manage_children(inner_rule, scope) - - @print_timing(10) - def _do_each(self, rule, scope, block): - """ - Implements @each - """ - varstring, _, valuestring = block.argument.partition(' in ') - calculator = Calculator(rule.namespace) - values = calculator.calculate(valuestring) - if not values: - return - - varlist = varstring.split(",") - varlist = [ - normalize_var(calculator.do_glob_math(var.strip())) - for var in varlist - ] - - inner_rule = rule.copy() - inner_rule.unparsed_contents = block.unparsed_contents - if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation - # DEVIATION: Allow not creating a new namespace - inner_rule.namespace = rule.namespace - - for v in List.from_maybe(values): - v = List.from_maybe(v) - for i, var in enumerate(varlist): - if i >= len(v): - value = Null() - else: - value = v[i] - inner_rule.namespace.set_variable(var, value) - self.manage_children(inner_rule, scope) - - @print_timing(10) - def _do_while(self, rule, scope, block): - """ - Implements @while - """ - calculator = Calculator(rule.namespace) - first_condition = condition = calculator.calculate(block.argument) - while condition: - inner_rule = rule.copy() - inner_rule.unparsed_contents = block.unparsed_contents - if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation - # DEVIATION: Allow not creating a new namespace - inner_rule.namespace = rule.namespace - self.manage_children(inner_rule, scope) - condition = calculator.calculate(block.argument) - rule.options['@if'] = first_condition - - @print_timing(10) - def _get_variables(self, rule, scope, block): - """ - Implements @variables and @vars - """ - _rule = rule.copy() - _rule.unparsed_contents = block.unparsed_contents - _rule.namespace = rule.namespace - _rule.properties = {} - self.manage_children(_rule, scope) - for name, value in _rule.properties.items(): - rule.namespace.set_variable(name, value) - - @print_timing(10) - def _get_properties(self, rule, scope, block): - """ - Implements properties and variables extraction and assignment - """ - prop, raw_value = (_prop_split_re.split(block.prop, 1) + [None])[:2] - try: - is_var = (block.prop[len(prop)] == '=') - except IndexError: - is_var = False - calculator = Calculator(rule.namespace) - prop = prop.strip() - prop = calculator.do_glob_math(prop) - if not prop: - return - - # Parse the value and determine whether it's a default assignment - is_default = False - if raw_value is not None: - raw_value = raw_value.strip() - if prop.startswith('$'): - raw_value, subs = re.subn(r'(?i)\s+!default\Z', '', raw_value) - if subs: - is_default = True - - _prop = (scope or '') + prop - if is_var or prop.startswith('$') and raw_value is not None: - # Variable assignment - _prop = normalize_var(_prop) - try: - existing_value = rule.namespace.variable(_prop) - except KeyError: - existing_value = None - - is_defined = existing_value is not None and not existing_value.is_null - if is_default and is_defined: - pass - else: - if is_defined and prop.startswith('$') and prop[1].isupper(): - log.warn("Constant %r redefined", prop) - - # Variable assignment is an expression, so it always performs - # real division - value = calculator.calculate(raw_value, divide=True) - rule.namespace.set_variable(_prop, value) - else: - # Regular property destined for output - _prop = calculator.apply_vars(_prop) - if raw_value is None: - value = None - else: - value = calculator.calculate(raw_value) - - if value is None: - pass - elif isinstance(value, six.string_types): - # TODO kill this branch - pass - else: - style = self.scss_opts.get('style', config.STYLE) - compress = style in (True, 'compressed') - value = value.render(compress=compress) - - rule.properties.append((_prop, value)) - - @print_timing(10) - def _nest_at_rules(self, rule, scope, block): - """ - Implements @-blocks - """ - # Interpolate the current block - # TODO this seems like it should be done in the block header. and more - # generally? - calculator = Calculator(rule.namespace) - block.header.argument = calculator.apply_vars(block.header.argument) - - # TODO merge into RuleAncestry - new_ancestry = list(rule.ancestry.headers) - if block.directive == '@media' and new_ancestry: - for i, header in reversed(list(enumerate(new_ancestry))): - if header.is_selector: - continue - elif header.directive == '@media': - from scss.rule import BlockAtRuleHeader - new_ancestry[i] = BlockAtRuleHeader( - '@media', - "%s and %s" % (header.argument, block.argument)) - break - else: - new_ancestry.insert(i, block.header) - else: - new_ancestry.insert(0, block.header) - else: - new_ancestry.append(block.header) - - from scss.rule import RuleAncestry - rule.descendants += 1 - new_rule = SassRule( - source_file=rule.source_file, - import_key=rule.import_key, - lineno=block.lineno, - unparsed_contents=block.unparsed_contents, - - options=rule.options.copy(), - #properties - #extends_selectors - ancestry=RuleAncestry(new_ancestry), - - namespace=rule.namespace.derive(), - nested=rule.nested + 1, - ) - self.rules.append(new_rule) - rule.namespace.use_import(rule.import_key) - self.manage_children(new_rule, scope) - - if new_rule.options.get('warn_unused'): - for name, file_and_line in new_rule.namespace.unused_imports(): - log.warn("Unused @import: '%s' (%s)", name, file_and_line) - - @print_timing(10) - def _nest_rules(self, rule, scope, block): - """ - Implements Nested CSS rules - """ - calculator = Calculator(rule.namespace) - raw_selectors = calculator.do_glob_math(block.prop) - # DEVIATION: ruby sass doesn't support bare variables in selectors - raw_selectors = calculator.apply_vars(raw_selectors) - c_selectors, c_parents = self.parse_selectors(raw_selectors) - - new_ancestry = rule.ancestry.with_nested_selectors(c_selectors) - - rule.descendants += 1 - new_rule = SassRule( - source_file=rule.source_file, - import_key=rule.import_key, - lineno=block.lineno, - unparsed_contents=block.unparsed_contents, - - options=rule.options.copy(), - #properties - extends_selectors=c_parents, - ancestry=new_ancestry, - - namespace=rule.namespace.derive(), - nested=rule.nested + 1, - ) - self.rules.append(new_rule) - rule.namespace.use_import(rule.import_key) - self.manage_children(new_rule, scope) - - if new_rule.options.get('warn_unused'): - for name, file_and_line in new_rule.namespace.unused_imports(): - log.warn("Unused @import: '%s' (%s)", name, file_and_line) - - @print_timing(3) - def apply_extends(self): - """Run through the given rules and translate all the pending @extends - declarations into real selectors on parent rules. - - The list is modified in-place and also sorted in dependency order. - """ - # Game plan: for each rule that has an @extend, add its selectors to - # every rule that matches that @extend. - # First, rig a way to find arbitrary selectors quickly. Most selectors - # revolve around elements, classes, and IDs, so parse those out and use - # them as a rough key. Ignore order and duplication for now. - key_to_selectors = defaultdict(set) - selector_to_rules = defaultdict(list) - # DEVIATION: These are used to rearrange rules in dependency order, so - # an @extended parent appears in the output before a child. Sass does - # not do this, and the results may be unexpected. Pending removal. - rule_order = dict() - rule_dependencies = dict() - order = 0 - for rule in self.rules: - rule_order[rule] = order - # Rules are ultimately sorted by the earliest rule they must - # *precede*, so every rule should "depend" on the next one - rule_dependencies[rule] = [order + 1] - order += 1 - - for selector in rule.selectors: - for key in selector.lookup_key(): - key_to_selectors[key].add(selector) - selector_to_rules[selector].append(rule) - - # Now go through all the rules with an @extends and find their parent - # rules. - for rule in self.rules: - for selector in rule.extends_selectors: - # This is a little dirty. intersection isn't a class method. - # Don't think about it too much. - candidates = set.intersection(*( - key_to_selectors[key] for key in selector.lookup_key())) - extendable_selectors = [ - candidate for candidate in candidates - if candidate.is_superset_of(selector)] - - if not extendable_selectors: - log.warn( - "Can't find any matching rules to extend: %s" - % selector.render()) - continue - - # Armed with a set of selectors that this rule can extend, do - # some substitution and modify the appropriate parent rules - for extendable_selector in extendable_selectors: - # list() shields us from problems mutating the list within - # this loop, which can happen in the case of @extend loops - parent_rules = list(selector_to_rules[extendable_selector]) - for parent_rule in parent_rules: - if parent_rule is rule: - # Don't extend oneself - continue - - more_parent_selectors = [] - - for rule_selector in rule.selectors: - more_parent_selectors.extend( - extendable_selector.substitute( - selector, rule_selector)) - - for parent in more_parent_selectors: - # Update indices, in case any later rules try to - # extend this one - for key in parent.lookup_key(): - key_to_selectors[key].add(parent) - # TODO this could lead to duplicates? maybe should - # be a set too - selector_to_rules[parent].append(parent_rule) - - parent_rule.ancestry = ( - parent_rule.ancestry.with_more_selectors( - more_parent_selectors)) - rule_dependencies[parent_rule].append(rule_order[rule]) - - self.rules.sort(key=lambda rule: min(rule_dependencies[rule])) - - @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): - """ - Generate the final CSS string - """ - style = self.scss_opts.get('style', config.STYLE) - debug_info = self.scss_opts.get('debug_info', False) - - if style == 'legacy' or style is False: - sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', ' ', False, '', '\n', '\n', '\n', debug_info - elif style == 'compressed' or style is True: - sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = False, '', '', False, '', '', '', '', False - elif style == 'compact': - sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', '', False, '\n', ' ', '\n', ' ', debug_info - elif style == 'expanded': - sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', ' ', False, '\n', '\n', '\n', '\n', debug_info - else: # if style == 'nested': - sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', ' ', True, '\n', '\n', '\n', ' ', debug_info - - return self._create_css(rules, sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg) - - def _textwrap(self, txt, width=70): - if not hasattr(self, '_textwrap_wordsep_re'): - self._textwrap_wordsep_re = re.compile(r'(?<=,)\s+') - self._textwrap_strings_re = re.compile(r'''(["'])(?:(?!\1)[^\\]|\\.)*\1''') - - # First, remove commas from anything within strings (marking commas as \0): - def _repl(m): - ori = m.group(0) - fin = ori.replace(',', '\0') - if ori != fin: - subs[fin] = ori - return fin - subs = {} - txt = self._textwrap_strings_re.sub(_repl, txt) - - # Mark split points for word separators using (marking spaces with \1): - txt = self._textwrap_wordsep_re.sub('\1', txt) - - # Replace all the strings back: - for fin, ori in subs.items(): - txt = txt.replace(fin, ori) - - # Split in chunks: - chunks = txt.split('\1') - - # Break in lines of at most long_width width appending chunks: - ln = '' - lines = [] - long_width = int(width * 1.2) - for chunk in chunks: - _ln = ln + ' ' if ln else '' - _ln += chunk - if len(ln) >= width or len(_ln) >= long_width: - if ln: - lines.append(ln) - _ln = chunk - ln = _ln - if ln: - lines.append(ln) - - return lines - - def _create_css(self, rules, sc=True, sp=' ', tb=' ', nst=True, srnl='\n', nl='\n', rnl='\n', lnl='', debug_info=False): - skip_selectors = False - - prev_ancestry_headers = [] - - total_rules = 0 - total_selectors = 0 - - result = '' - dangling_property = False - separate = False - nesting = current_nesting = last_nesting = -1 if nst else 0 - nesting_stack = [] - for rule in rules: - nested = rule.nested - if nested <= 1: - separate = True - - if nst: - last_nesting = current_nesting - current_nesting = nested - - delta_nesting = current_nesting - last_nesting - if delta_nesting > 0: - nesting_stack += [nesting] * delta_nesting - elif delta_nesting < 0: - nesting_stack = nesting_stack[:delta_nesting] - nesting = nesting_stack[-1] - - if rule.is_empty: - continue - - if nst: - nesting += 1 - - ancestry = rule.ancestry - ancestry_len = len(ancestry) - - first_mismatch = 0 - for i, (old_header, new_header) in enumerate(zip(prev_ancestry_headers, ancestry.headers)): - if old_header != new_header: - first_mismatch = i - break - - # When sc is False, sets of properties are printed without a - # trailing semicolon. If the previous block isn't being closed, - # that trailing semicolon needs adding in to separate the last - # property from the next rule. - if not sc and dangling_property and first_mismatch >= len(prev_ancestry_headers): - result += ';' - - # Close blocks and outdent as necessary - for i in range(len(prev_ancestry_headers), first_mismatch, -1): - result += tb * (i - 1) + '}' + rnl - - # Open new blocks as necessary - for i in range(first_mismatch, ancestry_len): - header = ancestry.headers[i] - - if separate: - if result: - result += srnl - separate = False - if debug_info: - if not rule.source_file.is_string: - filename = rule.source_file.filename - lineno = str(rule.lineno) - if debug_info == 'comments': - result += tb * (i + nesting) + "/* file: %s, line: %s */" % (filename, lineno) + nl - else: - filename = _escape_chars_re.sub(r'\\\1', filename) - result += tb * (i + nesting) + "@media -sass-debug-info{filename{font-family:file\:\/\/%s}line{font-family:\\00003%s}}" % (filename, lineno) + nl - - if header.is_selector: - header_string = header.render(sep=',' + sp, super_selector=self.super_selector) - if nl: - header_string = (nl + tb * (i + nesting)).join(self._textwrap(header_string)) - else: - header_string = header.render() - result += tb * (i + nesting) + header_string + sp + '{' + nl - - total_rules += 1 - if header.is_selector: - total_selectors += 1 - - prev_ancestry_headers = ancestry.headers - dangling_property = False - - if not skip_selectors: - result += self._print_properties(rule.properties, sc, sp, tb * (ancestry_len + nesting), nl, lnl) - dangling_property = True - - # Close all remaining blocks - for i in reversed(range(len(prev_ancestry_headers))): - result += tb * i + '}' + rnl - - return (result, total_rules, total_selectors) - - def _print_properties(self, properties, sc=True, sp=' ', tb='', nl='\n', lnl=' '): - result = '' - last_prop_index = len(properties) - 1 - for i, (name, value) in enumerate(properties): - if value is None: - prop = name - elif value: - if nl: - value = (nl + tb + tb).join(self._textwrap(value)) - prop = name + ':' + sp + value - else: - # Empty string means there's supposed to be a value but it - # evaluated to nothing; skip this - # TODO interacts poorly with last_prop_index - continue - - if i == last_prop_index: - if sc: - result += tb + prop + ';' + lnl - else: - result += tb + prop + lnl - else: - result += tb + prop + ';' + nl - return result - - -# TODO: this should inherit from SassError, but can't, because that assumes -# it's wrapping another error. fix this with the exception hierarchy -class SassReturn(Exception): - """Special control-flow exception used to hop up the stack from a Sass - function's ``@return``. - """ - def __init__(self, retval): - self.retval = retval - Exception.__init__(self) - - def __str__(self): - return "Returning {0!r}".format(self.retval) diff --git a/libs/scss/__main__.py b/libs/scss/__main__.py deleted file mode 100644 index 792ead73..00000000 --- a/libs/scss/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -import scss.tool - -scss.tool.main() diff --git a/libs/scss/_native.py b/libs/scss/_native.py deleted file mode 100644 index 7dafe548..00000000 --- a/libs/scss/_native.py +++ /dev/null @@ -1,262 +0,0 @@ -"""Pure-Python scanner and parser, used if _speedups is not available.""" -from __future__ import print_function - -from scss.cssdefs import SEPARATOR -import re - -DEBUG = False - -# TODO copied from __init__ -_nl_num_re = re.compile(r'\n.+' + SEPARATOR, re.MULTILINE) -_blocks_re = re.compile(r'[{},;()\'"\n]') - - -def _strip_selprop(selprop, lineno): - # Get the line number of the selector or property and strip all other - # line numbers that might still be there (from multiline selectors) - _lineno, _sep, selprop = selprop.partition(SEPARATOR) - if _sep == SEPARATOR: - _lineno = _lineno.strip(' \t\n;') - try: - lineno = int(_lineno) - except ValueError: - pass - else: - selprop = _lineno - selprop = _nl_num_re.sub('\n', selprop) - selprop = selprop.strip() - return selprop, lineno - - -def _strip(selprop): - # Strip all line numbers, ignoring them in the way - selprop, _ = _strip_selprop(selprop, None) - return selprop - - -def locate_blocks(codestr): - """ - For processing CSS like strings. - - Either returns all selectors (that can be "smart" multi-lined, as - long as it's joined by `,`, or enclosed in `(` and `)`) with its code block - (the one between `{` and `}`, which can be nested), or the "lose" code - (properties) that doesn't have any blocks. - """ - lineno = 0 - - par = 0 - instr = None - depth = 0 - skip = False - thin = None - i = init = safe = lose = 0 - start = end = None - - for m in _blocks_re.finditer(codestr): - i = m.start(0) - c = codestr[i] - if instr is not None: - if c == instr: - instr = None # A string ends (FIXME: needs to accept escaped characters) - elif c in ('"', "'"): - instr = c # A string starts - elif c == '(': # parenthesis begins: - par += 1 - thin = None - safe = i + 1 - elif c == ')': # parenthesis ends: - par -= 1 - elif not par and not instr: - if c == '{': # block begins: - if depth == 0: - if i > 0 and codestr[i - 1] == '#': # Do not process #{...} as blocks! - skip = True - else: - start = i - if thin is not None and _strip(codestr[thin:i]): - init = thin - if lose < init: - _property, lineno = _strip_selprop(codestr[lose:init], lineno) - if _property: - yield lineno, _property, None - lose = init - thin = None - depth += 1 - elif c == '}': # block ends: - if depth > 0: - depth -= 1 - if depth == 0: - if not skip: - end = i - _selectors, lineno = _strip_selprop(codestr[init:start], lineno) - _codestr = codestr[start + 1:end].strip() - if _selectors: - yield lineno, _selectors, _codestr - init = safe = lose = end + 1 - thin = None - skip = False - elif depth == 0: - if c == ';': # End of property (or block): - init = i - if lose < init: - _property, lineno = _strip_selprop(codestr[lose:init], lineno) - if _property: - yield lineno, _property, None - init = safe = lose = i + 1 - thin = None - elif c == ',': - if thin is not None and _strip(codestr[thin:i]): - init = thin - thin = None - safe = i + 1 - elif c == '\n': - if thin is not None and _strip(codestr[thin:i]): - init = thin - thin = i + 1 - elif thin is None and _strip(codestr[safe:i]): - thin = i + 1 # Step on thin ice, if it breaks, it breaks here - if depth > 0: - if not skip: - _selectors, lineno = _strip_selprop(codestr[init:start], lineno) - _codestr = codestr[start + 1:].strip() - if _selectors: - yield lineno, _selectors, _codestr - if par: - raise Exception("Missing closing parenthesis somewhere in block: '%s'" % _selectors) - elif instr: - raise Exception("Missing closing string somewhere in block: '%s'" % _selectors) - else: - raise Exception("Block never closed: '%s'" % _selectors) - losestr = codestr[lose:] - for _property in losestr.split(';'): - _property, lineno = _strip_selprop(_property, lineno) - if _property: - yield lineno, _property, None - - -################################################################################ -# Parser - -class NoMoreTokens(Exception): - """ - Another exception object, for when we run out of tokens - """ - pass - - -class Scanner(object): - def __init__(self, patterns, ignore, input=None): - """ - Patterns is [(terminal,regex)...] - Ignore is [terminal,...]; - Input is a string - """ - self.reset(input) - self.ignore = ignore - # The stored patterns are a pair (compiled regex,source - # regex). If the patterns variable passed in to the - # constructor is None, we assume that the class already has a - # proper .patterns list constructed - if patterns is not None: - self.patterns = [] - for k, r in patterns: - self.patterns.append((k, re.compile(r))) - - def reset(self, input): - self.tokens = [] - self.restrictions = [] - self.input = input - self.pos = 0 - - def __repr__(self): - """ - Print the last 10 tokens that have been scanned in - """ - output = '' - for t in self.tokens[-10:]: - output = "%s\n (@%s) %s = %s" % (output, t[0], t[2], repr(t[3])) - return output - - def _scan(self, restrict): - """ - Should scan another token and add it to the list, self.tokens, - and add the restriction to self.restrictions - """ - # Keep looking for a token, ignoring any in self.ignore - token = None - while True: - best_pat = None - # Search the patterns for a match, with earlier - # tokens in the list having preference - best_pat_len = 0 - for tok, regex in self.patterns: - if DEBUG: - print("\tTrying %s: %s at pos %d -> %s" % (repr(tok), repr(regex.pattern), self.pos, repr(self.input))) - # First check to see if we're restricting to this token - if restrict and tok not in restrict and tok not in self.ignore: - if DEBUG: - print("\tSkipping %r!" % (tok,)) - continue - m = regex.match(self.input, self.pos) - if m: - # We got a match - best_pat = tok - best_pat_len = len(m.group(0)) - if DEBUG: - print("Match OK! %s: %s at pos %d" % (repr(tok), repr(regex.pattern), self.pos)) - break - - # If we didn't find anything, raise an error - if best_pat is None: - msg = "Bad token found" - if restrict: - msg = "Bad token found while trying to find one of the restricted tokens: %s" % (", ".join(repr(r) for r in restrict)) - raise SyntaxError("SyntaxError[@ char %s: %s]" % (repr(self.pos), msg)) - - # If we found something that isn't to be ignored, return it - if best_pat in self.ignore: - # This token should be ignored... - self.pos += best_pat_len - else: - end_pos = self.pos + best_pat_len - # Create a token with this data - token = ( - self.pos, - end_pos, - best_pat, - self.input[self.pos:end_pos] - ) - break - if token is not None: - self.pos = token[1] - # Only add this token if it's not in the list - # (to prevent looping) - if not self.tokens or token != self.tokens[-1]: - self.tokens.append(token) - self.restrictions.append(restrict) - return 1 - return 0 - - def token(self, i, restrict=None): - """ - Get the i'th token, and if i is one past the end, then scan - for another token; restrict is a list of tokens that - are allowed, or 0 for any token. - """ - tokens_len = len(self.tokens) - if i == tokens_len: # We are at the end, get the next... - tokens_len += self._scan(restrict) - if i < tokens_len: - if restrict and self.restrictions[i] and restrict > self.restrictions[i]: - raise NotImplementedError("Unimplemented: restriction set changed") - return self.tokens[i] - raise NoMoreTokens - - def rewind(self, i): - tokens_len = len(self.tokens) - if i <= tokens_len: - token = self.tokens[i] - self.tokens = self.tokens[:i] - self.restrictions = self.restrictions[:i] - self.pos = token[0] diff --git a/libs/scss/config.py b/libs/scss/config.py deleted file mode 100644 index 7836b654..00000000 --- a/libs/scss/config.py +++ /dev/null @@ -1,36 +0,0 @@ -################################################################################ -# Configuration: -DEBUG = False -VERBOSITY = 1 - -import os -PROJECT_ROOT = os.path.normpath(os.path.dirname(os.path.abspath(__file__))) - -# Sass @import load_paths: -LOAD_PATHS = os.path.join(PROJECT_ROOT, 'sass/frameworks') - -# Assets path, where new sprite files are created (defaults to STATIC_ROOT + '/assets'): -ASSETS_ROOT = None -# Cache files path, where cache files are saved (defaults to ASSETS_ROOT): -CACHE_ROOT = None -# Assets path, where new sprite files are created: -STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static') -FONTS_ROOT = None # default: STATIC_ROOT -IMAGES_ROOT = None # default: STATIC_ROOT - -# Urls for the static and assets: -ASSETS_URL = 'static/assets/' -STATIC_URL = 'static/' -FONTS_URL = None # default: STATIC_URL -IMAGES_URL = None # default: STATIC_URL - -# Rendering style. Available values are 'nested', 'expanded', 'compact', 'compressed' and 'legacy' (defaults to 'nested'): -STYLE = 'nested' - -# Use a different scope inside control structures create a scope (defaults to create new scopes for control structures, same as Sass): -CONTROL_SCOPING = True - -# Throw fatal errors when finding undefined variables: -FATAL_UNDEFINED = True - -SPRTE_MAP_DIRECTION = 'vertical' diff --git a/libs/scss/cssdefs.py b/libs/scss/cssdefs.py deleted file mode 100644 index 7b738056..00000000 --- a/libs/scss/cssdefs.py +++ /dev/null @@ -1,384 +0,0 @@ -from math import pi -import re - -# ------------------------------------------------------------------------------ -# Built-in CSS color names -# See: http://www.w3.org/TR/css3-color/#svg-color - -COLOR_NAMES = { - 'aliceblue': (240, 248, 255, 1), - 'antiquewhite': (250, 235, 215, 1), - 'aqua': (0, 255, 255, 1), - 'aquamarine': (127, 255, 212, 1), - 'azure': (240, 255, 255, 1), - 'beige': (245, 245, 220, 1), - 'bisque': (255, 228, 196, 1), - 'black': (0, 0, 0, 1), - 'blanchedalmond': (255, 235, 205, 1), - 'blue': (0, 0, 255, 1), - 'blueviolet': (138, 43, 226, 1), - 'brown': (165, 42, 42, 1), - 'burlywood': (222, 184, 135, 1), - 'cadetblue': (95, 158, 160, 1), - 'chartreuse': (127, 255, 0, 1), - 'chocolate': (210, 105, 30, 1), - 'coral': (255, 127, 80, 1), - 'cornflowerblue': (100, 149, 237, 1), - 'cornsilk': (255, 248, 220, 1), - 'crimson': (220, 20, 60, 1), - 'cyan': (0, 255, 255, 1), - 'darkblue': (0, 0, 139, 1), - 'darkcyan': (0, 139, 139, 1), - 'darkgoldenrod': (184, 134, 11, 1), - 'darkgray': (169, 169, 169, 1), - 'darkgreen': (0, 100, 0, 1), - 'darkkhaki': (189, 183, 107, 1), - 'darkmagenta': (139, 0, 139, 1), - 'darkolivegreen': (85, 107, 47, 1), - 'darkorange': (255, 140, 0, 1), - 'darkorchid': (153, 50, 204, 1), - 'darkred': (139, 0, 0, 1), - 'darksalmon': (233, 150, 122, 1), - 'darkseagreen': (143, 188, 143, 1), - 'darkslateblue': (72, 61, 139, 1), - 'darkslategray': (47, 79, 79, 1), - 'darkturquoise': (0, 206, 209, 1), - 'darkviolet': (148, 0, 211, 1), - 'deeppink': (255, 20, 147, 1), - 'deepskyblue': (0, 191, 255, 1), - 'dimgray': (105, 105, 105, 1), - 'dodgerblue': (30, 144, 255, 1), - 'firebrick': (178, 34, 34, 1), - 'floralwhite': (255, 250, 240, 1), - 'forestgreen': (34, 139, 34, 1), - 'fuchsia': (255, 0, 255, 1), - 'gainsboro': (220, 220, 220, 1), - 'ghostwhite': (248, 248, 255, 1), - 'gold': (255, 215, 0, 1), - 'goldenrod': (218, 165, 32, 1), - 'gray': (128, 128, 128, 1), - 'green': (0, 128, 0, 1), - 'greenyellow': (173, 255, 47, 1), - 'honeydew': (240, 255, 240, 1), - 'hotpink': (255, 105, 180, 1), - 'indianred': (205, 92, 92, 1), - 'indigo': (75, 0, 130, 1), - 'ivory': (255, 255, 240, 1), - 'khaki': (240, 230, 140, 1), - 'lavender': (230, 230, 250, 1), - 'lavenderblush': (255, 240, 245, 1), - 'lawngreen': (124, 252, 0, 1), - 'lemonchiffon': (255, 250, 205, 1), - 'lightblue': (173, 216, 230, 1), - 'lightcoral': (240, 128, 128, 1), - 'lightcyan': (224, 255, 255, 1), - 'lightgoldenrodyellow': (250, 250, 210, 1), - 'lightgreen': (144, 238, 144, 1), - 'lightgrey': (211, 211, 211, 1), - 'lightpink': (255, 182, 193, 1), - 'lightsalmon': (255, 160, 122, 1), - 'lightseagreen': (32, 178, 170, 1), - 'lightskyblue': (135, 206, 250, 1), - 'lightslategray': (119, 136, 153, 1), - 'lightsteelblue': (176, 196, 222, 1), - 'lightyellow': (255, 255, 224, 1), - 'lime': (0, 255, 0, 1), - 'limegreen': (50, 205, 50, 1), - 'linen': (250, 240, 230, 1), - 'magenta': (255, 0, 255, 1), - 'maroon': (128, 0, 0, 1), - 'mediumaquamarine': (102, 205, 170, 1), - 'mediumblue': (0, 0, 205, 1), - 'mediumorchid': (186, 85, 211, 1), - 'mediumpurple': (147, 112, 219, 1), - 'mediumseagreen': (60, 179, 113, 1), - 'mediumslateblue': (123, 104, 238, 1), - 'mediumspringgreen': (0, 250, 154, 1), - 'mediumturquoise': (72, 209, 204, 1), - 'mediumvioletred': (199, 21, 133, 1), - 'midnightblue': (25, 25, 112, 1), - 'mintcream': (245, 255, 250, 1), - 'mistyrose': (255, 228, 225, 1), - 'moccasin': (255, 228, 181, 1), - 'navajowhite': (255, 222, 173, 1), - 'navy': (0, 0, 128, 1), - 'oldlace': (253, 245, 230, 1), - 'olive': (128, 128, 0, 1), - 'olivedrab': (107, 142, 35, 1), - 'orange': (255, 165, 0, 1), - 'orangered': (255, 69, 0, 1), - 'orchid': (218, 112, 214, 1), - 'palegoldenrod': (238, 232, 170, 1), - 'palegreen': (152, 251, 152, 1), - 'paleturquoise': (175, 238, 238, 1), - 'palevioletred': (219, 112, 147, 1), - 'papayawhip': (255, 239, 213, 1), - 'peachpuff': (255, 218, 185, 1), - 'peru': (205, 133, 63, 1), - 'pink': (255, 192, 203, 1), - 'plum': (221, 160, 221, 1), - 'powderblue': (176, 224, 230, 1), - 'purple': (128, 0, 128, 1), - 'red': (255, 0, 0, 1), - 'rosybrown': (188, 143, 143, 1), - 'royalblue': (65, 105, 225, 1), - 'saddlebrown': (139, 69, 19, 1), - 'salmon': (250, 128, 114, 1), - 'sandybrown': (244, 164, 96, 1), - 'seagreen': (46, 139, 87, 1), - 'seashell': (255, 245, 238, 1), - 'sienna': (160, 82, 45, 1), - 'silver': (192, 192, 192, 1), - 'skyblue': (135, 206, 235, 1), - 'slateblue': (106, 90, 205, 1), - 'slategray': (112, 128, 144, 1), - 'snow': (255, 250, 250, 1), - 'springgreen': (0, 255, 127, 1), - 'steelblue': (70, 130, 180, 1), - 'tan': (210, 180, 140, 1), - 'teal': (0, 128, 128, 1), - 'thistle': (216, 191, 216, 1), - 'tomato': (255, 99, 71, 1), - 'transparent': (0, 0, 0, 0), - 'turquoise': (64, 224, 208, 1), - 'violet': (238, 130, 238, 1), - 'wheat': (245, 222, 179, 1), - 'white': (255, 255, 255, 1), - 'whitesmoke': (245, 245, 245, 1), - 'yellow': (255, 255, 0, 1), - 'yellowgreen': (154, 205, 50, 1), -} -COLOR_LOOKUP = dict((v, k) for (k, v) in COLOR_NAMES.items()) - -# ------------------------------------------------------------------------------ -# Built-in CSS units -# See: http://www.w3.org/TR/2013/CR-css3-values-20130730/#numeric-types - -# Maps units to a set of common units per type, with conversion factors -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'), - - # Angles - 'deg': (1 / 360, 'turn'), - 'grad': (1 / 400, 'turn'), - 'rad': (pi / 2, 'turn'), - 'turn': (1, 'turn'), - - # Times - 'ms': (1, 'ms'), - 's': (1000, 'ms'), - - # Frequencies - 'hz': (1, 'hz'), - 'khz': (1000, 'hz'), - - # Resolutions - 'dpi': (1, 'dpi'), - 'dpcm': (2.54, 'dpi'), - 'dppx': (96, 'dpi'), -} - - -def get_conversion_factor(unit): - """Look up the "base" unit for this unit and the factor for converting to - it. - - Returns a 2-tuple of `factor, base_unit`. - """ - if unit in BASE_UNIT_CONVERSIONS: - return BASE_UNIT_CONVERSIONS[unit] - else: - return 1, unit - - -def convert_units_to_base_units(units): - """Convert a set of units into a set of "base" units. - - Returns a 2-tuple of `factor, new_units`. - """ - total_factor = 1 - new_units = [] - for unit in units: - if unit not in BASE_UNIT_CONVERSIONS: - continue - - factor, new_unit = BASE_UNIT_CONVERSIONS[unit] - total_factor *= factor - new_units.append(new_unit) - - new_units.sort() - return total_factor, tuple(new_units) - - -def count_base_units(units): - """Returns a dict mapping names of base units to how many times they - appear in the given iterable of units. Effectively this counts how - many length units you have, how many time units, and so forth. - """ - ret = {} - for unit in units: - factor, base_unit = get_conversion_factor(unit) - - ret.setdefault(base_unit, 0) - ret[base_unit] += 1 - - return ret - - -def cancel_base_units(units, to_remove): - """Given a list of units, remove a specified number of each base unit. - - Arguments: - units: an iterable of units - to_remove: a mapping of base_unit => count, such as that returned from - count_base_units - - Returns a 2-tuple of (factor, remaining_units). - """ - - # Copy the dict since we're about to mutate it - to_remove = to_remove.copy() - remaining_units = [] - total_factor = 1 - - for unit in units: - factor, base_unit = get_conversion_factor(unit) - if not to_remove.get(base_unit, 0): - remaining_units.append(unit) - continue - - total_factor *= factor - to_remove[base_unit] -= 1 - - return total_factor, remaining_units - - -# A fixed set of units can be omitted when the value is 0 -# See: http://www.w3.org/TR/2013/CR-css3-values-20130730/#lengths -ZEROABLE_UNITS = frozenset(( - # Relative lengths - 'em', 'ex', 'ch', 'rem', - # Viewport - 'vw', 'vh', 'vmin', 'vmax', - # Absolute lengths - 'cm', 'mm', 'in', 'px', 'pt', 'pc', -)) - - -# ------------------------------------------------------------------------------ -# Built-in CSS function reference - -# Known function names -BUILTIN_FUNCTIONS = frozenset([ - # CSS2 - 'attr', 'counter', 'counters', 'url', 'rgb', 'rect', - - # CSS3 values: http://www.w3.org/TR/css3-values/ - 'calc', 'min', 'max', 'cycle', - - # CSS3 colors: http://www.w3.org/TR/css3-color/ - 'rgba', 'hsl', 'hsla', - - # CSS3 fonts: http://www.w3.org/TR/css3-fonts/ - 'local', 'format', - - # CSS3 images: http://www.w3.org/TR/css3-images/ - 'image', 'element', - 'linear-gradient', 'radial-gradient', - 'repeating-linear-gradient', 'repeating-radial-gradient', - - # CSS3 transforms: http://www.w3.org/TR/css3-transforms/ - 'perspective', - 'matrix', 'matrix3d', - 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'rotate3d', - 'translate', 'translateX', 'translateY', 'translateZ', 'translate3d', - 'scale', 'scaleX', 'scaleY', 'scaleZ', 'scale3d', - 'skew', 'skewX', 'skewY', - - # CSS3 transitions: http://www.w3.org/TR/css3-transitions/ - 'cubic-bezier', 'steps', - - # CSS filter effects: - # https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html - 'grayscale', 'sepia', 'saturate', 'hue-rotate', 'invert', 'opacity', - 'brightness', 'contrast', 'blur', 'drop-shadow', 'custom', - - # CSS4 image module: - # http://dev.w3.org/csswg/css-images/ - 'image-set', 'cross-fade', - 'conic-gradient', 'repeating-conic-gradient', - - # Others - 'color-stop', # Older version of CSS3 gradients - 'mask', # ??? - 'from', 'to', # Very old WebKit gradient syntax -]) - - -def is_builtin_css_function(name): - """Returns whether the given `name` looks like the name of a builtin CSS - function. - - Unrecognized functions not in this list produce warnings. - """ - name = name.replace('_', '-') - - if name in BUILTIN_FUNCTIONS: - return True - - # Vendor-specific functions (-foo-bar) are always okay - if name[0] == '-' and '-' in name[1:]: - return True - - return False - -# ------------------------------------------------------------------------------ -# Bits and pieces of grammar, as regexen - -SEPARATOR = '\x00' - -_expr_glob_re = re.compile(r''' - \#\{(.*?)\} # Global Interpolation only -''', re.VERBOSE) - -# XXX these still need to be fixed; the //-in-functions thing is a chumpy hack -_ml_comment_re = re.compile(r'\/\*(.*?)\*\/', re.DOTALL) -_sl_comment_re = re.compile(r'(? 1: - log.error("Undefined variable '%s'", n, extra={'stack': True}) - return n - else: - if v: - if not isinstance(v, six.string_types): - v = v.render() - # TODO this used to test for _dequote - if m.group(1): - v = dequote(v) - else: - v = m.group(0) - return v - - cont = _interpolate_re.sub(_av, cont) - # TODO this is surprising and shouldn't be here - cont = self.do_glob_math(cont) - return cont - - def calculate(self, _base_str, divide=False): - better_expr_str = _base_str - - better_expr_str = self.do_glob_math(better_expr_str) - - better_expr_str = self.evaluate_expression(better_expr_str, divide=divide) - - if better_expr_str is None: - better_expr_str = String.unquoted(self.apply_vars(_base_str)) - - return better_expr_str - - # TODO only used by magic-import...? - def interpolate(self, var): - value = self.namespace.variable(var) - if var != value and isinstance(value, six.string_types): - _vi = self.evaluate_expression(value) - if _vi is not None: - value = _vi - return value - - def evaluate_expression(self, expr, divide=False): - try: - ast = self.parse_expression(expr) - except SassError: - if config.DEBUG: - raise - else: - return None - - try: - return ast.evaluate(self, divide=divide) - except Exception as e: - raise SassEvaluationError(e, expression=expr) - - def parse_expression(self, expr, target='goal'): - if not isinstance(expr, six.string_types): - raise TypeError("Expected string, got %r" % (expr,)) - - key = (target, expr) - if key in self.ast_cache: - return self.ast_cache[key] - - try: - parser = SassExpression(SassExpressionScanner(expr)) - ast = getattr(parser, target)() - except SyntaxError as e: - raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) - - self.ast_cache[key] = ast - return ast - - -# ------------------------------------------------------------------------------ -# Expression classes -- the AST resulting from a parse - -class Expression(object): - def __repr__(self): - return '<%s()>' % (self.__class__.__name__) - - def evaluate(self, calculator, divide=False): - """Evaluate this AST node, and return a Sass value. - - `divide` indicates whether a descendant node representing a division - should be forcibly treated as a division. See the commentary in - `BinaryOp`. - """ - raise NotImplementedError - - -class Parentheses(Expression): - """An expression of the form `(foo)`. - - Only exists to force a slash to be interpreted as division when contained - within parentheses. - """ - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.contents)) - - def __init__(self, contents): - self.contents = contents - - def evaluate(self, calculator, divide=False): - return self.contents.evaluate(calculator, divide=True) - - -class UnaryOp(Expression): - def __repr__(self): - return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.op), repr(self.operand)) - - def __init__(self, op, operand): - self.op = op - self.operand = operand - - def evaluate(self, calculator, divide=False): - return self.op(self.operand.evaluate(calculator, divide=True)) - - -class BinaryOp(Expression): - def __repr__(self): - return '<%s(%s, %s, %s)>' % (self.__class__.__name__, repr(self.op), repr(self.left), repr(self.right)) - - def __init__(self, op, left, right): - self.op = op - self.left = left - self.right = right - - def evaluate(self, calculator, divide=False): - left = self.left.evaluate(calculator, divide=True) - right = self.right.evaluate(calculator, divide=True) - - # Special handling of division: treat it as a literal slash if both - # operands are literals, there are parentheses, or this is part of a - # bigger expression. - # The first condition is covered by the type check. The other two are - # covered by the `divide` argument: other nodes that perform arithmetic - # will pass in True, indicating that this should always be a division. - if ( - self.op is operator.truediv - and not divide - and isinstance(self.left, Literal) - and isinstance(self.right, Literal) - ): - return String(left.render() + ' / ' + right.render(), quotes=None) - - return self.op(left, right) - - -class AnyOp(Expression): - def __repr__(self): - return '<%s(*%s)>' % (self.__class__.__name__, repr(self.op), repr(self.operands)) - - def __init__(self, *operands): - self.operands = operands - - def evaluate(self, calculator, divide=False): - for operand in self.operands: - value = operand.evaluate(calculator, divide=True) - if value: - return value - return value - - -class AllOp(Expression): - def __repr__(self): - return '<%s(*%s)>' % (self.__class__.__name__, repr(self.operands)) - - def __init__(self, *operands): - self.operands = operands - - def evaluate(self, calculator, divide=False): - for operand in self.operands: - value = operand.evaluate(calculator, divide=True) - if not value: - return value - return value - - -class NotOp(Expression): - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.operand)) - - def __init__(self, operand): - self.operand = operand - - def evaluate(self, calculator, divide=False): - operand = self.operand.evaluate(calculator, divide=True) - return Boolean(not(operand)) - - -class CallOp(Expression): - def __repr__(self): - return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.func_name), repr(self.argspec)) - - def __init__(self, func_name, argspec): - self.func_name = func_name - self.argspec = argspec - - def evaluate(self, calculator, divide=False): - # TODO bake this into the context and options "dicts", plus library - func_name = normalize_var(self.func_name) - - argspec_node = self.argspec - - # Turn the pairs of arg tuples into *args and **kwargs - # TODO unclear whether this is correct -- how does arg, kwarg, arg - # work? - args, kwargs = argspec_node.evaluate_call_args(calculator) - argspec_len = len(args) + len(kwargs) - - # Translate variable names to Python identifiers - # TODO what about duplicate kw names? should this happen in argspec? - # how does that affect mixins? - kwargs = dict( - (key.lstrip('$').replace('-', '_'), value) - for key, value in kwargs.items()) - - # TODO merge this with the library - 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 - funct = calculator.namespace.function(func_name, 1) - args = [List(args, use_comma=True)] - except KeyError: - if not is_builtin_css_function(func_name): - log.error("Function not found: %s:%s", func_name, argspec_len, extra={'stack': True}) - - if funct: - ret = funct(*args, **kwargs) - if not isinstance(ret, Value): - raise TypeError("Expected Sass type as return value, got %r" % (ret,)) - return ret - - # No matching function found, so render the computed values as a CSS - # function call. Slurpy arguments are expanded and named arguments are - # unsupported. - if kwargs: - raise TypeError("The CSS function %s doesn't support keyword arguments." % (func_name,)) - - # TODO another candidate for a "function call" sass type - rendered_args = [arg.render() for arg in args] - - return String( - u"%s(%s)" % (func_name, u", ".join(rendered_args)), - quotes=None) - - -class Literal(Expression): - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.value)) - - def __init__(self, value): - if isinstance(value, Undefined) and config.FATAL_UNDEFINED: - raise SyntaxError("Undefined literal.") - else: - self.value = value - - def evaluate(self, calculator, divide=False): - return self.value - - -class Variable(Expression): - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.name)) - - def __init__(self, name): - self.name = name - - def evaluate(self, calculator, divide=False): - try: - value = calculator.namespace.variable(self.name) - except KeyError: - if config.FATAL_UNDEFINED: - raise SyntaxError("Undefined variable: '%s'." % self.name) - else: - if config.VERBOSITY > 1: - log.error("Undefined variable '%s'", self.name, extra={'stack': True}) - return Undefined() - else: - if isinstance(value, six.string_types): - evald = calculator.evaluate_expression(value) - if evald is not None: - return evald - return value - - -class ListLiteral(Expression): - def __repr__(self): - return '<%s(%s, comma=%s)>' % (self.__class__.__name__, repr(self.items), repr(self.comma)) - - def __init__(self, items, comma=True): - self.items = items - self.comma = comma - - def evaluate(self, calculator, divide=False): - items = [item.evaluate(calculator, divide=divide) for item in self.items] - - # Whether this is a "plain" literal matters for null removal: nulls are - # left alone if this is a completely vanilla CSS property - is_literal = True - if divide: - # TODO sort of overloading "divide" here... rename i think - is_literal = False - elif not all(isinstance(item, Literal) for item in self.items): - is_literal = False - - return List(items, use_comma=self.comma, is_literal=is_literal) - - -class MapLiteral(Expression): - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.pairs)) - - def __init__(self, pairs): - self.pairs = tuple((var, value) for var, value in pairs if value is not None) - - def evaluate(self, calculator, divide=False): - scss_pairs = [] - for key, value in self.pairs: - scss_pairs.append(( - key.evaluate(calculator), - value.evaluate(calculator), - )) - - return Map(scss_pairs) - - -class ArgspecLiteral(Expression): - """Contains pairs of argument names and values, as parsed from a function - definition or function call. - - Note that the semantics are somewhat ambiguous. Consider parsing: - - $foo, $bar: 3 - - If this appeared in a function call, $foo would refer to a value; if it - appeared in a function definition, $foo would refer to an existing - variable. This it's up to the caller to use the right iteration function. - """ - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.argpairs)) - - def __init__(self, argpairs, slurp=None): - # argpairs is a list of 2-tuples, parsed as though this were a function - # call, so (variable name as string or None, default value as AST - # node). - # slurp is the name of a variable to receive slurpy arguments. - self.argpairs = tuple(argpairs) - if slurp is all: - # DEVIATION: special syntax to allow injecting arbitrary arguments - # from the caller to the callee - self.inject = True - self.slurp = None - elif slurp: - self.inject = False - self.slurp = Variable(slurp) - else: - self.inject = False - self.slurp = None - - def iter_list_argspec(self): - yield None, ListLiteral(zip(*self.argpairs)[1]) - - def iter_def_argspec(self): - """Interpreting this literal as a function definition, yields pairs of - (variable name as a string, default value as an AST node or None). - """ - started_kwargs = False - seen_vars = set() - - for var, value in self.argpairs: - if var is None: - # value is actually the name - var = value - value = None - - if started_kwargs: - raise SyntaxError( - "Required argument %r must precede optional arguments" - % (var.name,)) - - else: - started_kwargs = True - - if not isinstance(var, Variable): - raise SyntaxError("Expected variable name, got %r" % (var,)) - - if var.name in seen_vars: - raise SyntaxError("Duplicate argument %r" % (var.name,)) - seen_vars.add(var.name) - - yield var.name, value - - def evaluate_call_args(self, calculator): - """Interpreting this literal as a function call, return a 2-tuple of - ``(args, kwargs)``. - """ - args = [] - kwargs = {} - for var_node, value_node in self.argpairs: - value = value_node.evaluate(calculator, divide=True) - if var_node is None: - # Positional - args.append(value) - else: - # Named - if not isinstance(var_node, Variable): - raise SyntaxError("Expected variable name, got %r" % (var_node,)) - kwargs[var_node.name] = value - - # Slurpy arguments go on the end of the args - if self.slurp: - args.extend(self.slurp.evaluate(calculator, divide=True)) - - return args, kwargs - - -def parse_bareword(word): - if word in COLOR_NAMES: - return Color.from_name(word) - elif word == 'null': - return Null() - elif word == 'undefined': - return Undefined() - elif word == 'true': - return Boolean(True) - elif word == 'false': - return Boolean(False) - else: - return String(word, quotes=None) - - -class Parser(object): - def __init__(self, scanner): - self._scanner = scanner - self._pos = 0 - self._char_pos = 0 - - def reset(self, input): - self._scanner.reset(input) - self._pos = 0 - self._char_pos = 0 - - def _peek(self, types): - """ - Returns the token type for lookahead; if there are any args - then the list of args is the set of token types to allow - """ - try: - tok = self._scanner.token(self._pos, types) - return tok[2] - except SyntaxError: - return None - - def _scan(self, type): - """ - Returns the matched text, and moves to the next token - """ - tok = self._scanner.token(self._pos, set([type])) - self._char_pos = tok[0] - if tok[2] != type: - raise SyntaxError("SyntaxError[@ char %s: %s]" % (repr(tok[0]), "Trying to find " + type)) - self._pos += 1 - return tok[3] - - -################################################################################ -## Grammar compiled using Yapps: - -class SassExpressionScanner(Scanner): - patterns = None - _patterns = [ - ('":"', ':'), - ('","', ','), - ('[ \r\t\n]+', '[ \r\t\n]+'), - ('LPAR', '\\(|\\['), - ('RPAR', '\\)|\\]'), - ('END', '$'), - ('MUL', '[*]'), - ('DIV', '/'), - ('ADD', '[+]'), - ('SUB', '-\\s'), - ('SIGN', '-(?![a-zA-Z_])'), - ('AND', '(?='), - ('LT', '<'), - ('GT', '>'), - ('DOTDOTDOT', '[.]{3}'), - ('KWSTR', "'[^']*'(?=\\s*:)"), - ('STR', "'[^']*'"), - ('KWQSTR', '"[^"]*"(?=\\s*:)'), - ('QSTR', '"[^"]*"'), - ('UNITS', '(? 1 else v[0] - - def expr_slst(self): - or_expr = self.or_expr() - v = [or_expr] - while self._peek(self.expr_slst_rsts) not in self.argspec_items_rsts: - or_expr = self.or_expr() - v.append(or_expr) - return ListLiteral(v, comma=False) if len(v) > 1 else v[0] - - def or_expr(self): - and_expr = self.and_expr() - v = and_expr - while self._peek(self.or_expr_rsts) == 'OR': - OR = self._scan('OR') - and_expr = self.and_expr() - v = AnyOp(v, and_expr) - return v - - def and_expr(self): - not_expr = self.not_expr() - v = not_expr - while self._peek(self.and_expr_rsts) == 'AND': - AND = self._scan('AND') - not_expr = self.not_expr() - v = AllOp(v, not_expr) - return v - - def not_expr(self): - _token_ = self._peek(self.argspec_item_chks) - if _token_ != 'NOT': - comparison = self.comparison() - return comparison - else: # == 'NOT' - NOT = self._scan('NOT') - not_expr = self.not_expr() - return NotOp(not_expr) - - def comparison(self): - a_expr = self.a_expr() - v = a_expr - while self._peek(self.comparison_rsts) in self.comparison_chks: - _token_ = self._peek(self.comparison_chks) - if _token_ == 'LT': - LT = self._scan('LT') - a_expr = self.a_expr() - v = BinaryOp(operator.lt, v, a_expr) - elif _token_ == 'GT': - GT = self._scan('GT') - a_expr = self.a_expr() - v = BinaryOp(operator.gt, v, a_expr) - elif _token_ == 'LE': - LE = self._scan('LE') - a_expr = self.a_expr() - v = BinaryOp(operator.le, v, a_expr) - elif _token_ == 'GE': - GE = self._scan('GE') - a_expr = self.a_expr() - v = BinaryOp(operator.ge, v, a_expr) - elif _token_ == 'EQ': - EQ = self._scan('EQ') - a_expr = self.a_expr() - v = BinaryOp(operator.eq, v, a_expr) - else: # == 'NE' - NE = self._scan('NE') - a_expr = self.a_expr() - v = BinaryOp(operator.ne, v, a_expr) - return v - - def a_expr(self): - m_expr = self.m_expr() - v = m_expr - while self._peek(self.a_expr_rsts) in self.a_expr_chks: - _token_ = self._peek(self.a_expr_chks) - if _token_ == 'ADD': - ADD = self._scan('ADD') - m_expr = self.m_expr() - v = BinaryOp(operator.add, v, m_expr) - else: # == 'SUB' - SUB = self._scan('SUB') - m_expr = self.m_expr() - v = BinaryOp(operator.sub, v, m_expr) - return v - - def m_expr(self): - u_expr = self.u_expr() - v = u_expr - while self._peek(self.m_expr_rsts) in self.m_expr_chks: - _token_ = self._peek(self.m_expr_chks) - if _token_ == 'MUL': - MUL = self._scan('MUL') - u_expr = self.u_expr() - v = BinaryOp(operator.mul, v, u_expr) - else: # == 'DIV' - DIV = self._scan('DIV') - u_expr = self.u_expr() - v = BinaryOp(operator.truediv, v, u_expr) - return v - - def u_expr(self): - _token_ = self._peek(self.u_expr_rsts) - if _token_ == 'SIGN': - SIGN = self._scan('SIGN') - u_expr = self.u_expr() - return UnaryOp(operator.neg, u_expr) - elif _token_ == 'ADD': - ADD = self._scan('ADD') - u_expr = self.u_expr() - return UnaryOp(operator.pos, u_expr) - else: # in self.u_expr_chks - atom = self.atom() - return atom - - def atom(self): - _token_ = self._peek(self.u_expr_chks) - if _token_ == 'LPAR': - LPAR = self._scan('LPAR') - _token_ = self._peek(self.atom_rsts) - if _token_ not in self.argspec_item_chks: - expr_map = self.expr_map() - v = expr_map - else: # in self.argspec_item_chks - expr_lst = self.expr_lst() - v = expr_lst - RPAR = self._scan('RPAR') - return Parentheses(v) - elif _token_ == 'FNCT': - FNCT = self._scan('FNCT') - LPAR = self._scan('LPAR') - argspec = self.argspec() - RPAR = self._scan('RPAR') - return CallOp(FNCT, argspec) - elif _token_ == 'BANG_IMPORTANT': - BANG_IMPORTANT = self._scan('BANG_IMPORTANT') - return Literal(String(BANG_IMPORTANT, quotes=None)) - elif _token_ == 'ID': - ID = self._scan('ID') - return Literal(parse_bareword(ID)) - elif _token_ == 'NUM': - NUM = self._scan('NUM') - UNITS = None - if self._peek(self.atom_rsts_) == 'UNITS': - UNITS = self._scan('UNITS') - return Literal(Number(float(NUM), unit=UNITS)) - elif _token_ == 'STR': - STR = self._scan('STR') - return Literal(String(STR[1:-1], quotes="'")) - elif _token_ == 'QSTR': - QSTR = self._scan('QSTR') - return Literal(String(QSTR[1:-1], quotes='"')) - elif _token_ == 'COLOR': - COLOR = self._scan('COLOR') - return Literal(Color.from_hex(COLOR, literal=True)) - else: # == 'VAR' - VAR = self._scan('VAR') - return Variable(VAR) - - def kwatom(self): - _token_ = self._peek(self.kwatom_rsts) - if _token_ == '":"': - pass - elif _token_ == 'KWID': - KWID = self._scan('KWID') - return Literal(parse_bareword(KWID)) - elif _token_ == 'KWNUM': - KWNUM = self._scan('KWNUM') - UNITS = None - if self._peek(self.kwatom_rsts_) == 'UNITS': - UNITS = self._scan('UNITS') - return Literal(Number(float(KWNUM), unit=UNITS)) - elif _token_ == 'KWSTR': - KWSTR = self._scan('KWSTR') - return Literal(String(KWSTR[1:-1], quotes="'")) - elif _token_ == 'KWQSTR': - KWQSTR = self._scan('KWQSTR') - return Literal(String(KWQSTR[1:-1], quotes='"')) - elif _token_ == 'KWCOLOR': - KWCOLOR = self._scan('KWCOLOR') - return Literal(Color.from_hex(COLOR, literal=True)) - else: # == 'KWVAR' - KWVAR = self._scan('KWVAR') - return Variable(KWVAR) - - u_expr_chks = set(['LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) - argspec_items_rsts = set(['RPAR', 'END', '","']) - expr_map_rsts = set(['RPAR', '","']) - argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) - kwatom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'KWCOLOR', '":"', 'KWNUM']) - argspec_item_chks = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) - a_expr_chks = set(['ADD', 'SUB']) - expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","']) - or_expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) - and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) - comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","']) - argspec_chks = set(['DOTDOTDOT', 'SLURPYVAR']) - atom_rsts_ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) - expr_map_rsts_ = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'RPAR', 'KWCOLOR', '":"', 'KWNUM', '","']) - u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ']) - argspec_items_rsts_ = set(['KWVAR', 'LPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) - a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) - m_expr_chks = set(['MUL', 'DIV']) - kwatom_rsts_ = set(['UNITS', '":"']) - argspec_items_chks = set(['KWVAR', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) - argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID']) - atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT']) - argspec_chks_ = set(['END', 'RPAR']) - argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID']) - - -### Grammar ends. -################################################################################ - -__all__ = ('Calculator',) diff --git a/libs/scss/functions/__init__.py b/libs/scss/functions/__init__.py deleted file mode 100644 index 0d76eaf0..00000000 --- a/libs/scss/functions/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import absolute_import - -from scss.functions.library import FunctionLibrary - -from scss.functions.core import CORE_LIBRARY -from scss.functions.extra import EXTRA_LIBRARY -from scss.functions.compass.sprites import COMPASS_SPRITES_LIBRARY -from scss.functions.compass.gradients import COMPASS_GRADIENTS_LIBRARY -from scss.functions.compass.helpers import COMPASS_HELPERS_LIBRARY -from scss.functions.compass.images import COMPASS_IMAGES_LIBRARY - - -ALL_BUILTINS_LIBRARY = FunctionLibrary() -ALL_BUILTINS_LIBRARY.inherit( - CORE_LIBRARY, - EXTRA_LIBRARY, - COMPASS_GRADIENTS_LIBRARY, - COMPASS_HELPERS_LIBRARY, - COMPASS_IMAGES_LIBRARY, - COMPASS_SPRITES_LIBRARY, -) - -# TODO back-compat for the only codebase still using the old name :) -FunctionRegistry = FunctionLibrary -scss_builtins = ALL_BUILTINS_LIBRARY diff --git a/libs/scss/functions/compass/__init__.py b/libs/scss/functions/compass/__init__.py deleted file mode 100644 index 73cb9df9..00000000 --- a/libs/scss/functions/compass/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Global cache of image sizes, shared between sprites and images libraries. -_image_size_cache = {} diff --git a/libs/scss/functions/compass/gradients.py b/libs/scss/functions/compass/gradients.py deleted file mode 100644 index 6f2b5b83..00000000 --- a/libs/scss/functions/compass/gradients.py +++ /dev/null @@ -1,400 +0,0 @@ -"""Utilities for working with gradients. Inspired by Compass, but not quite -the same. -""" - -from __future__ import absolute_import - -import base64 -import logging - -import six - -from scss.functions.library import FunctionLibrary -from scss.functions.compass.helpers import opposite_position, position -from scss.types import Color, List, Number, String -from scss.util import escape, split_params, to_float, to_str - -log = logging.getLogger(__name__) - -COMPASS_GRADIENTS_LIBRARY = FunctionLibrary() -register = COMPASS_GRADIENTS_LIBRARY.register - - -# ------------------------------------------------------------------------------ - -def __color_stops(percentages, *args): - if len(args) == 1: - if isinstance(args[0], (list, tuple, List)): - list(args[0]) - elif isinstance(args[0], (String, six.string_types)): - color_stops = [] - colors = split_params(getattr(args[0], 'value', args[0])) - for color in colors: - color = color.strip() - if color.startswith('color-stop('): - s, c = split_params(color[11:].rstrip(')')) - s = s.strip() - c = c.strip() - else: - c, s = color.split() - color_stops.append((to_float(s), c)) - return color_stops - - colors = [] - stops = [] - prev_color = False - for c in args: - for c in List.from_maybe(c): - if isinstance(c, Color): - if prev_color: - stops.append(None) - colors.append(c) - prev_color = True - elif isinstance(c, Number): - stops.append(c) - prev_color = False - - if prev_color: - stops.append(None) - stops = stops[:len(colors)] - if stops[0] is None: - stops[0] = Number(0, '%') - if stops[-1] is None: - stops[-1] = Number(100, '%') - - maxable_stops = [s for s in stops if s and not s.is_simple_unit('%')] - if maxable_stops: - max_stops = max(maxable_stops) - else: - max_stops = None - - stops = [_s / max_stops if _s and not _s.is_simple_unit('%') else _s for _s in stops] - - init = 0 - start = None - for i, s in enumerate(stops + [1.0]): - if s is None: - if start is None: - start = i - end = i - else: - final = s - if start is not None: - stride = (final - init) / Number(end - start + 1 + (1 if i < len(stops) else 0)) - for j in range(start, end + 1): - stops[j] = init + stride * Number(j - start + 1) - init = final - start = None - - if not max_stops or percentages: - pass - else: - stops = [s if s.is_simple_unit('%') else s * max_stops for s in stops] - - return list(zip(stops, colors)) - - -def _render_standard_color_stops(color_stops): - pairs = [] - for i, (stop, color) in enumerate(color_stops): - if ((i == 0 and stop == Number(0, '%')) or - (i == len(color_stops) - 1 and stop == Number(100, '%'))): - pairs.append(color) - else: - pairs.append(List([color, stop], use_comma=False)) - - return List(pairs, use_comma=True) - - -@register('grad-color-stops') -def grad_color_stops(*args): - args = List.from_maybe_starargs(args) - color_stops = __color_stops(True, *args) - ret = ', '.join(['color-stop(%s, %s)' % (s.render(), c.render()) for s, c in color_stops]) - return String.unquoted(ret) - - -def __grad_end_position(radial, color_stops): - return __grad_position(-1, 100, radial, color_stops) - - -@register('grad-point') -def grad_point(*p): - pos = set() - hrz = vrt = Number(0.5, '%') - for _p in p: - pos.update(String.unquoted(_p).value.split()) - if 'left' in pos: - hrz = Number(0, '%') - elif 'right' in pos: - hrz = Number(1, '%') - if 'top' in pos: - vrt = Number(0, '%') - elif 'bottom' in pos: - vrt = Number(1, '%') - return List([v for v in (hrz, vrt) if v is not None]) - - -def __grad_position(index, default, radial, color_stops): - try: - stops = Number(color_stops[index][0]) - if radial and not stops.is_simple_unit('px') and (index == 0 or index == -1 or index == len(color_stops) - 1): - log.warn("Webkit only supports pixels for the start and end stops for radial gradients. Got %s", stops) - except IndexError: - stops = Number(default) - return stops - - -@register('grad-end-position') -def grad_end_position(*color_stops): - color_stops = __color_stops(False, *color_stops) - return Number(__grad_end_position(False, color_stops)) - - -@register('color-stops') -def color_stops(*args): - args = List.from_maybe_starargs(args) - color_stops = __color_stops(False, *args) - ret = ', '.join(['%s %s' % (c.render(), s.render()) for s, c in color_stops]) - return String.unquoted(ret) - - -@register('color-stops-in-percentages') -def color_stops_in_percentages(*args): - args = List.from_maybe_starargs(args) - color_stops = __color_stops(True, *args) - ret = ', '.join(['%s %s' % (c.render(), s.render()) for s, c in color_stops]) - return String.unquoted(ret) - - -def _get_gradient_position_and_angle(args): - for arg in args: - ret = None - skip = False - for a in arg: - if isinstance(a, Color): - skip = True - break - elif isinstance(a, Number): - ret = arg - if skip: - continue - if ret is not None: - return ret - for seek in ( - 'center', - 'top', 'bottom', - 'left', 'right', - ): - if String(seek) in arg: - return arg - return None - - -def _get_gradient_shape_and_size(args): - for arg in args: - for seek in ( - 'circle', 'ellipse', - 'closest-side', 'closest-corner', - 'farthest-side', 'farthest-corner', - 'contain', 'cover', - ): - if String(seek) in arg: - return arg - return None - - -def _get_gradient_color_stops(args): - color_stops = [] - for arg in args: - for a in List.from_maybe(arg): - if isinstance(a, Color): - color_stops.append(arg) - break - return color_stops or None - - -@register('radial-gradient') -def radial_gradient(*args): - args = List.from_maybe_starargs(args) - - position_and_angle = _get_gradient_position_and_angle(args) - shape_and_size = _get_gradient_shape_and_size(args) - color_stops = _get_gradient_color_stops(args) - if color_stops is None: - raise Exception('No color stops provided to radial-gradient function') - color_stops = __color_stops(False, *color_stops) - - args = [ - position(position_and_angle) if position_and_angle is not None else None, - shape_and_size if shape_and_size is not None else None, - ] - args.extend(_render_standard_color_stops(color_stops)) - - to__s = 'radial-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' - ret = String.unquoted(to__s) - - def to__css2(): - return String.unquoted('') - ret.to__css2 = to__css2 - - def to__moz(): - return String.unquoted('-moz-' + to__s) - ret.to__moz = to__moz - - def to__pie(): - log.warn("PIE does not support radial-gradient.") - return String.unquoted('-pie-radial-gradient(unsupported)') - ret.to__pie = to__pie - - def to__webkit(): - return String.unquoted('-webkit-' + to__s) - ret.to__webkit = to__webkit - - def to__owg(): - args = [ - 'radial', - grad_point(position_and_angle) if position_and_angle is not None else 'center', - '0', - grad_point(position_and_angle) if position_and_angle is not None else 'center', - __grad_end_position(True, color_stops), - ] - args.extend('color-stop(%s, %s)' % (s.render(), c.render()) for s, c in color_stops) - ret = '-webkit-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' - return String.unquoted(ret) - ret.to__owg = to__owg - - def to__svg(): - return radial_svg_gradient(color_stops, position_and_angle or 'center') - ret.to__svg = to__svg - - return ret - - -@register('linear-gradient') -def linear_gradient(*args): - args = List.from_maybe_starargs(args) - - position_and_angle = _get_gradient_position_and_angle(args) - color_stops = _get_gradient_color_stops(args) - if color_stops is None: - raise Exception('No color stops provided to linear-gradient function') - color_stops = __color_stops(False, *color_stops) - - args = [ - position(position_and_angle) if position_and_angle is not None else None, - ] - args.extend(_render_standard_color_stops(color_stops)) - - to__s = 'linear-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' - ret = String.unquoted(to__s) - - def to__css2(): - return String.unquoted('') - ret.to__css2 = to__css2 - - def to__moz(): - return String.unquoted('-moz-' + to__s) - ret.to__moz = to__moz - - def to__pie(): - return String.unquoted('-pie-' + to__s) - ret.to__pie = to__pie - - def to__ms(): - return String.unquoted('-ms-' + to__s) - ret.to__ms = to__ms - - def to__o(): - return String.unquoted('-o-' + to__s) - ret.to__o = to__o - - def to__webkit(): - return String.unquoted('-webkit-' + to__s) - ret.to__webkit = to__webkit - - def to__owg(): - args = [ - 'linear', - position(position_and_angle or None), - opposite_position(position_and_angle or None), - ] - args.extend('color-stop(%s, %s)' % (s.render(), c.render()) for s, c in color_stops) - ret = '-webkit-gradient(' + ', '.join(to_str(a) for a in args if a is not None) + ')' - return String.unquoted(ret) - ret.to__owg = to__owg - - def to__svg(): - return linear_svg_gradient(color_stops, position_and_angle or 'top') - ret.to__svg = to__svg - - return ret - - -@register('radial-svg-gradient') -def radial_svg_gradient(*args): - args = List.from_maybe_starargs(args) - color_stops = args - center = None - if isinstance(args[-1], (String, Number, six.string_types)): - center = args[-1] - color_stops = args[:-1] - color_stops = __color_stops(False, *color_stops) - cx, cy = zip(*grad_point(center).items())[1] - r = __grad_end_position(True, color_stops) - svg = __radial_svg(color_stops, cx, cy, r) - url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg) - inline = 'url("%s")' % escape(url) - return String.unquoted(inline) - - -@register('linear-svg-gradient') -def linear_svg_gradient(*args): - args = List.from_maybe_starargs(args) - color_stops = args - start = None - if isinstance(args[-1], (String, Number, six.string_types)): - start = args[-1] - color_stops = args[:-1] - color_stops = __color_stops(False, *color_stops) - x1, y1 = zip(*grad_point(start).items())[1] - x2, y2 = zip(*grad_point(opposite_position(start)).items())[1] - svg = _linear_svg(color_stops, x1, y1, x2, y2) - url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg) - inline = 'url("%s")' % escape(url) - return String.unquoted(inline) - - -def __color_stops_svg(color_stops): - ret = ''.join('' % (to_str(s), c) for s, c in color_stops) - return ret - - -def __svg_template(gradient): - ret = '\ -\ -%s\ -\ -' % gradient - return ret - - -def _linear_svg(color_stops, x1, y1, x2, y2): - gradient = '%s' % ( - to_str(Number(x1)), - to_str(Number(y1)), - to_str(Number(x2)), - to_str(Number(y2)), - __color_stops_svg(color_stops) - ) - return __svg_template(gradient) - - -def __radial_svg(color_stops, cx, cy, r): - gradient = '%s' % ( - to_str(Number(cx)), - to_str(Number(cy)), - to_str(Number(r)), - __color_stops_svg(color_stops) - ) - return __svg_template(gradient) diff --git a/libs/scss/functions/compass/helpers.py b/libs/scss/functions/compass/helpers.py deleted file mode 100644 index fe1ef6c9..00000000 --- a/libs/scss/functions/compass/helpers.py +++ /dev/null @@ -1,654 +0,0 @@ -"""Miscellaneous helper functions ported from Compass. - -See: http://compass-style.org/reference/compass/helpers/ - -This collection is not necessarily complete or up-to-date. -""" - -from __future__ import absolute_import - -import base64 -import logging -import math -import os.path -import time - -import six - -from scss import config -from scss.functions.library import FunctionLibrary -from scss.types import Boolean, List, Null, Number, String -from scss.util import escape, to_str -import re - -log = logging.getLogger(__name__) - - -COMPASS_HELPERS_LIBRARY = FunctionLibrary() -register = COMPASS_HELPERS_LIBRARY.register - -FONT_TYPES = { - 'woff': 'woff', - 'otf': 'opentype', - 'opentype': 'opentype', - 'ttf': 'truetype', - 'truetype': 'truetype', - 'svg': 'svg', - 'eot': 'embedded-opentype' -} - - -def add_cache_buster(url, mtime): - fragment = url.split('#') - query = fragment[0].split('?') - if len(query) > 1 and query[1] != '': - cb = '&_=%s' % (mtime) - url = '?'.join(query) + cb - else: - cb = '?_=%s' % (mtime) - url = query[0] + cb - if len(fragment) > 1: - url += '#' + fragment[1] - return url - - -# ------------------------------------------------------------------------------ -# Data manipulation - -@register('blank') -def blank(*objs): - """Returns true when the object is false, an empty string, or an empty list""" - for o in objs: - if isinstance(o, Boolean): - is_blank = not o - elif isinstance(o, String): - is_blank = not len(o.value.strip()) - elif isinstance(o, List): - is_blank = all(blank(el) for el in o) - else: - is_blank = False - - if not is_blank: - return Boolean(False) - - return Boolean(True) - - -@register('compact') -def compact(*args): - """Returns a new list after removing any non-true values""" - use_comma = True - if len(args) == 1 and isinstance(args[0], List): - use_comma = args[0].use_comma - args = args[0] - - return List( - [arg for arg in args if arg], - use_comma=use_comma, - ) - - -@register('reject') -def reject(lst, *values): - """Removes the given values from the list""" - lst = List.from_maybe(lst) - values = frozenset(List.from_maybe_starargs(values)) - - ret = [] - for item in lst: - if item not in values: - ret.append(item) - return List(ret, use_comma=lst.use_comma) - - -@register('first-value-of') -def first_value_of(*args): - if len(args) == 1 and isinstance(args[0], String): - first = args[0].value.split()[0] - return type(args[0])(first) - - args = List.from_maybe_starargs(args) - if len(args): - return args[0] - else: - return Null() - - -@register('-compass-list') -def dash_compass_list(*args): - return List.from_maybe_starargs(args) - - -@register('-compass-space-list') -def dash_compass_space_list(*lst): - """ - If the argument is a list, it will return a new list that is space delimited - Otherwise it returns a new, single element, space-delimited list. - """ - ret = dash_compass_list(*lst) - ret.value.pop('_', None) - return ret - - -@register('-compass-slice', 3) -def dash_compass_slice(lst, start_index, end_index=None): - start_index = Number(start_index).value - end_index = Number(end_index).value if end_index is not None else None - ret = {} - lst = List(lst) - if end_index: - # This function has an inclusive end, but Python slicing is exclusive - end_index += 1 - ret = lst.value[start_index:end_index] - return List(ret, use_comma=lst.use_comma) - - -# ------------------------------------------------------------------------------ -# Property prefixing - -@register('prefixed') -def prefixed(prefix, *args): - to_fnct_str = 'to_' + to_str(prefix).replace('-', '_') - for arg in List.from_maybe_starargs(args): - if hasattr(arg, to_fnct_str): - return Boolean(True) - return Boolean(False) - - -@register('prefix') -def prefix(prefix, *args): - to_fnct_str = 'to_' + to_str(prefix).replace('-', '_') - args = list(args) - for i, arg in enumerate(args): - if isinstance(arg, List): - _value = [] - for iarg in arg: - to_fnct = getattr(iarg, to_fnct_str, None) - if to_fnct: - _value.append(to_fnct()) - else: - _value.append(iarg) - args[i] = List(_value) - else: - to_fnct = getattr(arg, to_fnct_str, None) - if to_fnct: - args[i] = to_fnct() - - return List.maybe_new(args, use_comma=True) - - -@register('-moz') -def dash_moz(*args): - return prefix('_moz', *args) - - -@register('-svg') -def dash_svg(*args): - return prefix('_svg', *args) - - -@register('-css2') -def dash_css2(*args): - return prefix('_css2', *args) - - -@register('-pie') -def dash_pie(*args): - return prefix('_pie', *args) - - -@register('-webkit') -def dash_webkit(*args): - return prefix('_webkit', *args) - - -@register('-owg') -def dash_owg(*args): - return prefix('_owg', *args) - - -@register('-khtml') -def dash_khtml(*args): - return prefix('_khtml', *args) - - -@register('-ms') -def dash_ms(*args): - return prefix('_ms', *args) - - -@register('-o') -def dash_o(*args): - return prefix('_o', *args) - - -# ------------------------------------------------------------------------------ -# Selector generation - -@register('append-selector', 2) -def append_selector(selector, to_append): - if isinstance(selector, List): - lst = selector.value - else: - lst = String.unquoted(selector).value.split(',') - to_append = String.unquoted(to_append).value.strip() - ret = sorted(set(s.strip() + to_append for s in lst if s.strip())) - ret = dict(enumerate(ret)) - ret['_'] = ',' - return ret - - -_elements_of_type_block = 'address, article, aside, blockquote, center, dd, details, dir, div, dl, dt, fieldset, figcaption, figure, footer, form, frameset, h1, h2, h3, h4, h5, h6, header, hgroup, hr, isindex, menu, nav, noframes, noscript, ol, p, pre, section, summary, ul' -_elements_of_type_inline = 'a, abbr, acronym, audio, b, basefont, bdo, big, br, canvas, cite, code, command, datalist, dfn, em, embed, font, i, img, input, kbd, keygen, label, mark, meter, output, progress, q, rp, rt, ruby, s, samp, select, small, span, strike, strong, sub, sup, textarea, time, tt, u, var, video, wbr' -_elements_of_type_table = 'table' -_elements_of_type_list_item = 'li' -_elements_of_type_table_row_group = 'tbody' -_elements_of_type_table_header_group = 'thead' -_elements_of_type_table_footer_group = 'tfoot' -_elements_of_type_table_row = 'tr' -_elements_of_type_table_cel = 'td, th' -_elements_of_type_html5_block = 'article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary' -_elements_of_type_html5_inline = 'audio, canvas, command, datalist, embed, keygen, mark, meter, output, progress, rp, rt, ruby, time, video, wbr' -_elements_of_type_html5 = 'article, aside, audio, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup, keygen, mark, menu, meter, nav, output, progress, rp, rt, ruby, section, summary, time, video, wbr' -_elements_of_type = { - 'block': sorted(_elements_of_type_block.replace(' ', '').split(',')), - 'inline': sorted(_elements_of_type_inline.replace(' ', '').split(',')), - 'table': sorted(_elements_of_type_table.replace(' ', '').split(',')), - 'list-item': sorted(_elements_of_type_list_item.replace(' ', '').split(',')), - 'table-row-group': sorted(_elements_of_type_table_row_group.replace(' ', '').split(',')), - 'table-header-group': sorted(_elements_of_type_table_header_group.replace(' ', '').split(',')), - 'table-footer-group': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')), - 'table-row': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')), - 'table-cell': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')), - 'html5-block': sorted(_elements_of_type_html5_block.replace(' ', '').split(',')), - 'html5-inline': sorted(_elements_of_type_html5_inline.replace(' ', '').split(',')), - 'html5': sorted(_elements_of_type_html5.replace(' ', '').split(',')), -} - - -@register('elements-of-type', 1) -def elements_of_type(display): - d = String.unquoted(display) - ret = _elements_of_type.get(d.value, None) - if ret is None: - raise Exception("Elements of type '%s' not found!" % d.value) - return List(ret, use_comma=True) - - -@register('enumerate', 3) -@register('enumerate', 4) -def enumerate_(prefix, frm, through, separator='-'): - separator = String.unquoted(separator).value - try: - frm = int(getattr(frm, 'value', frm)) - except ValueError: - frm = 1 - try: - through = int(getattr(through, 'value', through)) - except ValueError: - through = frm - if frm > through: - # DEVIATION: allow reversed enumerations (and ranges as range() uses enumerate, like '@for .. from .. through') - frm, through = through, frm - rev = reversed - else: - rev = lambda x: x - - ret = [] - for i in rev(range(frm, through + 1)): - if prefix and prefix.value: - ret.append(String.unquoted(prefix.value + separator + str(i))) - else: - ret.append(Number(i)) - - return List(ret, use_comma=True) - - -@register('headers', 0) -@register('headers', 1) -@register('headers', 2) -@register('headings', 0) -@register('headings', 1) -@register('headings', 2) -def headers(frm=None, to=None): - if frm and to is None: - if isinstance(frm, String) and frm.value.lower() == 'all': - frm = 1 - to = 6 - else: - try: - to = int(getattr(frm, 'value', frm)) - except ValueError: - to = 6 - frm = 1 - else: - try: - frm = 1 if frm is None else int(getattr(frm, 'value', frm)) - except ValueError: - frm = 1 - try: - to = 6 if to is None else int(getattr(to, 'value', to)) - except ValueError: - to = 6 - ret = [String.unquoted('h' + str(i)) for i in range(frm, to + 1)] - return List(ret, use_comma=True) - - -@register('nest') -def nest(*arguments): - if isinstance(arguments[0], List): - lst = arguments[0] - elif isinstance(arguments[0], String): - lst = arguments[0].value.split(',') - else: - raise TypeError("Expected list or string, got %r" % (arguments[0],)) - - ret = [] - for s in lst: - if isinstance(s, String): - s = s.value - elif isinstance(s, six.string_types): - s = s - else: - raise TypeError("Expected string, got %r" % (s,)) - - s = s.strip() - if not s: - continue - - ret.append(s) - - for arg in arguments[1:]: - if isinstance(arg, List): - lst = arg - elif isinstance(arg, String): - lst = arg.value.split(',') - else: - raise TypeError("Expected list or string, got %r" % (arg,)) - - new_ret = [] - for s in lst: - if isinstance(s, String): - s = s.value - elif isinstance(s, six.string_types): - s = s - else: - raise TypeError("Expected string, got %r" % (s,)) - - s = s.strip() - if not s: - continue - - for r in ret: - if '&' in s: - new_ret.append(s.replace('&', r)) - else: - if not r or r[-1] in ('.', ':', '#'): - new_ret.append(r + s) - else: - new_ret.append(r + ' ' + s) - ret = new_ret - - ret = [String.unquoted(s) for s in sorted(set(ret))] - return List(ret, use_comma=True) - - -# This isn't actually from Compass, but it's just a shortcut for enumerate(). -# DEVIATION: allow reversed ranges (range() uses enumerate() which allows reversed values, like '@for .. from .. through') -@register('range', 1) -@register('range', 2) -def range_(frm, through=None): - if through is None: - through = frm - frm = 1 - return enumerate_(None, frm, through) - -# ------------------------------------------------------------------------------ -# Working with CSS constants - -OPPOSITE_POSITIONS = { - 'top': String.unquoted('bottom'), - 'bottom': String.unquoted('top'), - 'left': String.unquoted('right'), - 'right': String.unquoted('left'), - 'center': String.unquoted('center'), -} -DEFAULT_POSITION = [String.unquoted('center'), String.unquoted('top')] - - -def _position(opposite, positions): - if positions is None: - positions = DEFAULT_POSITION - else: - positions = List.from_maybe(positions) - - ret = [] - for pos in positions: - if isinstance(pos, (String, six.string_types)): - pos_value = getattr(pos, 'value', pos) - if pos_value in OPPOSITE_POSITIONS: - if opposite: - ret.append(OPPOSITE_POSITIONS[pos_value]) - else: - ret.append(pos) - continue - elif pos_value == 'to': - # Gradient syntax keyword; leave alone - ret.append(pos) - continue - - elif isinstance(pos, Number): - if pos.is_simple_unit('%'): - if opposite: - ret.append(Number(100 - pos.value, '%')) - else: - ret.append(pos) - continue - elif pos.is_simple_unit('deg'): - # TODO support other angle types? - if opposite: - ret.append(Number((pos.value + 180) % 360, 'deg')) - else: - ret.append(pos) - continue - - if opposite: - log.warn("Can't find opposite for position %r" % (pos,)) - ret.append(pos) - - return List(ret, use_comma=False).maybe() - - -@register('position') -def position(p): - return _position(False, p) - - -@register('opposite-position') -def opposite_position(p): - return _position(True, p) - - -# ------------------------------------------------------------------------------ -# Math - -@register('pi', 0) -def pi(): - return Number(math.pi) - - -@register('e', 0) -def e(): - return Number(math.e) - - -@register('log', 1) -@register('log', 2) -def log_(number, base=None): - if not isinstance(number, Number): - raise TypeError("Expected number, got %r" % (number,)) - elif not number.is_unitless: - raise ValueError("Expected unitless number, got %r" % (number,)) - - if base is None: - pass - elif not isinstance(base, Number): - raise TypeError("Expected number, got %r" % (base,)) - elif not base.is_unitless: - raise ValueError("Expected unitless number, got %r" % (base,)) - - if base is None: - ret = math.log(number.value) - else: - ret = math.log(number.value, base.value) - - return Number(ret) - - -@register('pow', 2) -def pow(number, exponent): - return number ** exponent - - -COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.sqrt), 'sqrt', 1) -COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.sin), 'sin', 1) -COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.cos), 'cos', 1) -COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.tan), 'tan', 1) - - -# ------------------------------------------------------------------------------ -# Fonts - -def _fonts_root(): - return config.STATIC_ROOT if config.FONTS_ROOT is None else config.FONTS_ROOT - - -def _font_url(path, only_path=False, cache_buster=True, inline=False): - filepath = String.unquoted(path).value - file = None - FONTS_ROOT = _fonts_root() - if callable(FONTS_ROOT): - try: - _file, _storage = list(FONTS_ROOT(filepath))[0] - d_obj = _storage.modified_time(_file) - filetime = int(time.mktime(d_obj.timetuple())) - if inline: - file = _storage.open(_file) - except: - filetime = 'NA' - else: - _path = os.path.join(FONTS_ROOT, filepath.strip('/')) - if os.path.exists(_path): - filetime = int(os.path.getmtime(_path)) - if inline: - file = open(_path, 'rb') - else: - filetime = 'NA' - - BASE_URL = config.FONTS_URL or config.STATIC_URL - if file and inline: - font_type = None - if re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value): - font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value).groups()[1]).value - - if not FONT_TYPES.get(font_type): - raise Exception('Could not determine font type for "%s"' % path.value) - - mime = FONT_TYPES.get(font_type) - if font_type == 'woff': - mime = 'application/font-woff' - elif font_type == 'eot': - mime = 'application/vnd.ms-fontobject' - url = 'data:' + (mime if '/' in mime else 'font/%s' % mime) + ';base64,' + base64.b64encode(file.read()) - file.close() - else: - url = '%s/%s' % (BASE_URL.rstrip('/'), filepath.lstrip('/')) - if cache_buster and filetime != 'NA': - url = add_cache_buster(url, filetime) - - if not only_path: - url = 'url(%s)' % escape(url) - return String.unquoted(url) - - -def _font_files(args, inline): - if args == (): - return String.unquoted("") - - fonts = [] - args_len = len(args) - skip_next = False - for index in range(len(args)): - arg = args[index] - if not skip_next: - font_type = args[index + 1] if args_len > (index + 1) else None - if font_type and font_type.value in FONT_TYPES: - skip_next = True - else: - if re.match(r'^([^?]+)[.](.*)([?].*)?$', arg.value): - font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', arg.value).groups()[1]) - - if font_type.value in FONT_TYPES: - fonts.append(String.unquoted('%s format("%s")' % (_font_url(arg, inline=inline), String.unquoted(FONT_TYPES[font_type.value]).value))) - else: - raise Exception('Could not determine font type for "%s"' % arg.value) - else: - skip_next = False - - return List(fonts, separator=',') - - -@register('font-url', 1) -@register('font-url', 2) -def font_url(path, only_path=False, cache_buster=True): - """ - Generates a path to an asset found relative to the project's font directory. - Passing a true value as the second argument will cause the only the path to - be returned instead of a `url()` function - """ - return _font_url(path, only_path, cache_buster, False) - - -@register('font-files') -def font_files(*args): - return _font_files(args, inline=False) - - -@register('inline-font-files') -def inline_font_files(*args): - return _font_files(args, inline=True) - - -# ------------------------------------------------------------------------------ -# External stylesheets - -@register('stylesheet-url', 1) -@register('stylesheet-url', 2) -def stylesheet_url(path, only_path=False, cache_buster=True): - """ - Generates a path to an asset found relative to the project's css directory. - Passing a true value as the second argument will cause the only the path to - be returned instead of a `url()` function - """ - filepath = String.unquoted(path).value - if callable(config.STATIC_ROOT): - try: - _file, _storage = list(config.STATIC_ROOT(filepath))[0] - d_obj = _storage.modified_time(_file) - filetime = int(time.mktime(d_obj.timetuple())) - except: - filetime = 'NA' - else: - _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) - if os.path.exists(_path): - filetime = int(os.path.getmtime(_path)) - else: - filetime = 'NA' - BASE_URL = config.STATIC_URL - - url = '%s%s' % (BASE_URL, filepath) - if cache_buster: - url = add_cache_buster(url, filetime) - if not only_path: - url = 'url("%s")' % (url) - return String.unquoted(url) diff --git a/libs/scss/functions/compass/images.py b/libs/scss/functions/compass/images.py deleted file mode 100644 index 3eab26ce..00000000 --- a/libs/scss/functions/compass/images.py +++ /dev/null @@ -1,285 +0,0 @@ -"""Image utilities ported from Compass.""" - -from __future__ import absolute_import -from __future__ import print_function - -import base64 -import hashlib -import logging -import mimetypes -import os.path -import time - -import six -from six.moves import xrange - -from scss import config -from scss.functions.compass import _image_size_cache -from scss.functions.compass.helpers import add_cache_buster -from scss.functions.library import FunctionLibrary -from scss.types import Color, List, Number, String -from scss.util import escape - -try: - from PIL import Image -except ImportError: - try: - import Image - except: - Image = None - -log = logging.getLogger(__name__) - -COMPASS_IMAGES_LIBRARY = FunctionLibrary() -register = COMPASS_IMAGES_LIBRARY.register - - -# ------------------------------------------------------------------------------ - -def _images_root(): - return config.STATIC_ROOT if config.IMAGES_ROOT is None else config.IMAGES_ROOT - - -def _image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, inline=False, mime_type=None, spacing=None, collapse_x=None, collapse_y=None): - """ - src_color - a list of or a single color to be replaced by each corresponding dst_color colors - spacing - spaces to be added to the image - collapse_x, collapse_y - collapsable (layered) image of the given size (x, y) - """ - if inline or dst_color or spacing: - if not Image: - raise Exception("Images manipulation require PIL") - filepath = String.unquoted(path).value - fileext = os.path.splitext(filepath)[1].lstrip('.').lower() - if mime_type: - mime_type = String.unquoted(mime_type).value - if not mime_type: - mime_type = mimetypes.guess_type(filepath)[0] - if not mime_type: - mime_type = 'image/%s' % fileext - path = None - IMAGES_ROOT = _images_root() - if callable(IMAGES_ROOT): - try: - _file, _storage = list(IMAGES_ROOT(filepath))[0] - d_obj = _storage.modified_time(_file) - filetime = int(time.mktime(d_obj.timetuple())) - if inline or dst_color or spacing: - path = _storage.open(_file) - except: - filetime = 'NA' - else: - _path = os.path.join(IMAGES_ROOT.rstrip('/'), filepath.strip('/')) - if os.path.exists(_path): - filetime = int(os.path.getmtime(_path)) - if inline or dst_color or spacing: - path = open(_path, 'rb') - else: - filetime = 'NA' - - BASE_URL = config.IMAGES_URL or config.STATIC_URL - if path: - dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v] - - src_color = Color.from_name('black') if src_color is None else src_color - src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)] - - len_colors = max(len(dst_colors), len(src_colors)) - dst_colors = (dst_colors * len_colors)[:len_colors] - src_colors = (src_colors * len_colors)[:len_colors] - - spacing = Number(0) if spacing is None else spacing - spacing = [int(Number(v).value) for v in List.from_maybe(spacing)] - spacing = (spacing * 4)[:4] - - file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace('\\', '_').replace('/', '_')) - key = (filetime, src_color, dst_color, spacing) - key = file_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') - asset_file = key + file_ext - ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets') - asset_path = os.path.join(ASSETS_ROOT, asset_file) - - if os.path.exists(asset_path): - filepath = asset_file - BASE_URL = config.ASSETS_URL - if inline: - path = open(asset_path, 'rb') - url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read()) - else: - url = '%s%s' % (BASE_URL, filepath) - if cache_buster: - filetime = int(os.path.getmtime(asset_path)) - url = add_cache_buster(url, filetime) - else: - simply_process = False - image = None - - if fileext in ('cur',): - simply_process = True - else: - try: - image = Image.open(path) - except IOError: - if not collapse_x and not collapse_y and not dst_colors: - simply_process = True - - if simply_process: - if inline: - url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read()) - else: - url = '%s%s' % (BASE_URL, filepath) - if cache_buster: - filetime = int(os.path.getmtime(asset_path)) - url = add_cache_buster(url, filetime) - else: - width, height = collapse_x or image.size[0], collapse_y or image.size[1] - new_image = Image.new( - mode='RGBA', - size=(width + spacing[1] + spacing[3], height + spacing[0] + spacing[2]), - color=(0, 0, 0, 0) - ) - for i, dst_color in enumerate(dst_colors): - src_color = src_colors[i] - pixdata = image.load() - for _y in xrange(image.size[1]): - for _x in xrange(image.size[0]): - pixel = pixdata[_x, _y] - if pixel[:3] == src_color: - pixdata[_x, _y] = tuple([int(c) for c in dst_color] + [pixel[3] if len(pixel) == 4 else 255]) - iwidth, iheight = image.size - if iwidth != width or iheight != height: - cy = 0 - while cy < iheight: - cx = 0 - while cx < iwidth: - cropped_image = image.crop((cx, cy, cx + width, cy + height)) - new_image.paste(cropped_image, (int(spacing[3]), int(spacing[0])), cropped_image) - cx += width - cy += height - else: - new_image.paste(image, (int(spacing[3]), int(spacing[0]))) - - if not inline: - try: - new_image.save(asset_path) - filepath = asset_file - BASE_URL = config.ASSETS_URL - if cache_buster: - filetime = int(os.path.getmtime(asset_path)) - except IOError: - log.exception("Error while saving image") - inline = True # Retry inline version - url = os.path.join(config.ASSETS_URL.rstrip('/'), asset_file.lstrip('/')) - if cache_buster: - url = add_cache_buster(url, filetime) - if inline: - output = six.BytesIO() - new_image.save(output, format='PNG') - contents = output.getvalue() - output.close() - url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) - else: - url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('/')) - if cache_buster and filetime != 'NA': - url = add_cache_buster(url, filetime) - - if not only_path: - url = 'url(%s)' % escape(url) - return String.unquoted(url) - - -@register('inline-image', 1) -@register('inline-image', 2) -@register('inline-image', 3) -@register('inline-image', 4) -@register('inline-image', 5) -def inline_image(image, mime_type=None, dst_color=None, src_color=None, spacing=None, collapse_x=None, collapse_y=None): - """ - Embeds the contents of a file directly inside your stylesheet, eliminating - the need for another HTTP request. For small files such images or fonts, - this can be a performance benefit at the cost of a larger generated CSS - file. - """ - return _image_url(image, False, False, dst_color, src_color, True, mime_type, spacing, collapse_x, collapse_y) - - -@register('image-url', 1) -@register('image-url', 2) -@register('image-url', 3) -@register('image-url', 4) -@register('image-url', 5) -@register('image-url', 6) -def image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, spacing=None, collapse_x=None, collapse_y=None): - """ - Generates a path to an asset found relative to the project's images - directory. - Passing a true value as the second argument will cause the only the path to - be returned instead of a `url()` function - """ - return _image_url(path, only_path, cache_buster, dst_color, src_color, False, None, spacing, collapse_x, collapse_y) - - -@register('image-width', 1) -def image_width(image): - """ - Returns the width of the image found at the path supplied by `image` - relative to your project's images directory. - """ - if not Image: - raise Exception("Images manipulation require PIL") - filepath = String.unquoted(image).value - path = None - try: - width = _image_size_cache[filepath][0] - except KeyError: - width = 0 - IMAGES_ROOT = _images_root() - if callable(IMAGES_ROOT): - try: - _file, _storage = list(IMAGES_ROOT(filepath))[0] - path = _storage.open(_file) - except: - pass - else: - _path = os.path.join(IMAGES_ROOT, filepath.strip('/')) - if os.path.exists(_path): - path = open(_path, 'rb') - if path: - image = Image.open(path) - size = image.size - width = size[0] - _image_size_cache[filepath] = size - return Number(width, 'px') - - -@register('image-height', 1) -def image_height(image): - """ - Returns the height of the image found at the path supplied by `image` - relative to your project's images directory. - """ - if not Image: - raise Exception("Images manipulation require PIL") - filepath = String.unquoted(image).value - path = None - try: - height = _image_size_cache[filepath][1] - except KeyError: - height = 0 - IMAGES_ROOT = _images_root() - if callable(IMAGES_ROOT): - try: - _file, _storage = list(IMAGES_ROOT(filepath))[0] - path = _storage.open(_file) - except: - pass - else: - _path = os.path.join(IMAGES_ROOT, filepath.strip('/')) - if os.path.exists(_path): - path = open(_path, 'rb') - if path: - image = Image.open(path) - size = image.size - height = size[1] - _image_size_cache[filepath] = size - return Number(height, 'px') diff --git a/libs/scss/functions/compass/layouts.py b/libs/scss/functions/compass/layouts.py deleted file mode 100644 index 06e67c8d..00000000 --- a/libs/scss/functions/compass/layouts.py +++ /dev/null @@ -1,346 +0,0 @@ -"""Functions used for generating packed CSS sprite maps. - - -These are ported from the Binary Tree Bin Packing Algorithm: -http://codeincomplete.com/posts/2011/5/7/bin_packing/ -""" - -# Copyright (c) 2011, 2012, 2013 Jake Gordon and contributors -# Copyright (c) 2013 German M. Bravo - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -class LayoutNode(object): - def __init__(self, x, y, w, h, down=None, right=None, used=False): - self.x = x - self.y = y - self.w = w - self.h = h - self.down = down - self.right = right - self.used = used - self.width = 0 - self.height = 0 - - @property - def area(self): - return self.width * self.height - - def __repr__(self): - return '<%s (%s, %s) [%sx%s]>' % (self.__class__.__name__, self.x, self.y, self.w, self.h) - - -class SpritesLayout(object): - def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None): - self.num_blocks = len(blocks) - - if margin is None: - margin = [[0] * 4] * self.num_blocks - elif not isinstance(margin, (tuple, list)): - margin = [[margin] * 4] * self.num_blocks - elif not isinstance(margin[0], (tuple, list)): - margin = [margin] * self.num_blocks - - if padding is None: - padding = [[0] * 4] * self.num_blocks - elif not isinstance(padding, (tuple, list)): - padding = [[padding] * 4] * self.num_blocks - elif not isinstance(padding[0], (tuple, list)): - padding = [padding] * self.num_blocks - - if pmargin is None: - pmargin = [[0.0] * 4] * self.num_blocks - elif not isinstance(pmargin, (tuple, list)): - pmargin = [[pmargin] * 4] * self.num_blocks - elif not isinstance(pmargin[0], (tuple, list)): - pmargin = [pmargin] * self.num_blocks - - if ppadding is None: - ppadding = [[0.0] * 4] * self.num_blocks - elif not isinstance(ppadding, (tuple, list)): - ppadding = [[ppadding] * 4] * self.num_blocks - elif not isinstance(ppadding[0], (tuple, list)): - ppadding = [ppadding] * self.num_blocks - - self.blocks = tuple(( - b[0] + padding[i][3] + padding[i][1] + margin[i][3] + margin[i][1] + int(round(b[0] * (ppadding[i][3] + ppadding[i][1] + pmargin[i][3] + pmargin[i][1]))), - b[1] + padding[i][0] + padding[i][2] + margin[i][0] + margin[i][2] + int(round(b[1] * (ppadding[i][0] + ppadding[i][2] + pmargin[i][0] + pmargin[i][2]))), - b[0], - b[1], - i - ) for i, b in enumerate(blocks)) - - self.margin = margin - self.padding = padding - self.pmargin = pmargin - self.ppadding = ppadding - - -class PackedSpritesLayout(SpritesLayout): - @staticmethod - def MAXSIDE(a, b): - """maxside: Sort pack by maximum sides""" - return cmp(max(b[0], b[1]), max(a[0], a[1])) or cmp(min(b[0], b[1]), min(a[0], a[1])) or cmp(b[1], a[1]) or cmp(b[0], a[0]) - - @staticmethod - def WIDTH(a, b): - """width: Sort pack by width""" - return cmp(b[0], a[0]) or cmp(b[1], a[1]) - - @staticmethod - def HEIGHT(a, b): - """height: Sort pack by height""" - return cmp(b[1], a[1]) or cmp(b[0], a[0]) - - @staticmethod - def AREA(a, b): - """area: Sort pack by area""" - return cmp(b[0] * b[1], a[0] * a[1]) or cmp(b[1], a[1]) or cmp(b[0], a[0]) - - def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, methods=None): - super(PackedSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) - - ratio = 0 - - if methods is None: - methods = (self.MAXSIDE, self.WIDTH, self.HEIGHT, self.AREA) - - for method in methods: - sorted_blocks = sorted( - self.blocks, - cmp=method, - ) - root = LayoutNode( - x=0, - y=0, - w=sorted_blocks[0][0] if sorted_blocks else 0, - h=sorted_blocks[0][1] if sorted_blocks else 0 - ) - - area = 0 - nodes = [None] * self.num_blocks - - for block in sorted_blocks: - w, h, width, height, i = block - node = self._findNode(root, w, h) - if node: - node = self._splitNode(node, w, h) - else: - root = self._growNode(root, w, h) - node = self._findNode(root, w, h) - if node: - node = self._splitNode(node, w, h) - else: - node = None - nodes[i] = node - node.width = width - node.height = height - area += node.area - - this_ratio = area / float(root.w * root.h) - # print method.__doc__, "%g%%" % (this_ratio * 100) - if ratio < this_ratio: - self.root = root - self.nodes = nodes - self.method = method - ratio = this_ratio - if ratio > 0.96: - break - # print self.method.__doc__, "%g%%" % (ratio * 100) - - def __iter__(self): - for i, node in enumerate(self.nodes): - margin, padding = self.margin[i], self.padding[i] - pmargin, ppadding = self.pmargin[i], self.ppadding[i] - cssw = node.width + padding[3] + padding[1] + int(round(node.width * (ppadding[3] + ppadding[1]))) # image width plus padding - cssh = node.height + padding[0] + padding[2] + int(round(node.height * (ppadding[0] + ppadding[2]))) # image height plus padding - cssx = node.x + margin[3] + int(round(node.width * pmargin[3])) - cssy = node.y + margin[0] + int(round(node.height * pmargin[0])) - x = cssx + padding[3] + int(round(node.width * ppadding[3])) - y = cssy + padding[0] + int(round(node.height * ppadding[0])) - yield x, y, node.width, node.height, cssx, cssy, cssw, cssh - - @property - def width(self): - return self.root.w - - @property - def height(self): - return self.root.h - - def _findNode(self, root, w, h): - if root.used: - return self._findNode(root.right, w, h) or self._findNode(root.down, w, h) - elif w <= root.w and h <= root.h: - return root - else: - return None - - def _splitNode(self, node, w, h): - node.used = True - node.down = LayoutNode( - x=node.x, - y=node.y + h, - w=node.w, - h=node.h - h - ) - node.right = LayoutNode( - x=node.x + w, - y=node.y, - w=node.w - w, - h=h - ) - return node - - def _growNode(self, root, w, h): - canGrowDown = w <= root.w - canGrowRight = h <= root.h - - shouldGrowRight = canGrowRight and (root.h >= root.w + w) # attempt to keep square-ish by growing right when height is much greater than width - shouldGrowDown = canGrowDown and (root.w >= root.h + h) # attempt to keep square-ish by growing down when width is much greater than height - - if shouldGrowRight: - return self._growRight(root, w, h) - elif shouldGrowDown: - return self._growDown(root, w, h) - elif canGrowRight: - return self._growRight(root, w, h) - elif canGrowDown: - return self._growDown(root, w, h) - else: - # need to ensure sensible root starting size to avoid this happening - assert False, "Blocks must be properly sorted!" - - def _growRight(self, root, w, h): - root = LayoutNode( - used=True, - x=0, - y=0, - w=root.w + w, - h=root.h, - down=root, - right=LayoutNode( - x=root.w, - y=0, - w=w, - h=root.h - ) - ) - return root - - def _growDown(self, root, w, h): - root = LayoutNode( - used=True, - x=0, - y=0, - w=root.w, - h=root.h + h, - down=LayoutNode( - x=0, - y=root.h, - w=root.w, - h=h - ), - right=root - ) - return root - - -class HorizontalSpritesLayout(SpritesLayout): - def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, position=None): - super(HorizontalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) - - self.width = sum(zip(*self.blocks)[0]) - self.height = max(zip(*self.blocks)[1]) - - if position is None: - position = [0.0] * self.num_blocks - elif not isinstance(position, (tuple, list)): - position = [position] * self.num_blocks - self.position = position - - def __iter__(self): - cx = 0 - for i, block in enumerate(self.blocks): - w, h, width, height, i = block - margin, padding = self.margin[i], self.padding[i] - pmargin, ppadding = self.pmargin[i], self.ppadding[i] - position = self.position[i] - cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding - cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding - cssx = cx + margin[3] + int(round(width * pmargin[3])) # anchored at x - cssy = int(round((self.height - cssh) * position)) # centered vertically - x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding - y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding - yield x, y, width, height, cssx, cssy, cssw, cssh - cx += cssw + margin[3] + margin[1] + int(round(width * (pmargin[3] + pmargin[1]))) - - -class VerticalSpritesLayout(SpritesLayout): - def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, position=None): - super(VerticalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) - - self.width = max(zip(*self.blocks)[0]) - self.height = sum(zip(*self.blocks)[1]) - - if position is None: - position = [0.0] * self.num_blocks - elif not isinstance(position, (tuple, list)): - position = [position] * self.num_blocks - self.position = position - - def __iter__(self): - cy = 0 - for i, block in enumerate(self.blocks): - w, h, width, height, i = block - margin, padding = self.margin[i], self.padding[i] - pmargin, ppadding = self.pmargin[i], self.ppadding[i] - position = self.position[i] - cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding - cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding - cssx = int(round((self.width - cssw) * position)) # centered horizontally - cssy = cy + margin[0] + int(round(height * pmargin[0])) # anchored at y - x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding - y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding - yield x, y, width, height, cssx, cssy, cssw, cssh - cy += cssh + margin[0] + margin[2] + int(round(height * (pmargin[0] + pmargin[2]))) - - -class DiagonalSpritesLayout(SpritesLayout): - def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None): - super(DiagonalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) - self.width = sum(zip(*self.blocks)[0]) - self.height = sum(zip(*self.blocks)[1]) - - def __iter__(self): - cx, cy = 0, 0 - for i, block in enumerate(self.blocks): - w, h, width, height, i = block - margin, padding = self.margin[i], self.padding[i] - pmargin, ppadding = self.pmargin[i], self.ppadding[i] - cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding - cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding - cssx = cx + margin[3] + int(round(width * pmargin[3])) # anchored at x - cssy = cy + margin[0] + int(round(height * pmargin[0])) # anchored at y - x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding - y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding - yield x, y, width, height, cssx, cssy, cssw, cssh - cx += cssw + margin[3] + margin[1] + int(round(width * (pmargin[3] + pmargin[1]))) - cy += cssh + margin[0] + margin[2] + int(round(height * (pmargin[0] + pmargin[2]))) diff --git a/libs/scss/functions/compass/sprites.py b/libs/scss/functions/compass/sprites.py deleted file mode 100644 index 2549cb36..00000000 --- a/libs/scss/functions/compass/sprites.py +++ /dev/null @@ -1,524 +0,0 @@ -"""Functions used for generating CSS sprites. - -These are ported from the Compass sprite library: -http://compass-style.org/reference/compass/utilities/sprites/ -""" - -from __future__ import absolute_import - -import six - -import base64 -import glob -import hashlib -import logging -import os.path -import tempfile -import time - -try: - import cPickle as pickle -except ImportError: - import pickle - -try: - from PIL import Image -except ImportError: - try: - import Image - except: - Image = None - -from six.moves import xrange - -from scss import config -from scss.functions.compass import _image_size_cache -from scss.functions.compass.layouts import PackedSpritesLayout, HorizontalSpritesLayout, VerticalSpritesLayout, DiagonalSpritesLayout -from scss.functions.library import FunctionLibrary -from scss.types import Color, List, Number, String, Boolean -from scss.util import escape - -log = logging.getLogger(__name__) - -MAX_SPRITE_MAPS = 4096 -KEEP_SPRITE_MAPS = int(MAX_SPRITE_MAPS * 0.8) - -COMPASS_SPRITES_LIBRARY = FunctionLibrary() -register = COMPASS_SPRITES_LIBRARY.register - - -# ------------------------------------------------------------------------------ -# Compass-like functionality for sprites and images - -sprite_maps = {} - - -def alpha_composite(im1, im2, offset=None, box=None, opacity=1): - im1size = im1.size - im2size = im2.size - if offset is None: - offset = (0, 0) - if box is None: - box = (0, 0) + im2size - o1x, o1y = offset - o2x, o2y, o2w, o2h = box - width = o2w - o2x - height = o2h - o2y - im1_data = im1.load() - im2_data = im2.load() - for y in xrange(height): - for x in xrange(width): - pos1 = o1x + x, o1y + y - if pos1[0] >= im1size[0] or pos1[1] >= im1size[1]: - continue - pos2 = o2x + x, o2y + y - if pos2[0] >= im2size[0] or pos2[1] >= im2size[1]: - continue - dr, dg, db, da = im1_data[pos1] - sr, sg, sb, sa = im2_data[pos2] - da /= 255.0 - sa /= 255.0 - sa *= opacity - ida = da * (1 - sa) - oa = (sa + ida) - if oa: - pixel = ( - int(round((sr * sa + dr * ida)) / oa), - int(round((sg * sa + dg * ida)) / oa), - int(round((sb * sa + db * ida)) / oa), - int(round(255 * oa)) - ) - else: - pixel = (0, 0, 0, 0) - im1_data[pos1] = pixel - return im1 - - -@register('sprite-map') -def sprite_map(g, **kwargs): - """ - Generates a sprite map from the files matching the glob pattern. - Uses the keyword-style arguments passed in to control the placement. - - $direction - Sprite map layout. Can be `vertical` (default), `horizontal`, `diagonal` or `smart`. - - $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`) - $-position - Position of a given sprite. - - $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`) - $-padding, $-spacing - Padding for a given sprite. - - $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`). - $-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`) - - $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`) - $-dst-color - Source colors for a given sprite. (defaults to `$src-color`) - - $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`). - $collapse-x - Collapses a size for `x`. - $collapse-y - Collapses a size for `y`. - """ - if not Image: - raise Exception("Images manipulation require PIL") - - now_time = time.time() - - g = String(g, quotes=None).value - - if g in sprite_maps: - sprite_maps[glob]['*'] = now_time - elif '..' not in g: # Protect against going to prohibited places... - if callable(config.STATIC_ROOT): - glob_path = g - rfiles = files = sorted(config.STATIC_ROOT(g)) - else: - glob_path = os.path.join(config.STATIC_ROOT, g) - files = glob.glob(glob_path) - files = sorted((f, None) for f in files) - rfiles = [(rf[len(config.STATIC_ROOT):], s) for rf, s in files] - - if not files: - log.error("Nothing found at '%s'", glob_path) - return String.unquoted('') - - map_name = os.path.normpath(os.path.dirname(g)).replace('\\', '_').replace('/', '_') - key = list(zip(*files)[0]) + [repr(kwargs), config.ASSETS_URL] - key = map_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') - asset_file = key + '.png' - ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets') - asset_path = os.path.join(ASSETS_ROOT, asset_file) - cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, asset_file + '.cache') - - inline = Boolean(kwargs.get('inline', False)) - - sprite_map = None - asset = None - file_asset = None - inline_asset = None - if os.path.exists(asset_path) or inline: - try: - save_time, file_asset, inline_asset, sprite_map, sizes = pickle.load(open(cache_path)) - if file_asset: - sprite_maps[file_asset.render()] = sprite_map - if inline_asset: - sprite_maps[inline_asset.render()] = sprite_map - if inline: - asset = inline_asset - else: - asset = file_asset - except: - pass - - if sprite_map: - for file_, storage in files: - if storage is not None: - d_obj = storage.modified_time(file_) - _time = time.mktime(d_obj.timetuple()) - else: - _time = os.path.getmtime(file_) - if save_time < _time: - if _time > now_time: - log.warning("File '%s' has a date in the future (cache ignored)" % file_) - sprite_map = None # Invalidate cached sprite map - break - - if sprite_map is None or asset is None: - cache_buster = Boolean(kwargs.get('cache_buster', True)) - direction = String.unquoted(kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value - repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value - collapse = kwargs.get('collapse', Number(0)) - if isinstance(collapse, List): - collapse_x = int(Number(collapse[0]).value) - collapse_y = int(Number(collapse[-1]).value) - else: - collapse_x = collapse_y = int(Number(collapse).value) - if 'collapse_x' in kwargs: - collapse_x = int(Number(kwargs['collapse_x']).value) - if 'collapse_y' in kwargs: - collapse_y = int(Number(kwargs['collapse_y']).value) - - position = Number(kwargs.get('position', 0)) - if not position.is_simple_unit('%') and position.value > 1: - position = position.value / 100.0 - else: - position = position.value - if position < 0: - position = 0.0 - elif position > 1: - position = 1.0 - - padding = kwargs.get('padding', kwargs.get('spacing', Number(0))) - padding = [int(Number(v).value) for v in List.from_maybe(padding)] - padding = (padding * 4)[:4] - - dst_colors = kwargs.get('dst_color') - dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_colors) if v] - src_colors = kwargs.get('src_color', Color.from_name('black')) - src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_colors)] - len_colors = max(len(dst_colors), len(src_colors)) - dst_colors = (dst_colors * len_colors)[:len_colors] - src_colors = (src_colors * len_colors)[:len_colors] - - def images(f=lambda x: x): - for file_, storage in f(files): - if storage is not None: - _file = storage.open(file_) - else: - _file = file_ - _image = Image.open(_file) - yield _image - - names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files) - - has_dst_colors = False - all_dst_colors = [] - all_src_colors = [] - all_positions = [] - all_paddings = [] - - for name in names: - name = name.replace('-', '_') - - _position = kwargs.get(name + '_position') - if _position is None: - _position = position - else: - _position = Number(_position) - if not _position.is_simple_unit('%') and _position.value > 1: - _position = _position.value / 100.0 - else: - _position = _position.value - if _position < 0: - _position = 0.0 - elif _position > 1: - _position = 1.0 - all_positions.append(_position) - - _padding = kwargs.get(name + '_padding', kwargs.get(name + '_spacing')) - if _padding is None: - _padding = padding - else: - _padding = [int(Number(v).value) for v in List.from_maybe(_padding)] - _padding = (_padding * 4)[:4] - all_paddings.append(_padding) - - _dst_colors = kwargs.get(name + '_dst_color') - if _dst_colors is None: - _dst_colors = dst_colors - if dst_colors: - has_dst_colors = True - else: - has_dst_colors = True - _dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(_dst_colors) if v] - _src_colors = kwargs.get(name + '_src_color', Color.from_name('black')) - if _src_colors is None: - _src_colors = src_colors - else: - _src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(_src_colors)] - _len_colors = max(len(_dst_colors), len(_src_colors)) - _dst_colors = (_dst_colors * _len_colors)[:_len_colors] - _src_colors = (_src_colors * _len_colors)[:_len_colors] - all_dst_colors.append(_dst_colors) - all_src_colors.append(_src_colors) - - sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1]) for i in images()) - - if direction == 'horizontal': - layout = HorizontalSpritesLayout(sizes, all_paddings, position=all_positions) - elif direction == 'vertical': - layout = VerticalSpritesLayout(sizes, all_paddings, position=all_positions) - elif direction == 'diagonal': - layout = DiagonalSpritesLayout(sizes, all_paddings) - elif direction == 'smart': - layout = PackedSpritesLayout(sizes, all_paddings) - else: - raise Exception("Invalid direction %r" % (direction,)) - layout_positions = list(layout) - - new_image = Image.new( - mode='RGBA', - size=(layout.width, layout.height), - color=(0, 0, 0, 0) - ) - - useless_dst_color = has_dst_colors - - offsets_x = [] - offsets_y = [] - for i, image in enumerate(images()): - x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[i] - iwidth, iheight = image.size - - if has_dst_colors: - pixdata = image.load() - for _y in xrange(iheight): - for _x in xrange(iwidth): - pixel = pixdata[_x, _y] - a = pixel[3] if len(pixel) == 4 else 255 - if a: - rgb = pixel[:3] - for j, dst_color in enumerate(all_dst_colors[i]): - if rgb == all_src_colors[i][j]: - new_color = tuple([int(c) for c in dst_color] + [a]) - if pixel != new_color: - pixdata[_x, _y] = new_color - useless_dst_color = False - break - - if iwidth != width or iheight != height: - cy = 0 - while cy < iheight: - cx = 0 - while cx < iwidth: - new_image = alpha_composite(new_image, image, (x, y), (cx, cy, cx + width, cy + height)) - cx += width - cy += height - else: - new_image.paste(image, (x, y)) - offsets_x.append(cssx) - offsets_y.append(cssy) - - if useless_dst_color: - log.warning("Useless use of $dst-color in sprite map for files at '%s' (never used for)" % glob_path) - - filetime = int(now_time) - - if not inline: - try: - new_image.save(asset_path) - url = '%s%s' % (config.ASSETS_URL, asset_file) - if cache_buster: - url += '?_=%s' % filetime - except IOError: - log.exception("Error while saving image") - inline = True - if inline: - output = six.BytesIO() - new_image.save(output, format='PNG') - contents = output.getvalue() - output.close() - mime_type = 'image/png' - url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) - - url = 'url(%s)' % escape(url) - if inline: - asset = inline_asset = List([String.unquoted(url), String.unquoted(repeat)]) - else: - asset = file_asset = List([String.unquoted(url), String.unquoted(repeat)]) - - # Add the new object: - sprite_map = dict(zip(names, zip(sizes, rfiles, offsets_x, offsets_y))) - sprite_map['*'] = now_time - sprite_map['*f*'] = asset_file - sprite_map['*k*'] = key - sprite_map['*n*'] = map_name - sprite_map['*t*'] = filetime - - cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT) - pickle.dump((now_time, file_asset, inline_asset, sprite_map, zip(files, sizes)), cache_tmp) - cache_tmp.close() - os.rename(cache_tmp.name, cache_path) - - # Use the sorted list to remove older elements (keep only 500 objects): - if len(sprite_maps) > MAX_SPRITE_MAPS: - for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]['*'], reverse=True)[KEEP_SPRITE_MAPS:]: - del sprite_maps[a] - log.warning("Exceeded maximum number of sprite maps (%s)" % MAX_SPRITE_MAPS) - sprite_maps[asset.render()] = sprite_map - for file_, size in sizes: - _image_size_cache[file_] = size - # TODO this sometimes returns an empty list, or is never assigned to - return asset - - -@register('sprite-map-name', 1) -def sprite_map_name(map): - """ - Returns the name of a sprite map The name is derived from the folder than - contains the sprites. - """ - map = map.render() - sprite_map = sprite_maps.get(map) - if not sprite_map: - log.error("No sprite map found: %s", map, extra={'stack': True}) - if sprite_map: - return String.unquoted(sprite_map['*n*']) - return String.unquoted('') - - -@register('sprite-file', 2) -def sprite_file(map, sprite): - """ - Returns the relative path (from the images directory) to the original file - used when construction the sprite. This is suitable for passing to the - image_width and image_height helpers. - """ - map = map.render() - sprite_map = sprite_maps.get(map) - sprite_name = String.unquoted(sprite).value - sprite = sprite_map and sprite_map.get(sprite_name) - if not sprite_map: - log.error("No sprite map found: %s", map, extra={'stack': True}) - elif not sprite: - log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) - if sprite: - return String(sprite[1][0]) - return String.unquoted('') - - -@register('sprites', 1) -@register('sprite-names', 1) -def sprites(map): - map = map.render() - sprite_map = sprite_maps.get(map, {}) - return List(list(String.unquoted(s) for s in sorted(s for s in sprite_map if not s.startswith('*')))) - - -@register('sprite', 2) -@register('sprite', 3) -@register('sprite', 4) -@register('sprite', 5) -def sprite(map, sprite, offset_x=None, offset_y=None, cache_buster=True): - """ - Returns the image and background position for use in a single shorthand - property - """ - map = map.render() - sprite_map = sprite_maps.get(map) - sprite_name = String.unquoted(sprite).value - sprite = sprite_map and sprite_map.get(sprite_name) - if not sprite_map: - log.error("No sprite map found: %s", map, extra={'stack': True}) - elif not sprite: - log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) - if sprite: - url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*']) - if cache_buster: - url += '?_=%s' % sprite_map['*t*'] - x = Number(offset_x or 0, 'px') - y = Number(offset_y or 0, 'px') - if not x.value or (x.value <= -1 or x.value >= 1) and not x.is_simple_unit('%'): - x -= Number(sprite[2], 'px') - if not y.value or (y.value <= -1 or y.value >= 1) and not y.is_simple_unit('%'): - y -= Number(sprite[3], 'px') - url = "url(%s)" % escape(url) - return List([String.unquoted(url), x, y]) - return List([Number(0), Number(0)]) - - -@register('sprite-url', 1) -@register('sprite-url', 2) -def sprite_url(map, cache_buster=True): - """ - Returns a url to the sprite image. - """ - map = map.render() - sprite_map = sprite_maps.get(map) - if not sprite_map: - log.error("No sprite map found: %s", map, extra={'stack': True}) - if sprite_map: - url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*']) - if cache_buster: - url += '?_=%s' % sprite_map['*t*'] - url = "url(%s)" % escape(url) - return String.unquoted(url) - return String.unquoted('') - - -@register('sprite-position', 2) -@register('sprite-position', 3) -@register('sprite-position', 4) -def sprite_position(map, sprite, offset_x=None, offset_y=None): - """ - Returns the position for the original image in the sprite. - This is suitable for use as a value to background-position. - """ - map = map.render() - sprite_map = sprite_maps.get(map) - sprite_name = String.unquoted(sprite).value - sprite = sprite_map and sprite_map.get(sprite_name) - if not sprite_map: - log.error("No sprite map found: %s", map, extra={'stack': True}) - elif not sprite: - log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) - if sprite: - x = None - if offset_x is not None and not isinstance(offset_x, Number): - x = offset_x - if not x or x.value not in ('left', 'right', 'center'): - if x: - offset_x = None - x = Number(offset_x or 0, 'px') - if not x.value or (x.value <= -1 or x.value >= 1) and not x.is_simple_unit('%'): - x -= Number(sprite[2], 'px') - y = None - if offset_y is not None and not isinstance(offset_y, Number): - y = offset_y - if not y or y.value not in ('top', 'bottom', 'center'): - if y: - offset_y = None - y = Number(offset_y or 0, 'px') - if not y.value or (y.value <= -1 or y.value >= 1) and not y.is_simple_unit('%'): - y -= Number(sprite[3], 'px') - return List([x, y]) - return List([Number(0), Number(0)]) diff --git a/libs/scss/functions/core.py b/libs/scss/functions/core.py deleted file mode 100644 index e916acae..00000000 --- a/libs/scss/functions/core.py +++ /dev/null @@ -1,784 +0,0 @@ -"""Functions from the Sass "standard library", i.e., built into the original -Ruby implementation. -""" - -from __future__ import absolute_import -from __future__ import division - -import logging -import math - -from six.moves import xrange - -from scss.functions.library import FunctionLibrary -from scss.types import Boolean, Color, List, Null, Number, String, Map, expect_type - -log = logging.getLogger(__name__) - -CORE_LIBRARY = FunctionLibrary() -register = CORE_LIBRARY.register - - -# ------------------------------------------------------------------------------ -# Color creation - -def _interpret_percentage(n, relto=1., clamp=True): - expect_type(n, Number, unit='%') - - if n.is_unitless: - ret = n.value / relto - else: - ret = n.value / 100 - - if clamp: - if ret < 0: - return 0 - elif ret > 1: - return 1 - - return ret - - -@register('rgba', 4) -def rgba(r, g, b, a): - r = _interpret_percentage(r, relto=255) - g = _interpret_percentage(g, relto=255) - b = _interpret_percentage(b, relto=255) - a = _interpret_percentage(a, relto=1) - - return Color.from_rgb(r, g, b, a) - - -@register('rgb', 3) -def rgb(r, g, b, type='rgb'): - return rgba(r, g, b, Number(1.0)) - - -@register('rgba', 1) -@register('rgba', 2) -def rgba2(color, a=None): - if a is None: - alpha = 1 - else: - alpha = _interpret_percentage(a) - - return Color.from_rgb(*color.rgba[:3], alpha=alpha) - - -@register('rgb', 1) -def rgb1(color): - return rgba2(color, a=Number(1)) - - -@register('hsla', 4) -def hsla(h, s, l, a): - return Color.from_hsl( - h.value / 360 % 1, - # Ruby sass treats plain numbers for saturation and lightness as though - # they were percentages, just without the % - _interpret_percentage(s, relto=100), - _interpret_percentage(l, relto=100), - alpha=a.value, - ) - - -@register('hsl', 3) -def hsl(h, s, l): - return hsla(h, s, l, Number(1)) - - -@register('hsla', 1) -@register('hsla', 2) -def hsla2(color, a=None): - return rgba2(color, a) - - -@register('hsl', 1) -def hsl1(color): - return rgba2(color, a=Number(1)) - - -@register('mix', 2) -@register('mix', 3) -def mix(color1, color2, weight=Number(50, "%")): - """ - Mixes together two colors. Specifically, takes the average of each of the - RGB components, optionally weighted by the given percentage. - The opacity of the colors is also considered when weighting the components. - - Specifically, takes the average of each of the RGB components, - optionally weighted by the given percentage. - The opacity of the colors is also considered when weighting the components. - - The weight specifies the amount of the first color that should be included - in the returned color. - 50%, means that half the first color - and half the second color should be used. - 25% means that a quarter of the first color - and three quarters of the second color should be used. - - For example: - - mix(#f00, #00f) => #7f007f - mix(#f00, #00f, 25%) => #3f00bf - mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75) - """ - # This algorithm factors in both the user-provided weight - # and the difference between the alpha values of the two colors - # to decide how to perform the weighted average of the two RGB values. - # - # It works by first normalizing both parameters to be within [-1, 1], - # where 1 indicates "only use color1", -1 indicates "only use color 0", - # and all values in between indicated a proportionately weighted average. - # - # Once we have the normalized variables w and a, - # we apply the formula (w + a)/(1 + w*a) - # to get the combined weight (in [-1, 1]) of color1. - # This formula has two especially nice properties: - # - # * When either w or a are -1 or 1, the combined weight is also that number - # (cases where w * a == -1 are undefined, and handled as a special case). - # - # * When a is 0, the combined weight is w, and vice versa - # - # Finally, the weight of color1 is renormalized to be within [0, 1] - # and the weight of color2 is given by 1 minus the weight of color1. - # - # Algorithm from the Sass project: http://sass-lang.com/ - - p = _interpret_percentage(weight) - - # Scale weight to [-1, 1] - w = p * 2 - 1 - # Compute difference in alpha channels - a = color1.alpha - color2.alpha - - # Weight of first color - if w * a == -1: - # Avoid zero-div case - scaled_weight1 = w - else: - scaled_weight1 = (w + a) / (1 + w * a) - - # Unscale back to [0, 1] and get the weight of the other color - w1 = (scaled_weight1 + 1) / 2 - w2 = 1 - w1 - - # Do the scaling. Note that alpha isn't scaled by alpha, as that wouldn't - # make much sense; it uses the original untwiddled weight, p. - channels = [ - ch1 * w1 + ch2 * w2 - for (ch1, ch2) in zip(color1.rgba[:3], color2.rgba[:3])] - alpha = color1.alpha * p + color2.alpha * (1 - p) - return Color.from_rgb(*channels, alpha=alpha) - - -# ------------------------------------------------------------------------------ -# Color inspection - -@register('red', 1) -def red(color): - r, g, b, a = color.rgba - return Number(r * 255) - - -@register('green', 1) -def green(color): - r, g, b, a = color.rgba - return Number(g * 255) - - -@register('blue', 1) -def blue(color): - r, g, b, a = color.rgba - return Number(b * 255) - - -@register('opacity', 1) -@register('alpha', 1) -def alpha(color): - return Number(color.alpha) - - -@register('hue', 1) -def hue(color): - h, s, l = color.hsl - return Number(h * 360, "deg") - - -@register('saturation', 1) -def saturation(color): - h, s, l = color.hsl - return Number(s * 100, "%") - - -@register('lightness', 1) -def lightness(color): - h, s, l = color.hsl - return Number(l * 100, "%") - - -@register('ie-hex-str', 1) -def ie_hex_str(color): - c = Color(color).value - return String(u'#%02X%02X%02X%02X' % (round(c[3] * 255), round(c[0]), round(c[1]), round(c[2]))) - - -# ------------------------------------------------------------------------------ -# Color modification - -@register('fade-in', 2) -@register('fadein', 2) -@register('opacify', 2) -def opacify(color, amount): - r, g, b, a = color.rgba - return Color.from_rgb( - r, g, b, - alpha=color.alpha + amount.value) - - -@register('fade-out', 2) -@register('fadeout', 2) -@register('transparentize', 2) -def transparentize(color, amount): - r, g, b, a = color.rgba - return Color.from_rgb( - r, g, b, - alpha=color.alpha - amount.value) - - -@register('lighten', 2) -def lighten(color, amount): - return adjust_color(color, lightness=amount) - - -@register('darken', 2) -def darken(color, amount): - return adjust_color(color, lightness=-amount) - - -@register('saturate', 2) -def saturate(color, amount): - return adjust_color(color, saturation=amount) - - -@register('desaturate', 2) -def desaturate(color, amount): - return adjust_color(color, saturation=-amount) - - -@register('greyscale', 1) -def greyscale(color): - h, s, l = color.hsl - return Color.from_hsl(h, 0, l, alpha=color.alpha) - - -@register('grayscale', 1) -def grayscale(color): - if isinstance(color, Number) and color.is_unitless: - # grayscale(n) is a CSS3 filter and should be left intact, but only - # when using the "a" spelling - return String.unquoted("grayscale(%d)" % (color.value,)) - else: - return greyscale(color) - - -@register('spin', 2) -@register('adjust-hue', 2) -def adjust_hue(color, degrees): - h, s, l = color.hsl - delta = degrees.value / 360 - return Color.from_hsl((h + delta) % 1, s, l, alpha=color.alpha) - - -@register('complement', 1) -def complement(color): - h, s, l = color.hsl - return Color.from_hsl((h + 0.5) % 1, s, l, alpha=color.alpha) - - -@register('invert', 1) -def invert(color): - """ - Returns the inverse (negative) of a color. - The red, green, and blue values are inverted, while the opacity is left alone. - """ - r, g, b, a = color.rgba - return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a) - - -@register('adjust-lightness', 2) -def adjust_lightness(color, amount): - return adjust_color(color, lightness=amount) - - -@register('adjust-saturation', 2) -def adjust_saturation(color, amount): - return adjust_color(color, saturation=amount) - - -@register('scale-lightness', 2) -def scale_lightness(color, amount): - return scale_color(color, lightness=amount) - - -@register('scale-saturation', 2) -def scale_saturation(color, amount): - return scale_color(color, saturation=amount) - - -@register('adjust-color') -def adjust_color(color, red=None, green=None, blue=None, hue=None, saturation=None, lightness=None, alpha=None): - do_rgb = red or green or blue - do_hsl = hue or saturation or lightness - if do_rgb and do_hsl: - raise ValueError("Can't adjust both RGB and HSL channels at the same time") - - zero = Number(0) - a = color.alpha + (alpha or zero).value - - if do_rgb: - r, g, b = color.rgba[:3] - channels = [ - current + (adjustment or zero).value / 255 - for (current, adjustment) in zip(color.rgba, (red, green, blue))] - return Color.from_rgb(*channels, alpha=a) - - else: - h, s, l = color.hsl - h = (h + (hue or zero).value / 360) % 1 - s += _interpret_percentage(saturation or zero, relto=100, clamp=False) - l += _interpret_percentage(lightness or zero, relto=100, clamp=False) - return Color.from_hsl(h, s, l, a) - - -def _scale_channel(channel, scaleby): - if scaleby is None: - return channel - - expect_type(scaleby, Number) - if not scaleby.is_simple_unit('%'): - raise ValueError("Expected percentage, got %r" % (scaleby,)) - - factor = scaleby.value / 100 - if factor > 0: - # Add x% of the remaining range, up to 1 - return channel + (1 - channel) * factor - else: - # Subtract x% of the existing channel. We add here because the factor - # is already negative - return channel * (1 + factor) - - -@register('scale-color') -def scale_color(color, red=None, green=None, blue=None, saturation=None, lightness=None, alpha=None): - do_rgb = red or green or blue - do_hsl = saturation or lightness - if do_rgb and do_hsl: - raise ValueError("Can't scale both RGB and HSL channels at the same time") - - scaled_alpha = _scale_channel(color.alpha, alpha) - - if do_rgb: - channels = [ - _scale_channel(channel, scaleby) - for channel, scaleby in zip(color.rgba, (red, green, blue))] - return Color.from_rgb(*channels, alpha=scaled_alpha) - - else: - channels = [ - _scale_channel(channel, scaleby) - for channel, scaleby in zip(color.hsl, (None, saturation, lightness))] - return Color.from_hsl(*channels, alpha=scaled_alpha) - - -@register('change-color') -def change_color(color, red=None, green=None, blue=None, hue=None, saturation=None, lightness=None, alpha=None): - do_rgb = red or green or blue - do_hsl = hue or saturation or lightness - if do_rgb and do_hsl: - raise ValueError("Can't change both RGB and HSL channels at the same time") - - if alpha is None: - alpha = color.alpha - else: - alpha = alpha.value - - if do_rgb: - channels = list(color.rgba[:3]) - if red: - channels[0] = _interpret_percentage(red, relto=255) - if green: - channels[1] = _interpret_percentage(green, relto=255) - if blue: - channels[2] = _interpret_percentage(blue, relto=255) - - return Color.from_rgb(*channels, alpha=alpha) - - else: - channels = list(color.hsl) - if hue: - expect_type(hue, Number, unit=None) - channels[0] = (hue.value / 360) % 1 - # Ruby sass treats plain numbers for saturation and lightness as though - # they were percentages, just without the % - if saturation: - channels[1] = _interpret_percentage(saturation, relto=100) - if lightness: - channels[2] = _interpret_percentage(lightness, relto=100) - - return Color.from_hsl(*channels, alpha=alpha) - - -# ------------------------------------------------------------------------------ -# String functions - -@register('e', 1) -@register('escape', 1) -@register('unquote') -def unquote(*args): - arg = List.from_maybe_starargs(args).maybe() - - if isinstance(arg, String): - return String(arg.value, quotes=None) - else: - return String(arg.render(), quotes=None) - - -@register('quote') -def quote(*args): - arg = List.from_maybe_starargs(args).maybe() - - if isinstance(arg, String): - return String(arg.value, quotes='"') - else: - return String(arg.render(), quotes='"') - - -@register('str-length', 1) -def str_length(string): - expect_type(string, String) - - # nb: can't use `len(string)`, because that gives the Sass list length, - # which is 1 - return Number(len(string.value)) - - -# TODO this and several others should probably also require integers -# TODO and assert that the indexes are valid -@register('str-insert', 3) -def str_insert(string, insert, index): - expect_type(string, String) - expect_type(insert, String) - expect_type(index, Number, unit=None) - - py_index = index.to_python_index(len(string.value), check_bounds=False) - return String( - string.value[:py_index] + - insert.value + - string.value[py_index:], - quotes=string.quotes) - - -@register('str-index', 2) -def str_index(string, substring): - expect_type(string, String) - expect_type(substring, String) - - # 1-based indexing, with 0 for failure - return Number(string.value.find(substring.value) + 1) - - -@register('str-slice', 2) -@register('str-slice', 3) -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: - py_end_at = None - else: - expect_type(end_at, Number, unit=None) - # Endpoint is inclusive, unlike Python - py_end_at = end_at.to_python_index(len(string.value)) + 1 - - return String( - string.value[py_start_at:py_end_at], - quotes=string.quotes) - - -@register('to-upper-case', 1) -def to_upper_case(string): - expect_type(string, String) - - return String(string.value.upper(), quotes=string.quotes) - - -@register('to-lower-case', 1) -def to_lower_case(string): - expect_type(string, String) - - return String(string.value.lower(), quotes=string.quotes) - - -# ------------------------------------------------------------------------------ -# Number functions - -@register('percentage', 1) -def percentage(value): - expect_type(value, Number, unit=None) - return value * Number(100, unit='%') - -CORE_LIBRARY.add(Number.wrap_python_function(abs), 'abs', 1) -CORE_LIBRARY.add(Number.wrap_python_function(round), 'round', 1) -CORE_LIBRARY.add(Number.wrap_python_function(math.ceil), 'ceil', 1) -CORE_LIBRARY.add(Number.wrap_python_function(math.floor), 'floor', 1) - - -# ------------------------------------------------------------------------------ -# 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 -@register('-compass-list-size') -@register('length') -def _length(*lst): - if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)): - lst = lst[0] - return Number(len(lst)) - - -@register('set-nth', 3) -def set_nth(list, n, value): - expect_type(n, Number, unit=None) - - py_n = n.to_python_index(len(list)) - return List( - tuple(list[:py_n]) + (value,) + tuple(list[py_n + 1:]), - use_comma=list.use_comma) - - -# TODO get the compass bit outta here -@register('-compass-nth', 2) -@register('nth', 2) -def nth(lst, n): - """Return the nth item in the list.""" - expect_type(n, (String, Number), unit=None) - - if isinstance(n, String): - if n.value.lower() == 'first': - i = 0 - elif n.value.lower() == 'last': - i = -1 - else: - raise ValueError("Invalid index %r" % (n,)) - else: - # DEVIATION: nth treats lists as circular lists - i = n.to_python_index(len(lst), circular=True) - - return lst[i] - - -@register('join', 2) -@register('join', 3) -def join(lst1, lst2, separator=None): - ret = [] - ret.extend(List.from_maybe(lst1)) - ret.extend(List.from_maybe(lst2)) - - use_comma = __parse_separator(separator, default_from=lst1) - return List(ret, use_comma=use_comma) - - -@register('min') -def min_(*lst): - if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)): - lst = lst[0] - return min(lst) - - -@register('max') -def max_(*lst): - if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)): - lst = lst[0] - return max(lst) - - -@register('append', 2) -@register('append', 3) -def append(lst, val, separator=None): - ret = [] - ret.extend(List.from_maybe(lst)) - ret.append(val) - - use_comma = __parse_separator(separator, default_from=lst) - return List(ret, use_comma=use_comma) - - -@register('index', 2) -def index(lst, val): - for i in xrange(len(lst)): - if lst.value[i] == val: - return Number(i + 1) - return Boolean(False) - - -@register('zip') -def zip_(*lists): - return List( - [List(zipped) for zipped in zip(*lists)], - use_comma=True) - - -# TODO need a way to use "list" as the arg name without shadowing the builtin -@register('list-separator', 1) -def list_separator(list): - if list.use_comma: - return String.unquoted('comma') - else: - return String.unquoted('space') - - -# ------------------------------------------------------------------------------ -# Map functions - -@register('map-get', 2) -def map_get(map, key): - return map.to_dict().get(key, Null()) - - -@register('map-merge', 2) -def map_merge(*maps): - key_order = [] - index = {} - for map in maps: - for key, value in map.to_pairs(): - if key not in index: - key_order.append(key) - - index[key] = value - - pairs = [(key, index[key]) for key in key_order] - return Map(pairs, index=index) - - -@register('map-keys', 1) -def map_keys(map): - return List( - [k for (k, v) in map.to_pairs()], - use_comma=True) - - -@register('map-values', 1) -def map_values(map): - return List( - [v for (k, v) in map.to_pairs()], - use_comma=True) - - -@register('map-has-key', 2) -def map_has_key(map, key): - return Boolean(key in map.to_dict()) - - -# DEVIATIONS: these do not exist in ruby sass - -@register('map-get', 3) -def map_get3(map, key, default): - return map.to_dict().get(key, default) - - -@register('map-get-nested', 2) -@register('map-get-nested', 3) -def map_get_nested3(map, keys, default=Null()): - for key in keys: - map = map.to_dict().get(key, None) - if map is None: - return default - - return map - - -@register('map-merge-deep', 2) -def map_merge_deep(*maps): - pairs = [] - keys = set() - for map in maps: - for key, value in map.to_pairs(): - keys.add(key) - - for key in keys: - values = [map.to_dict().get(key, None) for map in maps] - values = [v for v in values if v is not None] - if all(isinstance(v, Map) for v in values): - pairs.append((key, map_merge_deep(*values))) - else: - pairs.append((key, values[-1])) - - return Map(pairs) - - -# ------------------------------------------------------------------------------ -# Meta functions - -@register('type-of', 1) -def _type_of(obj): # -> bool, number, string, color, list - return String(obj.sass_type_name) - - -@register('unit', 1) -def unit(number): # -> px, em, cm, etc. - numer = '*'.join(sorted(number.unit_numer)) - denom = '*'.join(sorted(number.unit_denom)) - - if denom: - ret = numer + '/' + denom - else: - ret = numer - return String.unquoted(ret) - - -@register('unitless', 1) -def unitless(value): - if not isinstance(value, Number): - raise TypeError("Expected number, got %r" % (value,)) - - return Boolean(value.is_unitless) - - -@register('comparable', 2) -def comparable(number1, number2): - left = number1.to_base_units() - right = number2.to_base_units() - return Boolean( - left.unit_numer == right.unit_numer - and left.unit_denom == right.unit_denom) - - -# ------------------------------------------------------------------------------ -# Miscellaneous - -@register('if', 2) -@register('if', 3) -def if_(condition, if_true, if_false=Null()): - return if_true if condition else if_false diff --git a/libs/scss/functions/extra.py b/libs/scss/functions/extra.py deleted file mode 100644 index da317a64..00000000 --- a/libs/scss/functions/extra.py +++ /dev/null @@ -1,471 +0,0 @@ -"""Functions new to the pyScss library.""" - -from __future__ import absolute_import - -import base64 -import hashlib -import logging -import os.path -import random - -import six -from six.moves import xrange - -from scss import config -from scss.functions.library import FunctionLibrary -from scss.types import Color, Number, String, List -from scss.util import escape - -try: - from PIL import Image, ImageDraw -except ImportError: - try: - import Image - import ImageDraw - except: - Image = None - -log = logging.getLogger(__name__) - -EXTRA_LIBRARY = FunctionLibrary() -register = EXTRA_LIBRARY.register - - -# ------------------------------------------------------------------------------ -# Image stuff -def _image_noise(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, background=None): - if not density: - density = [0.8] - elif not isinstance(density, (tuple, list)): - density = [density] - - if not intensity: - intensity = [0.5] - elif not isinstance(intensity, (tuple, list)): - intensity = [intensity] - - if not color: - color = [(0, 0, 0, 0)] - elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)): - color = [color] - - if not opacity: - opacity = [0.2] - elif not isinstance(opacity, (tuple, list)): - opacity = [opacity] - - if not monochrome: - monochrome = [False] - elif not isinstance(monochrome, (tuple, list)): - monochrome = [monochrome] - - pixels = {} - - if background: - for y in xrange(size): - for x in xrange(size): - ca = float(background[3]) - pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca) - - loops = max(map(len, (density, intensity, color, opacity, monochrome))) - for l in range(loops): - _density = density[l % len(density)] - _intensity = intensity[l % len(intensity)] - _color = color[l % len(color)] - _opacity = opacity[l % len(opacity)] - _monochrome = monochrome[l % len(monochrome)] - _intensity = 1 - _intensity - if _intensity < 0.5: - cx = 255 * _intensity - cm = cx - else: - cx = 255 * (1 - _intensity) - cm = 255 * _intensity - xa = int(cm - cx) - xb = int(cm + cx) - if xa > 0: - xa &= 255 - else: - xa = 0 - if xb > 0: - xb &= 255 - else: - xb = 0 - r, g, b, a = _color - for i in xrange(int(round(_density * size ** 2))): - x = random.randint(1, size) - y = random.randint(1, size) - cc = random.randint(xa, xb) - cr = (cc) * (1 - a) + a * r - cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g - cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b - ca = random.random() * _opacity - ica = 1 - ca - pos = (x - 1, y - 1) - dst = pixels.get(pos, (0, 0, 0, 0)) - src = (cr * ca, cg * ca, cb * ca, ca) - pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica) - - for pos, col in pixels.items(): - ca = col[3] - if ca: - pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255)) - - -def _image_brushed(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, direction=None, spread=None, background=None): - if not density: - density = [0.8] - elif not isinstance(density, (tuple, list)): - density = [density] - - if not intensity: - intensity = [0.5] - elif not isinstance(intensity, (tuple, list)): - intensity = [intensity] - - if not color: - color = [(0, 0, 0, 0)] - elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)): - color = [color] - - if not opacity: - opacity = [0.2] - elif not isinstance(opacity, (tuple, list)): - opacity = [opacity] - - if not monochrome: - monochrome = [False] - elif not isinstance(monochrome, (tuple, list)): - monochrome = [monochrome] - - if not direction: - direction = [0] - elif not isinstance(direction, (tuple, list)): - direction = [direction] - - if not spread: - spread = [0] - elif not isinstance(spread, (tuple, list)): - spread = [spread] - - def ppgen(d): - if d is None: - return - d = d % 4 - if d == 0: - pp = lambda x, y, o: ((x - o) % size, y) - elif d == 1: - pp = lambda x, y, o: ((x - o) % size, (y + x - o) % size) - elif d == 2: - pp = lambda x, y, o: (y, (x - o) % size) - else: - pp = lambda x, y, o: ((x - o) % size, (y - x - o) % size) - return pp - - pixels = {} - - if background: - for y in xrange(size): - for x in xrange(size): - ca = float(background[3]) - pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca) - - loops = max(map(len, (density, intensity, color, opacity, monochrome, direction, spread))) - for l in range(loops): - _density = density[l % len(density)] - _intensity = intensity[l % len(intensity)] - _color = color[l % len(color)] - _opacity = opacity[l % len(opacity)] - _monochrome = monochrome[l % len(monochrome)] - _direction = direction[l % len(direction)] - _spread = spread[l % len(spread)] - _intensity = 1 - _intensity - if _intensity < 0.5: - cx = 255 * _intensity - cm = cx - else: - cx = 255 * (1 - _intensity) - cm = 255 * _intensity - xa = int(cm - cx) - xb = int(cm + cx) - if xa > 0: - xa &= 255 - else: - xa = 0 - if xb > 0: - xb &= 255 - else: - xb = 0 - r, g, b, a = _color - pp = ppgen(_direction) - if pp: - for y in xrange(size): - if _spread and (y + (l % 2)) % _spread: - continue - o = random.randint(1, size) - cc = random.randint(xa, xb) - cr = (cc) * (1 - a) + a * r - cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g - cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b - da = random.randint(0, 255) * _opacity - ip = round((size / 2.0 * _density) / int(1 / _density)) - iq = round((size / 2.0 * (1 - _density)) / int(1 / _density)) - if ip: - i = da / ip - aa = 0 - else: - i = 0 - aa = da - d = 0 - p = ip - for x in xrange(size): - if d == 0: - if p > 0: - p -= 1 - aa += i - else: - d = 1 - q = iq - elif d == 1: - if q > 0: - q -= 1 - else: - d = 2 - p = ip - elif d == 2: - if p > 0: - p -= 1 - aa -= i - else: - d = 3 - q = iq - elif d == 3: - if q > 0: - q -= 1 - else: - d = 0 - p = ip - if aa > 0: - ca = aa / 255.0 - else: - ca = 0.0 - ica = 1 - ca - pos = pp(x, y, o) - dst = pixels.get(pos, (0, 0, 0, 0)) - src = (cr * ca, cg * ca, cb * ca, ca) - pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica) - - for pos, col in pixels.items(): - ca = col[3] - if ca: - pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255)) - - -@register('background-noise', 0) -@register('background-noise', 1) -@register('background-noise', 2) -@register('background-noise', 3) -@register('background-noise', 4) -@register('background-noise', 5) -@register('background-noise', 6) -@register('background-noise', 7) -def background_noise(density=None, opacity=None, size=None, monochrome=False, intensity=(), color=None, background=None, inline=False): - if not Image: - raise Exception("Images manipulation require PIL") - - density = [Number(v).value for v in List.from_maybe(density)] - intensity = [Number(v).value for v in List.from_maybe(intensity)] - color = [Color(v).value for v in List.from_maybe(color) if v] - opacity = [Number(v).value for v in List.from_maybe(opacity)] - - size = int(Number(size).value) if size else 0 - if size < 1 or size > 512: - size = 200 - - monochrome = bool(monochrome) - - background = Color(background).value if background else None - - new_image = Image.new( - mode='RGBA', - size=(size, size) - ) - - pixdata = new_image.load() - _image_noise(pixdata, size, density, intensity, color, opacity, monochrome) - - if not inline: - key = (size, density, intensity, color, opacity, monochrome) - asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size, size) - # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or [])) - asset_file += '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') - asset_file += '.png' - asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file) - try: - new_image.save(asset_path) - except IOError: - log.exception("Error while saving image") - inline = True # Retry inline version - url = '%s%s' % (config.ASSETS_URL, asset_file) - if inline: - output = six.BytesIO() - new_image.save(output, format='PNG') - contents = output.getvalue() - output.close() - url = 'data:image/png;base64,' + base64.b64encode(contents) - - inline = 'url("%s")' % escape(url) - return String.unquoted(inline) - - -@register('background-brushed', 0) -@register('background-brushed', 1) -@register('background-brushed', 2) -@register('background-brushed', 3) -@register('background-brushed', 4) -@register('background-brushed', 5) -@register('background-brushed', 6) -@register('background-brushed', 7) -@register('background-brushed', 8) -@register('background-brushed', 9) -def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=(), spread=(), background=None, inline=False): - if not Image: - raise Exception("Images manipulation require PIL") - - density = [Number(v).value for v in List.from_maybe(density)] - intensity = [Number(v).value for v in List.from_maybe(intensity)] - color = [Color(v).value for v in List.from_maybe(color) if v] - opacity = [Number(v).value for v in List.from_maybe(opacity)] - - size = int(Number(size).value) if size else -1 - if size < 0 or size > 512: - size = 200 - - monochrome = bool(monochrome) - - direction = [Number(v).value for v in List.from_maybe(direction)] - spread = [Number(v).value for v in List.from_maybe(spread)] - - background = Color(background).value if background else None - - new_image = Image.new( - mode='RGBA', - size=(size, size) - ) - - pixdata = new_image.load() - _image_brushed(pixdata, size, density, intensity, color, opacity, monochrome, direction, spread, background) - - if not inline: - key = (size, density, intensity, color, opacity, monochrome, direction, spread, background) - asset_file = 'brushed-%s%sx%s' % ('mono-' if monochrome else '', size, size) - # asset_file += '-[%s][%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []), '-'.join(to_str(s).replace('.', '_') for s in direction or [])) - asset_file += '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') - asset_file += '.png' - asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file) - try: - new_image.save(asset_path) - except IOError: - log.exception("Error while saving image") - inline = True # Retry inline version - url = '%s%s' % (config.ASSETS_URL, asset_file) - if inline: - output = six.BytesIO() - new_image.save(output, format='PNG') - contents = output.getvalue() - output.close() - url = 'data:image/png;base64,' + base64.b64encode(contents) - - inline = 'url("%s")' % escape(url) - return String.unquoted(inline) - - -@register('grid-image', 4) -@register('grid-image', 5) -def _grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False): - if not Image: - raise Exception("Images manipulation require PIL") - if grid_color is None: - grid_color = (120, 170, 250, 15) - else: - c = Color(grid_color).value - grid_color = (c[0], c[1], c[2], int(c[3] * 255.0)) - if baseline_color is None: - baseline_color = (120, 170, 250, 30) - else: - c = Color(baseline_color).value - baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0)) - if background_color is None: - background_color = (0, 0, 0, 0) - else: - c = Color(background_color).value - background_color = (c[0], c[1], c[2], int(c[3] * 255.0)) - _height = int(height) if height >= 1 else int(height * 1000.0) - _width = int(width) if width >= 1 else int(width * 1000.0) - _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0) - _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0) - if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0: - raise ValueError - _full_width = (_left_gutter + _width + _right_gutter) - new_image = Image.new( - mode='RGBA', - size=(_full_width * int(columns), _height), - color=background_color - ) - draw = ImageDraw.Draw(new_image) - for i in range(int(columns)): - draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1), fill=grid_color) - if _height > 1: - draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1), fill=baseline_color) - if not inline: - grid_name = 'grid_' - if left_gutter: - grid_name += str(int(left_gutter)) + '+' - grid_name += str(int(width)) - if right_gutter: - grid_name += '+' + str(int(right_gutter)) - if height and height > 1: - grid_name += 'x' + str(int(height)) - key = (columns, grid_color, baseline_color, background_color) - key = grid_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') - asset_file = key + '.png' - asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file) - try: - new_image.save(asset_path) - except IOError: - log.exception("Error while saving image") - inline = True # Retry inline version - url = '%s%s' % (config.ASSETS_URL, asset_file) - if inline: - output = six.BytesIO() - new_image.save(output, format='PNG') - contents = output.getvalue() - output.close() - url = 'data:image/png;base64,' + base64.b64encode(contents) - inline = 'url("%s")' % escape(url) - return String.unquoted(inline) - - -@register('image-color', 1) -@register('image-color', 2) -@register('image-color', 3) -def image_color(color, width=1, height=1): - if not Image: - raise Exception("Images manipulation require PIL") - w = int(Number(width).value) - h = int(Number(height).value) - if w <= 0 or h <= 0: - raise ValueError - new_image = Image.new( - mode='RGB' if color.alpha == 1 else 'RGBA', - size=(w, h), - color=color.rgba255, - ) - output = six.BytesIO() - new_image.save(output, format='PNG') - contents = output.getvalue() - output.close() - mime_type = 'image/png' - url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) - inline = 'url("%s")' % escape(url) - return String.unquoted(inline) diff --git a/libs/scss/functions/library.py b/libs/scss/functions/library.py deleted file mode 100644 index ce4a45c4..00000000 --- a/libs/scss/functions/library.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Defines `FunctionLibrary`, a class that collects functions to be exposed to -SCSS during compilation. -""" - -# TODO the constant versions of this should be frozen -class FunctionLibrary(object): - """Contains a set of functions to be exposed to SCSS. - - Functions are keyed by both their name _and_ arity; this allows for - function overloading somewhat beyond what Python easily allows, and is - required for several functions in the standard Sass library. - - Functions may also have arbitrary arity, in which case they accept any - number of arguments, though a function of the same name with a specific - arity takes precedence. - """ - - def __init__(self): - self._functions = {} - - def derive(self): - """Returns a new registry object, pre-populated with all the functions - in this registry. - """ - new = type(self)() - new.inherit(self) - return new - - def inherit(self, *other_libraries): - """Import all the functions from the given other libraries into this one. - - Note that existing functions ARE NOT replaced -- which also means that - functions from the first library take precedence over functions from - the second library, etc. - """ - new_functions = dict() - - # dict.update replaces keys; go through the other libraries in reverse, - # so the first one wins - for library in reversed(other_libraries): - new_functions.update(library._functions) - - new_functions.update(self._functions) - self._functions = new_functions - - def register(self, name, argc=None): - """Decorator for adding a function to this library.""" - # XXX: this should allow specifying names of keyword arguments, as - # well. currently none of these functions support kwargs, i think. - # XXX automatically inferring the name and args would be... - # interesting - # XXX also it would be nice if a function which EXISTS but has the - # wrong number of args threw a useful error; atm i think it'll become - # literal (yikes!) - - key = (name, argc) - - def decorator(f): - self._functions[key] = f - return f - - return decorator - - def add(self, function, name, argc=None): - """Add a function to this library imperatively.""" - - key = (name, argc) - self._functions[key] = function - - def lookup(self, name, argc=None): - """Find a function given its name and the number of arguments it takes. - Falls back to a function with the same name that takes any number of - arguments. - """ - # Try the given arity first - key = name, argc - if key in self._functions: - return self._functions[key] - - # Fall back to arbitrary-arity (or KeyError if neither exists) - return self._functions[name, None] diff --git a/libs/scss/rule.py b/libs/scss/rule.py deleted file mode 100644 index 60fec68d..00000000 --- a/libs/scss/rule.py +++ /dev/null @@ -1,524 +0,0 @@ -from __future__ import absolute_import -from __future__ import print_function - -import logging -import six - -from scss.types import Value, Undefined - -log = logging.getLogger(__name__) - -SORTED_SELECTORS = False - -sort = sorted if SORTED_SELECTORS else lambda it: it - - -def normalize_var(name): - if isinstance(name, six.string_types): - return name.replace('_', '-') - else: - log.warn("Variable name doesn't look like a string: %r", name) - return name - - -def extend_unique(seq, more): - """Return a new sequence containing the items in `seq` plus any items in - `more` that aren't already in `seq`, preserving the order of both. - """ - seen = set(seq) - new = [] - for item in more: - if item not in seen: - seen.add(item) - new.append(item) - - return seq + type(seq)(new) - - -class Scope(object): - """Implements Sass variable scoping. - - Similar to `ChainMap`, except that assigning a new value will replace an - existing value, not mask it. - """ - def __init__(self, maps=()): - maps = list(maps) - assert all(isinstance(m, dict) or isinstance(m, type(self)) for m in maps) # Check all passed maps are compatible with the current Scope - self.maps = [dict()] + maps - - def __repr__(self): - return "<%s(%s) at 0x%x>" % (type(self).__name__, ', '.join(repr(map) for map in self.maps), id(self)) - - def __getitem__(self, key): - for map in self.maps: - if key in map: - return map[key] - - raise KeyError(key) - - def __setitem__(self, key, value): - self.set(key, value) - - def __contains__(self, key): - try: - self[key] - except KeyError: - return False - else: - return True - - def keys(self): - # For mapping interface - keys = set() - for map in self.maps: - keys.update(map.keys()) - return list(keys) - - def set(self, key, value, force_local=False): - if not force_local: - for map in self.maps: - if key in map: - if isinstance(map[key], Undefined): - break - map[key] = value - return - - self.maps[0][key] = value - - def new_child(self): - return type(self)(self.maps) - - -class VariableScope(Scope): - pass - - -class FunctionScope(Scope): - def __repr__(self): - return "<%s(%s) at 0x%x>" % (type(self).__name__, ', '.join('[%s]' % ', '.join('%s:%s' % (f, n) for f, n in sorted(map.keys())) for map in self.maps), id(self)) - - -class MixinScope(Scope): - def __repr__(self): - return "<%s(%s) at 0x%x>" % (type(self).__name__, ', '.join('[%s]' % ', '.join('%s:%s' % (f, n) for f, n in sorted(map.keys())) for map in self.maps), id(self)) - - -class ImportScope(Scope): - pass - - -class Namespace(object): - """...""" - _mutable = True - - def __init__(self, variables=None, functions=None, mixins=None, mutable=True): - self._mutable = mutable - - if variables is None: - self._variables = VariableScope() - else: - # TODO parse into sass values once that's a thing, or require them - # all to be - self._variables = VariableScope([variables]) - - if functions is None: - self._functions = FunctionScope() - else: - self._functions = FunctionScope([functions._functions]) - - self._mixins = MixinScope() - - self._imports = ImportScope() - - def _assert_mutable(self): - if not self._mutable: - raise AttributeError("This Namespace instance is immutable") - - @classmethod - def derive_from(cls, *others): - self = cls() - if len(others) == 1: - self._variables = others[0]._variables.new_child() - self._functions = others[0]._functions.new_child() - self._mixins = others[0]._mixins.new_child() - self._imports = others[0]._imports.new_child() - else: - # Note that this will create a 2-dimensional scope where each of - # these scopes is checked first in order. TODO is this right? - self._variables = VariableScope(other._variables for other in others) - self._functions = FunctionScope(other._functions for other in others) - self._mixins = MixinScope(other._mixins for other in others) - self._imports = ImportScope(other._imports for other in others) - return self - - def derive(self): - """Return a new child namespace. All existing variables are still - readable and writeable, but any new variables will only exist within a - new scope. - """ - return type(self).derive_from(self) - - @property - def variables(self): - return dict((k, self._variables[k]) for k in self._variables.keys()) - - def variable(self, name, throw=False): - name = normalize_var(name) - return self._variables[name] - - def set_variable(self, name, value, local_only=False): - self._assert_mutable() - name = normalize_var(name) - if not isinstance(value, Value): - raise TypeError("Expected a Sass type, while setting %s got %r" % (name, value,)) - self._variables.set(name, value, force_local=local_only) - - def has_import(self, import_key): - return import_key in self._imports - - def add_import(self, import_key, parent_import_key, file_and_line): - self._assert_mutable() - if import_key: - imports = [0, parent_import_key, file_and_line] - self._imports[import_key] = imports - - def use_import(self, import_key): - self._assert_mutable() - if import_key and import_key in self._imports: - imports = self._imports[import_key] - imports[0] += 1 - self.use_import(imports[1]) - - def unused_imports(self): - unused = [] - for import_key in self._imports.keys(): - imports = self._imports[import_key] - if not imports[0]: - unused.append((import_key[0], imports[2])) - return unused - - def _get_callable(self, chainmap, name, arity): - name = normalize_var(name) - if arity is not None: - # With explicit arity, try the particular arity before falling back - # to the general case (None) - try: - return chainmap[name, arity] - except KeyError: - pass - - return chainmap[name, None] - - def _set_callable(self, chainmap, name, arity, cb): - name = normalize_var(name) - chainmap[name, arity] = cb - - def mixin(self, name, arity): - return self._get_callable(self._mixins, name, arity) - - def set_mixin(self, name, arity, cb): - self._assert_mutable() - self._set_callable(self._mixins, name, arity, cb) - - def function(self, name, arity): - return self._get_callable(self._functions, name, arity) - - def set_function(self, name, arity, cb): - self._assert_mutable() - self._set_callable(self._functions, name, arity, cb) - - -class SassRule(object): - """At its heart, a CSS rule: combination of a selector and zero or more - properties. But this is Sass, so it also tracks some Sass-flavored - metadata, like `@extend` rules and `@media` nesting. - """ - - def __init__(self, source_file, import_key=None, unparsed_contents=None, - options=None, properties=None, - namespace=None, - lineno=0, extends_selectors=frozenset(), - ancestry=None, - nested=0): - - self.source_file = source_file - self.import_key = import_key - self.lineno = lineno - - self.unparsed_contents = unparsed_contents - self.options = options - self.extends_selectors = extends_selectors - - if namespace is None: - assert False - self.namespace = Namespace() - else: - self.namespace = namespace - - if properties is None: - self.properties = [] - else: - self.properties = properties - - if ancestry is None: - self.ancestry = RuleAncestry() - else: - self.ancestry = ancestry - - self.nested = nested - - self.descendants = 0 - - def __repr__(self): - return "" % ( - self.ancestry, - len(self.properties), - ) - - @property - def selectors(self): - # TEMPORARY - if self.ancestry.headers and self.ancestry.headers[-1].is_selector: - return self.ancestry.headers[-1].selectors - else: - return () - - @property - def file_and_line(self): - """Return the filename and line number where this rule originally - appears, in the form "foo.scss:3". Used for error messages. - """ - return "%s:%d" % (self.source_file.filename, self.lineno) - - @property - def is_empty(self): - """Return whether this rule is considered "empty" -- i.e., has no - contents that should end up in the final CSS. - """ - if self.properties: - # Rules containing CSS properties are never empty - return False - - if not self.descendants: - for header in self.ancestry.headers: - if header.is_atrule and header.directive != '@media': - # At-rules should always be preserved, UNLESS they are @media - # blocks, which are known to be noise if they don't have any - # contents of their own - return False - - return True - - def copy(self): - return type(self)( - source_file=self.source_file, - lineno=self.lineno, - unparsed_contents=self.unparsed_contents, - - options=self.options, - #properties=list(self.properties), - properties=self.properties, - extends_selectors=self.extends_selectors, - #ancestry=list(self.ancestry), - ancestry=self.ancestry, - - namespace=self.namespace.derive(), - nested=self.nested, - ) - - -class RuleAncestry(object): - def __init__(self, headers=()): - self.headers = tuple(headers) - - def __repr__(self): - return "<%s %r>" % (type(self).__name__, self.headers) - - def __len__(self): - return len(self.headers) - - def with_nested_selectors(self, c_selectors): - if self.headers and self.headers[-1].is_selector: - # Need to merge with parent selectors - p_selectors = self.headers[-1].selectors - - new_selectors = [] - for p_selector in p_selectors: - for c_selector in c_selectors: - new_selectors.append(c_selector.with_parent(p_selector)) - - # Replace the last header with the new merged selectors - new_headers = self.headers[:-1] + (BlockSelectorHeader(new_selectors),) - return RuleAncestry(new_headers) - - else: - # Whoops, no parent selectors. Just need to double-check that - # there are no uses of `&`. - for c_selector in c_selectors: - if c_selector.has_parent_reference: - raise ValueError("Can't use parent selector '&' in top-level rules") - - # Add the children as a new header - new_headers = self.headers + (BlockSelectorHeader(c_selectors),) - return RuleAncestry(new_headers) - - def with_more_selectors(self, selectors): - """Return a new ancestry that also matches the given selectors. No - nesting is done. - """ - if self.headers and self.headers[-1].is_selector: - new_selectors = extend_unique( - self.headers[-1].selectors, - selectors) - new_headers = self.headers[:-1] + ( - BlockSelectorHeader(new_selectors),) - return RuleAncestry(new_headers) - else: - new_headers = self.headers + (BlockSelectorHeader(selectors),) - return RuleAncestry(new_headers) - - -class BlockHeader(object): - """...""" - # TODO doc me depending on how UnparsedBlock is handled... - - is_atrule = False - is_scope = False - is_selector = False - - @classmethod - def parse(cls, prop, has_contents=False): - # Simple pre-processing - if prop.startswith('+') and not has_contents: - # Expand '+' at the beginning of a rule as @include. But not if - # there's a block, because that's probably a CSS selector. - # DEVIATION: this is some semi hybrid of Sass and xCSS syntax - prop = '@include ' + prop[1:] - try: - if '(' not in prop or prop.index(':') < prop.index('('): - prop = prop.replace(':', '(', 1) - if '(' in prop: - prop += ')' - except ValueError: - pass - elif prop.startswith('='): - # Expand '=' at the beginning of a rule as @mixin - prop = '@mixin ' + prop[1:] - elif prop.startswith('@prototype '): - # Remove '@prototype ' - # TODO what is @prototype?? - prop = prop[11:] - - # Minor parsing - if prop.startswith('@'): - if prop.lower().startswith('@else if '): - directive = '@else if' - argument = prop[9:] - else: - directive, _, argument = prop.partition(' ') - directive = directive.lower() - - return BlockAtRuleHeader(directive, argument) - else: - if prop.endswith(':') or ': ' in prop: - # Syntax is ": [prop]" -- if the optional prop exists, - # it becomes the first rule with no suffix - scope, unscoped_value = prop.split(':', 1) - scope = scope.rstrip() - unscoped_value = unscoped_value.lstrip() - return BlockScopeHeader(scope, unscoped_value) - else: - return BlockSelectorHeader(prop) - - -class BlockAtRuleHeader(BlockHeader): - is_atrule = True - - def __init__(self, directive, argument): - self.directive = directive - self.argument = argument - - def __repr__(self): - return "<%s %r %r>" % (type(self).__name__, self.directive, self.argument) - - def render(self): - if self.argument: - return "%s %s" % (self.directive, self.argument) - else: - return self.directive - - -class BlockSelectorHeader(BlockHeader): - is_selector = True - - def __init__(self, selectors): - self.selectors = tuple(selectors) - - def __repr__(self): - return "<%s %r>" % (type(self).__name__, self.selectors) - - def render(self, sep=', ', super_selector=''): - return sep.join(sort( - super_selector + s.render() - for s in self.selectors - if not s.has_placeholder)) - - -class BlockScopeHeader(BlockHeader): - is_scope = True - - def __init__(self, scope, unscoped_value): - self.scope = scope - - if unscoped_value: - self.unscoped_value = unscoped_value - else: - self.unscoped_value = None - - -class UnparsedBlock(object): - """A Sass block whose contents have not yet been parsed. - - At the top level, CSS (and Sass) documents consist of a sequence of blocks. - A block may be a ruleset: - - selector { block; block; block... } - - Or it may be an @-rule: - - @rule arguments { block; block; block... } - - Or it may be only a single property declaration: - - property: value - - pyScss's first parsing pass breaks the document into these blocks, and each - block becomes an instance of this class. - """ - - def __init__(self, parent_rule, lineno, prop, unparsed_contents): - self.parent_rule = parent_rule - self.header = BlockHeader.parse(prop, has_contents=bool(unparsed_contents)) - - # Basic properties - self.lineno = lineno - self.prop = prop - self.unparsed_contents = unparsed_contents - - @property - def directive(self): - return self.header.directive - - @property - def argument(self): - return self.header.argument - - ### What kind of thing is this? - - @property - def is_atrule(self): - return self.header.is_atrule - - @property - def is_scope(self): - return self.header.is_scope diff --git a/libs/scss/scss_meta.py b/libs/scss/scss_meta.py deleted file mode 100644 index 481eba4a..00000000 --- a/libs/scss/scss_meta.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -#-*- coding: utf-8 -*- -""" -pyScss, a Scss compiler for Python - -@author German M. Bravo (Kronuz) -@version 1.2.0 alpha -@see https://github.com/Kronuz/pyScss -@copyright (c) 2012-2013 German M. Bravo (Kronuz) -@license MIT License - http://www.opensource.org/licenses/mit-license.php - -pyScss compiles Scss, a superset of CSS that is more powerful, elegant and -easier to maintain than plain-vanilla CSS. The library acts as a CSS source code -preprocesor which allows you to use variables, nested rules, mixins, andhave -inheritance of rules, all with a CSS-compatible syntax which the preprocessor -then compiles to standard CSS. - -Scss, as an extension of CSS, helps keep large stylesheets well-organized. It -borrows concepts and functionality from projects such as OOCSS and other similar -frameworks like as Sass. It's build on top of the original PHP xCSS codebase -structure but it's been completely rewritten, many bugs have been fixed and it -has been extensively extended to support almost the full range of Sass' Scss -syntax and functionality. - -Bits of code in pyScss come from various projects: -Compass: - (c) 2009 Christopher M. Eppstein - http://compass-style.org/ -Sass: - (c) 2006-2009 Hampton Catlin and Nathan Weizenbaum - http://sass-lang.com/ -xCSS: - (c) 2010 Anton Pawlik - http://xcss.antpaw.org/docs/ - - This file defines Meta data, according to PEP314 - (http://www.python.org/dev/peps/pep-0314/) which is common to both pyScss - and setup.py distutils. - - We create this here so this information can be compatible with BOTH - Python 2.x and Python 3.x so setup.py can use it when building pyScss - for both Py3.x and Py2.x - -""" - -VERSION_INFO = (1, 2, 0, 'post3') -DATE_INFO = (2013, 10, 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 + ")" -AUTHOR = "German M. Bravo (Kronuz)" -AUTHOR_EMAIL = 'german.mb@gmail.com' -URL = 'http://github.com/Kronuz/pyScss' -DOWNLOAD_URL = 'http://github.com/Kronuz/pyScss/tarball/v' + VERSION -LICENSE = "MIT" -PROJECT = "pyScss" - -if __name__ == "__main__": - print('VERSION = ' + VERSION) - print('REVISION = ' + REVISION) - print('BUILD_INFO = ' + BUILD_INFO) - print('AUTHOR = ' + AUTHOR) - print('AUTHOR_EMAIL = ' + AUTHOR_EMAIL) - print('URL = ' + URL) - print('LICENSE = ' + LICENSE) - print('PROJECT = ' + PROJECT) diff --git a/libs/scss/selector.py b/libs/scss/selector.py deleted file mode 100644 index ebaed3c0..00000000 --- a/libs/scss/selector.py +++ /dev/null @@ -1,607 +0,0 @@ -from __future__ import print_function - -import re - -# Super dumb little selector parser. - -# Yes, yes, this is a regex tokenizer. The actual meaning of the -# selector doesn't matter; the parts are just important for matching up -# during @extend. - -# Selectors have three levels: simple, combinator, comma-delimited. -# Each combinator can only appear once as a delimiter between simple -# selectors, so it can be thought of as a prefix. -# So this: -# a.b + c, d#e -# parses into two Selectors with these structures: -# [[' ', 'a', '.b'], ['+', 'c']] -# [[' ', 'd', '#e']] -# Note that the first simple selector has an implied descendant -# combinator -- i.e., it is a descendant of the root element. -# TODO `*html` is incorrectly parsed as a single selector -# TODO this oughta be touched up for css4 selectors -SELECTOR_TOKENIZER = re.compile(r''' - # Colons introduce pseudo-selectors, sometimes with parens - # TODO doesn't handle quoted ) - [:]+ [-\w]+ (?: [(] .+? [)] )? - - # These guys are combinators -- note that a single space counts too - | \s* [ +>~,] \s* - - # Square brackets are attribute tests - # TODO: this doesn't handle ] within a string - | [[] .+? []] - - # Dot and pound start class/id selectors. Percent starts a Sass - # extend-target faux selector. - | [.#%] [-\w]+ - - # Percentages are used for @keyframes - | [-.\d]+ [%] - - # Plain identifiers, or single asterisks, are element names - | [-\w]+ - | [*] - - # & is the sass replacement token - | [&] - - # And as a last-ditch effort, just eat up to whitespace - | (\S+) -''', re.VERBOSE | re.MULTILINE) - - -# Maps the first character of a token to a rough ordering. The default -# (element names) is zero. -TOKEN_TYPE_ORDER = { - '#': 2, - '.': 3, - '[': 3, - ':': 3, - '%': 4, -} -TOKEN_SORT_KEY = lambda token: TOKEN_TYPE_ORDER.get(token[0], 0) - - -def _is_combinator_subset_of(specific, general, is_first=True): - """Return whether `specific` matches a non-strict subset of what `general` - matches. - """ - if is_first and general == ' ': - # First selector always has a space to mean "descendent of root", which - # still holds if any other selector appears above it - return True - - if specific == general: - return True - - if specific == '>' and general == ' ': - return True - - if specific == '+' and general == '~': - return True - - return False - - -class SimpleSelector(object): - """A simple selector, by CSS 2.1 terminology: a combination of element - name, class selectors, id selectors, and other criteria that all apply to a - single element. - - Note that CSS 3 considers EACH of those parts to be a "simple selector", - and calls a group of them a "sequence of simple selectors". That's a - terrible class name, so we're going with 2.1 here. - - For lack of a better name, each of the individual parts is merely called a - "token". - """ - def __init__(self, combinator, tokens): - self.combinator = combinator - # TODO enforce that only one element name (including *) appears in a - # selector - # TODO remove duplicates - self.tokens = tuple(sorted(tokens, key=TOKEN_SORT_KEY)) - - def __repr__(self): - return "<%s: %r>" % (type(self).__name__, self.render()) - - def __hash__(self): - return hash((self.combinator, self.tokens)) - - def __eq__(self, other): - if not isinstance(other, SimpleSelector): - return NotImplemented - - return ( - self.combinator == other.combinator and - self.tokens == other.tokens) - - @property - def has_parent_reference(self): - return '&' in self.tokens or 'self' in self.tokens - - @property - def has_placeholder(self): - return any( - token[0] == '%' - for token in self.tokens) - - def is_superset_of(self, other, soft_combinator=False): - """Return True iff this selector matches the same elements as `other`, - and perhaps others. - - That is, ``.foo`` is a superset of ``.foo.bar``, because the latter is - more specific. - - Set `soft_combinator` true to ignore the specific case of this selector - having a descendent combinator and `other` having anything else. This - is for superset checking for ``@extend``, where a space combinator - really means "none". - """ - # Combinators must match, OR be compatible -- space is a superset of >, - # ~ is a superset of + - if soft_combinator and self.combinator == ' ': - combinator_superset = True - else: - combinator_superset = ( - self.combinator == other.combinator or - (self.combinator == ' ' and other.combinator == '>') or - (self.combinator == '~' and other.combinator == '+')) - - return ( - combinator_superset and - set(self.tokens) <= set(other.tokens)) - - def replace_parent(self, parent_simples): - """If ``&`` (or the legacy xCSS equivalent ``self``) appears in this - selector, replace it with the given iterable of parent selectors. - - Returns a tuple of simple selectors. - """ - assert parent_simples - - ancestors = parent_simples[:-1] - parent = parent_simples[-1] - - did_replace = False - new_tokens = [] - for token in self.tokens: - if not did_replace and token in ('&', 'self'): - did_replace = True - new_tokens.extend(parent.tokens) - else: - new_tokens.append(token) - - if not did_replace: - # This simple selector doesn't contain a parent reference so just - # stick it on the end - return parent_simples + (self,) - - # This simple selector was merged into the direct parent. - merged_self = type(self)(parent.combinator, new_tokens) - selector = ancestors + (merged_self,) - # Our combinator goes on the first ancestor, i.e., substituting "foo - # bar baz" into "+ &.quux" produces "+ foo bar baz.quux". This means a - # potential conflict with the first ancestor's combinator! - root = selector[0] - if not _is_combinator_subset_of(self.combinator, root.combinator): - raise ValueError( - "Can't sub parent {0!r} into {1!r}: " - "combinators {2!r} and {3!r} conflict!" - .format( - parent_simples, self, self.combinator, root.combinator)) - - root = type(self)(self.combinator, root.tokens) - selector = (root,) + selector[1:] - return tuple(selector) - - # TODO just use set ops for these, once the constructor removes dupes - def merge_with(self, other): - new_tokens = self.tokens + tuple(token for token in other.tokens if token not in set(self.tokens)) - return type(self)(self.combinator, new_tokens) - - def difference(self, other): - new_tokens = tuple(token for token in self.tokens if token not in set(other.tokens)) - return type(self)(self.combinator, new_tokens) - - 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 - - -class Selector(object): - """A single CSS selector.""" - - def __init__(self, simples): - """Return a selector containing a sequence of `SimpleSelector`s. - - You probably want to use `parse_many` or `parse_one` instead. - """ - # TODO enforce uniqueness - self.simple_selectors = tuple(simples) - - @classmethod - def parse_many(cls, selector): - selector = selector.strip() - ret = [] - - pending = dict( - simples=[], - combinator=' ', - tokens=[], - ) - - def promote_simple(): - if pending['tokens']: - pending['simples'].append( - SimpleSelector(pending['combinator'], pending['tokens'])) - pending['combinator'] = ' ' - pending['tokens'] = [] - - def promote_selector(): - promote_simple() - if pending['simples']: - ret.append(cls(pending['simples'])) - pending['simples'] = [] - - pos = 0 - while pos < len(selector): - # TODO i don't think this deals with " + " correctly. anywhere. - # TODO this used to turn "1.5%" into empty string; why does error - # not work? - m = SELECTOR_TOKENIZER.match(selector, pos) - if not m: - # TODO prettify me - raise SyntaxError("Couldn't parse selector: %r" % (selector,)) - - token = m.group(0) - pos += len(token) - - # Kill any extraneous space, BUT make sure not to turn a lone space - # into an empty string - token = token.strip() or ' ' - - if token == ',': - # End current selector - # TODO what about "+ ,"? what do i even do with that - promote_selector() - elif token in ' +>~': - # End current simple selector - promote_simple() - pending['combinator'] = token - else: - # Add to pending simple selector - pending['tokens'].append(token) - - # Deal with any remaining pending bits - promote_selector() - - return ret - - @classmethod - def parse_one(cls, selector_string): - selectors = cls.parse_many(selector_string) - if len(selectors) != 1: - # TODO better error - raise ValueError - - return selectors[0] - - def __repr__(self): - return "<%s: %r>" % (type(self).__name__, self.render()) - - def __hash__(self): - return hash(self.simple_selectors) - - def __eq__(self, other): - if not isinstance(other, Selector): - return NotImplemented - - return self.simple_selectors == other.simple_selectors - - @property - def has_parent_reference(self): - return any( - simple.has_parent_reference - for simple in self.simple_selectors) - - @property - def has_placeholder(self): - return any( - simple.has_placeholder - for simple in self.simple_selectors) - - def with_parent(self, parent): - saw_parent_ref = False - - new_simples = [] - for simple in self.simple_selectors: - if simple.has_parent_reference: - new_simples.extend(simple.replace_parent(parent.simple_selectors)) - saw_parent_ref = True - else: - new_simples.append(simple) - - if not saw_parent_ref: - new_simples = parent.simple_selectors + tuple(new_simples) - - return type(self)(new_simples) - - def lookup_key(self): - """Build a key from the "important" parts of a selector: elements, - classes, ids. - """ - parts = set() - for node in self.simple_selectors: - for token in node.tokens: - if token[0] not in ':[': - parts.add(token) - - if not parts: - # Should always have at least ONE key; selectors with no elements, - # no classes, and no ids can be indexed as None to avoid a scan of - # every selector in the entire document - parts.add(None) - - return frozenset(parts) - - def is_superset_of(self, other): - assert isinstance(other, Selector) - - idx = 0 - for other_node in other.simple_selectors: - if idx >= len(self.simple_selectors): - return False - - while idx < len(self.simple_selectors): - node = self.simple_selectors[idx] - idx += 1 - - if node.is_superset_of(other_node): - break - - return True - - def substitute(self, target, replacement): - """Return a list of selectors obtained by replacing the `target` - selector with `replacement`. - - Herein lie the guts of the Sass @extend directive. - - In general, for a selector ``a X b Y c``, a target ``X Y``, and a - replacement ``q Z``, return the selectors ``a q X b Z c`` and ``q a X b - Z c``. Note in particular that no more than two selectors will be - returned, and the permutation of ancestors will never insert new simple - selectors "inside" the target selector. - """ - - # Find the target in the parent selector, and split it into - # before/after - p_before, p_extras, p_after = self.break_around(target.simple_selectors) - - # The replacement has no hinge; it only has the most specific simple - # selector (which is the part that replaces "self" in the parent) and - # whatever preceding simple selectors there may be - r_trail = replacement.simple_selectors[:-1] - r_extras = replacement.simple_selectors[-1] - - # TODO what if the prefix doesn't match? who wins? should we even get - # this far? - focal_nodes = (p_extras.merge_with(r_extras),) - - befores = _merge_selectors(p_before, r_trail) - - cls = type(self) - return [ - cls(before + focal_nodes + p_after) - for before in befores] - - def break_around(self, hinge): - """Given a simple selector node contained within this one (a "hinge"), - break it in half and return a parent selector, extra specifiers for the - hinge, and a child selector. - - That is, given a hinge X, break the selector A + X.y B into A, + .y, - and B. - """ - hinge_start = hinge[0] - for i, node in enumerate(self.simple_selectors): - # In this particular case, a ' ' combinator actually means "no" (or - # any) combinator, so it should be ignored - if hinge_start.is_superset_of(node, soft_combinator=True): - start_idx = i - break - else: - raise ValueError( - "Couldn't find hinge %r in compound selector %r" % - (hinge_start, self.simple_selectors)) - - for i, hinge_node in enumerate(hinge): - if i == 0: - # We just did this - continue - - self_node = self.simple_selectors[start_idx + i] - if hinge_node.is_superset_of(self_node): - continue - - # TODO this isn't true; consider finding `a b` in `a c a b` - raise ValueError( - "Couldn't find hinge %r in compound selector %r" % - (hinge_node, self.simple_selectors)) - - end_idx = start_idx + len(hinge) - 1 - - focal_node = self.simple_selectors[end_idx] - extras = focal_node.difference(hinge[-1]) - - return ( - self.simple_selectors[:start_idx], - extras, - self.simple_selectors[end_idx + 1:]) - - def render(self): - return ' '.join(simple.render() for simple in self.simple_selectors) - - -def _merge_selectors(left, right): - """Given two selector chains (lists of simple selectors), return a list of - selector chains representing elements matched by both of them. - - This operation is not exact, and involves some degree of fudging -- the - wackier and more divergent the input, the more fudging. It's meant to be - what a human might expect rather than a precise covering of all possible - cases. Most notably, when the two input chains have absolutely nothing in - common, the output is merely ``left + right`` and ``right + left`` rather - than all possible interleavings. - """ - - if not left or not right: - # At least one is empty, so there are no conflicts; just return - # whichever isn't empty. Remember to return a LIST, though - return [left or right] - - lcs = longest_common_subsequence(left, right, _merge_simple_selectors) - - ret = [()] # start with a dummy empty chain or weaving won't work - - left_last = 0 - right_last = 0 - for left_next, right_next, merged in lcs: - ret = _weave_conflicting_selectors( - ret, - left[left_last:left_next], - right[right_last:right_next], - (merged,)) - - left_last = left_next + 1 - right_last = right_next + 1 - - ret = _weave_conflicting_selectors( - ret, - left[left_last:], - right[right_last:]) - - return ret - - -def _weave_conflicting_selectors(prefixes, a, b, suffix=()): - """Part of the selector merge algorithm above. Not useful on its own. Pay - no attention to the man behind the curtain. - """ - # OK, what this actually does: given a list of selector chains, two - # "conflicting" selector chains, and an optional suffix, return a new list - # of chains like this: - # prefix[0] + a + b + suffix, - # prefix[0] + b + a + suffix, - # prefix[1] + a + b + suffix, - # ... - # In other words, this just appends a new chain to each of a list of given - # chains, except that the new chain might be the superposition of two - # other incompatible chains. - both = a and b - for prefix in prefixes: - yield prefix + a + b + suffix - if both: - # Only use both orderings if there's an actual conflict! - yield prefix + b + a + suffix - - -def _merge_simple_selectors(a, b): - """Merge two simple selectors, for the purposes of the LCS algorithm below. - - In practice this returns the more specific selector if one is a subset of - the other, else it returns None. - """ - # TODO what about combinators - if a.is_superset_of(b): - return b - elif b.is_superset_of(a): - return a - else: - return None - - -def longest_common_subsequence(a, b, mergefunc=None): - """Find the longest common subsequence between two iterables. - - The longest common subsequence is the core of any diff algorithm: it's the - longest sequence of elements that appears in both parent sequences in the - same order, but NOT necessarily consecutively. - - Original algorithm borrowed from Wikipedia: - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Code_for_the_dynamic_programming_solution - - This function is used only to implement @extend, largely because that's - what the Ruby implementation does. Thus it's been extended slightly from - the simple diff-friendly algorithm given above. - - What @extend wants to know is whether two simple selectors are compatible, - not just equal. To that end, you must pass in a "merge" function to - compare a pair of elements manually. It should return `None` if they are - incompatible, and a MERGED element if they are compatible -- in the case of - selectors, this is whichever one is more specific. - - Because of this fuzzier notion of equality, the return value is a list of - ``(a_index, b_index, value)`` tuples rather than items alone. - """ - if mergefunc is None: - # Stupid default, just in case - def mergefunc(a, b): - if a == b: - return a - return None - - # Precalculate equality, since it can be a tad expensive and every pair is - # compared at least once - eq = {} - for ai, aval in enumerate(a): - for bi, bval in enumerate(b): - eq[ai, bi] = mergefunc(aval, bval) - - # Build the "length" matrix, which provides the length of the LCS for - # arbitrary-length prefixes. -1 exists only to support the base case - prefix_lcs_length = {} - for ai in range(-1, len(a)): - for bi in range(-1, len(b)): - if ai == -1 or bi == -1: - l = 0 - elif eq[ai, bi]: - l = prefix_lcs_length[ai - 1, bi - 1] + 1 - else: - l = max( - prefix_lcs_length[ai, bi - 1], - prefix_lcs_length[ai - 1, bi]) - - prefix_lcs_length[ai, bi] = l - - # The interesting part. The key insight is that the bottom-right value in - # the length matrix must be the length of the LCS because of how the matrix - # is defined, so all that's left to do is backtrack from the ends of both - # sequences in whatever way keeps the LCS as long as possible, and keep - # track of the equal pairs of elements we see along the way. - # Wikipedia does this with recursion, but the algorithm is trivial to - # rewrite as a loop, as below. - ai = len(a) - 1 - bi = len(b) - 1 - - ret = [] - while ai >= 0 and bi >= 0: - merged = eq[ai, bi] - if merged is not None: - ret.append((ai, bi, merged)) - ai -= 1 - bi -= 1 - elif prefix_lcs_length[ai, bi - 1] > prefix_lcs_length[ai - 1, bi]: - bi -= 1 - else: - ai -= 1 - - # ret has the latest items first, which is backwards - ret.reverse() - return ret diff --git a/libs/scss/setup.py b/libs/scss/setup.py deleted file mode 100644 index 7112a187..00000000 --- a/libs/scss/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -from distutils.core import setup, Extension - -setup(name='pyScss', - version='1.1.1', - description='pyScss', - ext_modules=[ - Extension( - '_scss', - sources=['src/_scss.c', 'src/block_locator.c', 'src/scanner.c'], - libraries=['pcre'], - optional=True - ) - ] -) diff --git a/libs/scss/src/_speedups.c b/libs/scss/src/_speedups.c deleted file mode 100644 index b4b990cf..00000000 --- a/libs/scss/src/_speedups.c +++ /dev/null @@ -1,554 +0,0 @@ -/* -* pyScss, a Scss compiler for Python -* SCSS blocks scanner. -* -* German M. Bravo (Kronuz) -* https://github.com/Kronuz/pyScss -* -* MIT license (http://www.opensource.org/licenses/mit-license.php) -* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. -*/ -#include -#include "block_locator.h" -#include "scanner.h" - -/* BlockLocator */ -staticforward PyTypeObject scss_BlockLocatorType; - -typedef struct { - PyObject_HEAD - BlockLocator *locator; -} scss_BlockLocator; - -static int -scss_BlockLocator_init(scss_BlockLocator *self, PyObject *args, PyObject *kwds) -{ - char *codestr; - int codestr_sz; - - self->locator = NULL; - - if (!PyArg_ParseTuple(args, "s#", &codestr, &codestr_sz)) { - return -1; - } - - self->locator = BlockLocator_new(codestr, codestr_sz); - - #ifdef DEBUG - PySys_WriteStderr("Scss BlockLocator object initialized! (%lu bytes)\n", sizeof(scss_BlockLocator)); - #endif - - return 0; -} - -static void -scss_BlockLocator_dealloc(scss_BlockLocator *self) -{ - if (self->locator != NULL) BlockLocator_del(self->locator); - - self->ob_type->tp_free((PyObject*)self); - - #ifdef DEBUG - PySys_WriteStderr("Scss BlockLocator object destroyed!\n"); - #endif -} - -scss_BlockLocator* -scss_BlockLocator_iter(scss_BlockLocator *self) -{ - Py_INCREF(self); - return self; -} - -PyObject* -scss_BlockLocator_iternext(scss_BlockLocator *self) -{ - Block *block; - - if (self->locator != NULL) { - block = BlockLocator_iternext(self->locator); - - if (block->error > 0) { - return Py_BuildValue( - "is#s#", - block->lineno, - block->selprop, - block->selprop_sz, - block->codestr, - block->codestr_sz - ); - } - - if (block->error > 0) { - PyErr_SetString(PyExc_Exception, self->locator->exc); - return NULL; - } - } - - /* Raising of standard StopIteration exception with empty value. */ - PyErr_SetNone(PyExc_StopIteration); - return NULL; -} - -/* Type definition */ - -static PyTypeObject scss_BlockLocatorType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "scss._BlockLocator", /* tp_name */ - sizeof(scss_BlockLocator), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)scss_BlockLocator_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ - "Internal BlockLocator iterator object.", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - (getiterfunc)scss_BlockLocator_iter, /* tp_iter: __iter__() method */ - (iternextfunc)scss_BlockLocator_iternext, /* tp_iternext: next() method */ - 0, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)scss_BlockLocator_init, /* tp_init */ -}; - - -/* Scanner */ -static PyObject *PyExc_scss_NoMoreTokens; - -staticforward PyTypeObject scss_ScannerType; - -typedef struct { - PyObject_HEAD - Scanner *scanner; -} scss_Scanner; - - -static PyObject * -scss_Scanner_rewind(scss_Scanner *self, PyObject *args) -{ - int token_num; - if (self->scanner != NULL) { - if (PyArg_ParseTuple(args, "i", &token_num)) { - Scanner_rewind(self->scanner, token_num); - } - } - Py_INCREF(Py_None); - return (PyObject *)Py_None; -} - - -static PyObject * -scss_Scanner_token(scss_Scanner *self, PyObject *args) -{ - PyObject *iter; - PyObject *item; - long size; - - Token *p_token; - - int token_num; - PyObject *restrictions = NULL; - Pattern *_restrictions = NULL; - int restrictions_sz = 0; - if (self->scanner != NULL) { - if (PyArg_ParseTuple(args, "i|O", &token_num, &restrictions)) { - if (restrictions != NULL) { - size = PySequence_Size(restrictions); - if (size != -1) { - _restrictions = PyMem_New(Pattern, size); - iter = PyObject_GetIter(restrictions); - while ((item = PyIter_Next(iter))) { - if (PyString_Check(item)) { - _restrictions[restrictions_sz].tok = PyString_AsString(item); - _restrictions[restrictions_sz].expr = NULL; - restrictions_sz++; - } - Py_DECREF(item); - } - Py_DECREF(iter); - } - } - p_token = Scanner_token(self->scanner, token_num, _restrictions, restrictions_sz); - - if (_restrictions != NULL) PyMem_Del(_restrictions); - - if (p_token == (Token *)SCANNER_EXC_BAD_TOKEN) { - PyErr_SetString(PyExc_SyntaxError, self->scanner->exc); - return NULL; - } - if (p_token == (Token *)SCANNER_EXC_RESTRICTED) { - PyErr_SetString(PyExc_SyntaxError, self->scanner->exc); - return NULL; - } - if (p_token == (Token *)SCANNER_EXC_UNIMPLEMENTED) { - PyErr_SetString(PyExc_NotImplementedError, self->scanner->exc); - return NULL; - } - if (p_token == (Token *)SCANNER_EXC_NO_MORE_TOKENS) { - PyErr_SetNone(PyExc_scss_NoMoreTokens); - return NULL; - } - if (p_token < 0) { - PyErr_SetNone(PyExc_Exception); - return NULL; - } - return Py_BuildValue( - "iiss#", - p_token->string - self->scanner->input, - p_token->string - self->scanner->input + p_token->string_sz, - p_token->regex->tok, - p_token->string, - p_token->string_sz - ); - } - } - Py_INCREF(Py_None); - return (PyObject *)Py_None; -} - -static PyObject * -scss_Scanner_reset(scss_Scanner *self, PyObject *args, PyObject *kwds) -{ - char *input = NULL; - int input_sz = 0; - - if (self->scanner != NULL) { - if (PyArg_ParseTuple(args, "|z#", &input, &input_sz)) { - Scanner_reset(self->scanner, input, input_sz); - } - } - - Py_INCREF(Py_None); - return (PyObject *)Py_None; -} - -static PyObject * -scss_Scanner_setup_patterns(PyObject *self, PyObject *patterns) -{ - PyObject *item, *item0, *item1; - int i, is_tuple, _is_tuple; - long size; - - Pattern *_patterns = NULL; - int patterns_sz = 0; - if (!Scanner_initialized()) { - is_tuple = PyTuple_Check(patterns); - if (is_tuple || PyList_Check(patterns)) { - size = is_tuple ? PyTuple_Size(patterns) : PyList_Size(patterns); - _patterns = PyMem_New(Pattern, size); - for (i = 0; i < size; ++i) { - item = is_tuple ? PyTuple_GetItem(patterns, i) : PyList_GetItem(patterns, i); - _is_tuple = PyTuple_Check(item); - if (_is_tuple || PyList_Check(item)) { - item0 = _is_tuple ? PyTuple_GetItem(item, 0) : PyList_GetItem(item, 0); - item1 = _is_tuple ? PyTuple_GetItem(item, 1) : PyList_GetItem(item, 1); - if (PyString_Check(item0) && PyString_Check(item1)) { - _patterns[patterns_sz].tok = PyString_AsString(item0); - _patterns[patterns_sz].expr = PyString_AsString(item1); - patterns_sz++; - } - } - } - } - Scanner_initialize(_patterns, patterns_sz); - if (_patterns != NULL) PyMem_Del(_patterns); - } - Py_INCREF(Py_None); - return (PyObject *)Py_None; -} - -static int -scss_Scanner_init(scss_Scanner *self, PyObject *args, PyObject *kwds) -{ - PyObject *item, *item0, *item1; - int i, is_tuple, _is_tuple; - long size; - - PyObject *patterns, *ignore; - Pattern *_patterns = NULL; - int patterns_sz = 0; - Pattern *_ignore = NULL; - int ignore_sz = 0; - char *input = NULL; - int input_sz = 0; - - self->scanner = NULL; - - if (!PyArg_ParseTuple(args, "OO|z#", &patterns, &ignore, &input, &input_sz)) { - return -1; - } - - if (!Scanner_initialized()) { - is_tuple = PyTuple_Check(patterns); - if (is_tuple || PyList_Check(patterns)) { - size = is_tuple ? PyTuple_Size(patterns) : PyList_Size(patterns); - _patterns = PyMem_New(Pattern, size); - for (i = 0; i < size; ++i) { - item = is_tuple ? PyTuple_GetItem(patterns, i) : PyList_GetItem(patterns, i); - _is_tuple = PyTuple_Check(item); - if (_is_tuple || PyList_Check(item)) { - item0 = _is_tuple ? PyTuple_GetItem(item, 0) : PyList_GetItem(item, 0); - item1 = _is_tuple ? PyTuple_GetItem(item, 1) : PyList_GetItem(item, 1); - if (PyString_Check(item0) && PyString_Check(item1)) { - _patterns[patterns_sz].tok = PyString_AsString(item0); - _patterns[patterns_sz].expr = PyString_AsString(item1); - patterns_sz++; - } - } - } - } - Scanner_initialize(_patterns, patterns_sz); - } - - is_tuple = PyTuple_Check(ignore); - if (is_tuple || PyList_Check(ignore)) { - size = is_tuple ? PyTuple_Size(ignore) : PyList_Size(ignore); - _ignore = PyMem_New(Pattern, size); - for (i = 0; i < size; ++i) { - item = is_tuple ? PyTuple_GetItem(ignore, i) : PyList_GetItem(ignore, i); - if (PyString_Check(item)) { - _ignore[ignore_sz].tok = PyString_AsString(item); - _ignore[ignore_sz].expr = NULL; - ignore_sz++; - } - } - } - - self->scanner = Scanner_new(_patterns, patterns_sz, _ignore, ignore_sz, input, input_sz); - - if (_patterns != NULL) PyMem_Del(_patterns); - if (_ignore != NULL) PyMem_Del(_ignore); - - #ifdef DEBUG - PySys_WriteStderr("Scss Scanner object initialized! (%lu bytes)\n", sizeof(scss_Scanner)); - #endif - - return 0; -} - -static PyObject * -scss_Scanner_repr(scss_Scanner *self) -{ - /* Print the last 10 tokens that have been scanned in */ - PyObject *repr, *tmp; - Token *p_token; - int i, start, pos; - - if (self->scanner != NULL && self->scanner->tokens_sz) { - start = self->scanner->tokens_sz - 10; - repr = PyString_FromString(""); - for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { - p_token = &self->scanner->tokens[i]; - PyString_ConcatAndDel(&repr, PyString_FromString("\n")); - pos = (int)(p_token->string - self->scanner->input); - PyString_ConcatAndDel(&repr, PyString_FromFormat(" (@%d) %s = ", - pos, p_token->regex->tok)); - tmp = PyString_FromStringAndSize(p_token->string, p_token->string_sz); - PyString_ConcatAndDel(&repr, PyObject_Repr(tmp)); - Py_XDECREF(tmp); - } - } else { - repr = PyString_FromString("None"); - } - - return (PyObject *)repr; - -/* - PyObject *repr, *tmp, *tmp2; - Token *p_token; - char *tok; - int i, start, first = 1, cur, max=0, pos; - - if (self->scanner != NULL && self->scanner->tokens_sz) { - start = self->scanner->tokens_sz - 10; - repr = PyString_FromString(""); - for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { - p_token = self->scanner->tokens[i]; - PyString_ConcatAndDel(&repr, PyString_FromString("\n")); - pos = (int)(p_token->string - self->scanner->input); - PyString_ConcatAndDel(&repr, PyString_FromFormat(" (@%d) %s = ", - pos, p_token->regex->tok)); - tmp = PyString_FromString(p_token->string); - PyString_ConcatAndDel(&repr, PyObject_Repr(tmp)); - Py_XDECREF(tmp); - } - - start = self->scanner->tokens_sz - 10; - for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { - p_token = self->scanner->tokens[i]; - cur = strlen(p_token->regex->tok) * 2; - if (cur > max) max = cur; - } - tok = PyMem_New(char, max + 4); - repr = PyString_FromString(""); - for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { - p_token = self->scanner->tokens[i]; - if (!first) PyString_ConcatAndDel(&repr, PyString_FromString("\n")); - - pos = (int)(p_token->string - self->scanner->input); - if (pos < 10) PyString_ConcatAndDel(&repr, PyString_FromString(" ")); - if (pos < 100) PyString_ConcatAndDel(&repr, PyString_FromString(" ")); - if (pos < 1000) PyString_ConcatAndDel(&repr, PyString_FromString(" ")); - PyString_ConcatAndDel(&repr, PyString_FromFormat("(@%d) ", - pos)); - - tmp = PyString_FromString(p_token->regex->tok); - tmp2 = PyObject_Repr(tmp); - memset(tok, ' ', max + 4); - tok[max + 3 - PyString_Size(tmp2)] = '\0'; - PyString_ConcatAndDel(&repr, PyString_FromString(tok)); - PyString_ConcatAndDel(&repr, tmp2); - Py_XDECREF(tmp); - - PyString_ConcatAndDel(&repr, PyString_FromString(" = ")); - tmp = PyString_FromString(p_token->string); - PyString_ConcatAndDel(&repr, PyObject_Repr(tmp)); - Py_XDECREF(tmp); - - first = 0; - } - PyMem_Del(tok); - } else { - repr = PyString_FromString("None"); - } - - return (PyObject *)repr; -*/ -} - -static void -scss_Scanner_dealloc(scss_Scanner *self) -{ - if (self->scanner != NULL) Scanner_del(self->scanner); - - self->ob_type->tp_free((PyObject*)self); - - #ifdef DEBUG - PySys_WriteStderr("Scss Scanner object destroyed!\n"); - #endif -} - -static PyMethodDef scss_Scanner_methods[] = { - {"reset", (PyCFunction)scss_Scanner_reset, METH_VARARGS, "Scan the next token"}, - {"token", (PyCFunction)scss_Scanner_token, METH_VARARGS, "Get the nth token"}, - {"rewind", (PyCFunction)scss_Scanner_rewind, METH_VARARGS, "Rewind scanner"}, - {"setup_patterns", (PyCFunction)scss_Scanner_setup_patterns, METH_O | METH_STATIC, "Initialize patterns."}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -static PyTypeObject scss_ScannerType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "scss.Scanner", /* tp_name */ - sizeof(scss_Scanner), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)scss_Scanner_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - (reprfunc)scss_Scanner_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - "Scanner object.", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter: __iter__() method */ - 0, /* tp_iternext: next() method */ - scss_Scanner_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)scss_Scanner_init, /* tp_init */ -}; - - -/* Python constructor */ - -static PyObject * -scss_locate_blocks(PyObject *self, PyObject *args) -{ - scss_BlockLocator *result = NULL; - - result = PyObject_New(scss_BlockLocator, &scss_BlockLocatorType); - if (result) { - scss_BlockLocator_init(result, args, NULL); - } - - return (PyObject *)result; -} - - -/* Module functions */ - -static PyMethodDef scss_methods[] = { - {"locate_blocks", (PyCFunction)scss_locate_blocks, METH_VARARGS, "Locate Scss blocks."}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - - -/* Module init function */ - -PyMODINIT_FUNC -init_speedups(void) -{ - PyObject* m; - - scss_BlockLocatorType.tp_new = PyType_GenericNew; - if (PyType_Ready(&scss_BlockLocatorType) < 0) - return; - - scss_ScannerType.tp_new = PyType_GenericNew; - if (PyType_Ready(&scss_ScannerType) < 0) - return; - - BlockLocator_initialize(); - Scanner_initialize(NULL, 0); - - m = Py_InitModule("_speedups", scss_methods); - - Py_INCREF(&scss_BlockLocatorType); - PyModule_AddObject(m, "_BlockLocator", (PyObject *)&scss_BlockLocatorType); - - Py_INCREF(&scss_ScannerType); - PyModule_AddObject(m, "Scanner", (PyObject *)&scss_ScannerType); - - PyExc_scss_NoMoreTokens = PyErr_NewException("_speedups.NoMoreTokens", NULL, NULL); - Py_INCREF(PyExc_scss_NoMoreTokens); - PyModule_AddObject(m, "NoMoreTokens", (PyObject *)PyExc_scss_NoMoreTokens); -} diff --git a/libs/scss/src/block_locator.c b/libs/scss/src/block_locator.c deleted file mode 100644 index f35d6d33..00000000 --- a/libs/scss/src/block_locator.c +++ /dev/null @@ -1,547 +0,0 @@ -/* -* pyScss, a Scss compiler for Python -* SCSS blocks scanner. -* -* German M. Bravo (Kronuz) -* https://github.com/Kronuz/pyScss -* -* MIT license (http://www.opensource.org/licenses/mit-license.php) -* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. -*/ -#include - -#include -#include -#include -#include "block_locator.h" - -int _strip(char *begin, char *end, int *lineno) { - // " 1\0 some, \n 2\0 aca " - int _cnt, - cnt = 0, - pass = 1, - addnl = 0; - char c, - *line = NULL, - *first = begin, - *last = begin, - *write = lineno ? begin : NULL; - while (begin < end) { - c = *begin; - if (c == '\0') { - if (lineno && line == NULL) { - line = first - 1; - do { - c = *++line; - } while (c == ' ' || c == '\t' || c == '\n' || c == ';'); - if (c != '\0') { - sscanf(line, "%d", lineno); - } - } - first = last = begin + 1; - pass = 1; - } else if (c == '\n') { - _cnt = (int)(last - first); - if (_cnt > 0) { - cnt += _cnt + addnl; - if (write != NULL) { - if (addnl) { - *write++ = '\n'; - } - while (first < last) { - *write++ = *first++; - } - addnl = 1; - } - } - first = last = begin + 1; - pass = 1; - } else if (c == ' ' || c == '\t') { - if (pass) { - first = last = begin + 1; - } - } else { - last = begin + 1; - pass = 0; - } - begin++; - } - _cnt = (int)(last - first); - if (_cnt > 0) { - cnt += _cnt + addnl; - if (write != NULL) { - if (addnl) { - *write++ = '\n'; - } - while (first < last) { - *write++ = *first++; - } - } - } - return cnt; -} - - -/* BlockLocator */ - -typedef void _BlockLocator_Callback(BlockLocator*); - -static void -_BlockLocator_start_string(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // A string starts - self->instr = *(self->codestr_ptr); -} - -static void -_BlockLocator_end_string(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // A string ends (FIXME: needs to accept escaped characters) - self->instr = 0; -} - -static void -_BlockLocator_start_parenthesis(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // parenthesis begins: - self->par++; - self->thin = NULL; - self->safe = self->codestr_ptr + 1; -} - -static void -_BlockLocator_end_parenthesis(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - self->par--; -} - -static void -_BlockLocator_flush_properties(BlockLocator *self) { - int len, lineno = -1; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // Flush properties - if (self->lose <= self->init) { - len = _strip(self->lose, self->init, &lineno); - if (len) { - if (lineno != -1) { - self->lineno = lineno; - } - - self->block.selprop = self->lose; - self->block.selprop_sz = len; - self->block.codestr = NULL; - self->block.codestr_sz = 0; - self->block.lineno = self->lineno; - self->block.error = 1; - } - self->lose = self->init; - } -} - -static void -_BlockLocator_start_block1(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // Start block: - if (self->codestr_ptr > self->codestr && *(self->codestr_ptr - 1) == '#') { - self->skip = 1; - } else { - self->start = self->codestr_ptr; - if (self->thin != NULL && _strip(self->thin, self->codestr_ptr, NULL)) { - self->init = self->thin; - } - _BlockLocator_flush_properties(self); - self->thin = NULL; - } - self->depth++; -} - -static void -_BlockLocator_start_block(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // Start block: - self->depth++; -} - -static void -_BlockLocator_end_block1(BlockLocator *self) { - int len, lineno = -1; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // Block ends: - self->depth--; - if (!self->skip) { - self->end = self->codestr_ptr; - len = _strip(self->init, self->start, &lineno); - if (lineno != -1) { - self->lineno = lineno; - } - - self->block.selprop = self->init; - self->block.selprop_sz = len; - self->block.codestr = (self->start + 1); - self->block.codestr_sz = (int)(self->end - (self->start + 1)); - self->block.lineno = self->lineno; - self->block.error = 1; - - self->init = self->safe = self->lose = self->end + 1; - self->thin = NULL; - } - self->skip = 0; -} - -static void -_BlockLocator_end_block(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // Block ends: - self->depth--; -} - -static void -_BlockLocator_end_property(BlockLocator *self) { - int len, lineno = -1; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // End of property (or block): - self->init = self->codestr_ptr; - if (self->lose <= self->init) { - len = _strip(self->lose, self->init, &lineno); - if (len) { - if (lineno != -1) { - self->lineno = lineno; - } - - self->block.selprop = self->lose; - self->block.selprop_sz = len; - self->block.codestr = NULL; - self->block.codestr_sz = 0; - self->block.lineno = self->lineno; - self->block.error = 1; - } - self->init = self->safe = self->lose = self->codestr_ptr + 1; - } - self->thin = NULL; -} - -static void -_BlockLocator_mark_safe(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // We are on a safe zone - if (self->thin != NULL && _strip(self->thin, self->codestr_ptr, NULL)) { - self->init = self->thin; - } - self->thin = NULL; - self->safe = self->codestr_ptr + 1; -} - -static void -_BlockLocator_mark_thin(BlockLocator *self) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - // Step on thin ice, if it breaks, it breaks here - if (self->thin != NULL && _strip(self->thin, self->codestr_ptr, NULL)) { - self->init = self->thin; - self->thin = self->codestr_ptr + 1; - } else if (self->thin == NULL && _strip(self->safe, self->codestr_ptr, NULL)) { - self->thin = self->codestr_ptr + 1; - } -} - -int function_map_initialized = 0; -_BlockLocator_Callback* scss_function_map[256 * 256 * 2 * 3]; // (c, instr, par, depth) - -static void -init_function_map(void) { - int i; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (function_map_initialized) { - return; - } - function_map_initialized = 1; - - for (i = 0; i < 256 * 256 * 2 * 3; i++) { - scss_function_map[i] = NULL; - } - scss_function_map[(int)'\"' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_string; - scss_function_map[(int)'\'' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_string; - scss_function_map[(int)'\"' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_start_string; - scss_function_map[(int)'\'' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_start_string; - scss_function_map[(int)'\"' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_string; - scss_function_map[(int)'\'' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_string; - scss_function_map[(int)'\"' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_start_string; - scss_function_map[(int)'\'' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_start_string; - scss_function_map[(int)'\"' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_string; - scss_function_map[(int)'\'' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_string; - scss_function_map[(int)'\"' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_start_string; - scss_function_map[(int)'\'' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_start_string; - - scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*0 + 256*256*2*0] = _BlockLocator_end_string; - scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*0 + 256*256*2*0] = _BlockLocator_end_string; - scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*1 + 256*256*2*0] = _BlockLocator_end_string; - scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*1 + 256*256*2*0] = _BlockLocator_end_string; - scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*0 + 256*256*2*1] = _BlockLocator_end_string; - scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*0 + 256*256*2*1] = _BlockLocator_end_string; - scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*1 + 256*256*2*1] = _BlockLocator_end_string; - scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*1 + 256*256*2*1] = _BlockLocator_end_string; - scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*0 + 256*256*2*2] = _BlockLocator_end_string; - scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*0 + 256*256*2*2] = _BlockLocator_end_string; - scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*1 + 256*256*2*2] = _BlockLocator_end_string; - scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*1 + 256*256*2*2] = _BlockLocator_end_string; - - scss_function_map[(int)'(' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_parenthesis; - scss_function_map[(int)'(' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_start_parenthesis; - scss_function_map[(int)'(' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_parenthesis; - scss_function_map[(int)'(' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_start_parenthesis; - scss_function_map[(int)'(' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_parenthesis; - scss_function_map[(int)'(' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_start_parenthesis; - - scss_function_map[(int)')' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_end_parenthesis; - scss_function_map[(int)')' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_end_parenthesis; - scss_function_map[(int)')' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_end_parenthesis; - - scss_function_map[(int)'{' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_block1; - scss_function_map[(int)'{' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_block; - scss_function_map[(int)'{' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_block; - - scss_function_map[(int)'}' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_end_block1; - scss_function_map[(int)'}' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_end_block; - - scss_function_map[(int)';' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_end_property; - - scss_function_map[(int)',' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_mark_safe; - - scss_function_map[(int)'\n' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_mark_thin; - - scss_function_map[0 + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_flush_properties; - scss_function_map[0 + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_flush_properties; - scss_function_map[0 + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_flush_properties; - - #ifdef DEBUG - fprintf(stderr, "\tScss function maps initialized!\n"); - #endif -} - - -/* BlockLocator public interface */ - -void -BlockLocator_initialize(void) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - init_function_map(); -} - -void -BlockLocator_finalize(void) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif -} - -BlockLocator * -BlockLocator_new(char *codestr, int codestr_sz) -{ - BlockLocator *self; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - self = PyMem_New(BlockLocator, 1); - if (self) { - memset(self, 0, sizeof(BlockLocator)); - self->_codestr = PyMem_New(char, codestr_sz); - memcpy(self->_codestr, codestr, codestr_sz); - self->codestr_sz = codestr_sz; - self->codestr = PyMem_New(char, self->codestr_sz); - memcpy(self->codestr, self->_codestr, self->codestr_sz); - self->codestr_ptr = self->codestr; - self->lineno = 0; - self->par = 0; - self->instr = 0; - self->depth = 0; - self->skip = 0; - self->thin = self->codestr; - self->init = self->codestr; - self->safe = self->codestr; - self->lose = self->codestr; - self->start = NULL; - self->end = NULL; - #ifdef DEBUG - fprintf(stderr, "\tScss BlockLocator object created (%d bytes)!\n", codestr_sz); - #endif - } - return self; -} - -void -BlockLocator_del(BlockLocator *self) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - free(self->codestr); - free(self->_codestr); - free(self); -} - -void -BlockLocator_rewind(BlockLocator *self) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - free(self->codestr); - self->codestr = PyMem_New(char, self->codestr_sz); - memcpy(self->codestr, self->_codestr, self->codestr_sz); - self->codestr_ptr = self->codestr; - self->lineno = 0; - self->par = 0; - self->instr = 0; - self->depth = 0; - self->skip = 0; - self->thin = self->codestr; - self->init = self->codestr; - self->safe = self->codestr; - self->lose = self->codestr; - self->start = NULL; - self->end = NULL; - - #ifdef DEBUG - fprintf(stderr, "\tScss BlockLocator object rewound!\n"); - #endif -} - -Block* -BlockLocator_iternext(BlockLocator *self) -{ - _BlockLocator_Callback *fn; - unsigned char c = 0; - char *codestr_end = self->codestr + self->codestr_sz; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - memset(&self->block, 0, sizeof(Block)); - - while (self->codestr_ptr < codestr_end) { - c = *(self->codestr_ptr); - if (!c) { - self->codestr_ptr++; - continue; - } - - repeat: - - fn = scss_function_map[ - (int)c + - 256 * self->instr + - 256 * 256 * (int)(self->par != 0) + - 256 * 256 * 2 * (int)(self->depth > 1 ? 2 : self->depth) - ]; - - if (fn != NULL) { - fn(self); - } - - self->codestr_ptr++; - if (self->codestr_ptr > codestr_end) { - self->codestr_ptr = codestr_end; - } - - if (self->block.error) { - #ifdef DEBUG - if (self->block.error > 0) { - fprintf(stderr, "\tBlock found!\n"); - } else { - fprintf(stderr, "\tException!\n"); - } - #endif - return &self->block; - } - } - if (self->par > 0) { - if (self->block.error >= 0) { - self->block.error = -1; - sprintf(self->exc, "Missing closing parenthesis somewhere in block"); - #ifdef DEBUG - fprintf(stderr, "\t%s\n", self->exc); - #endif - } - } else if (self->instr != 0) { - if (self->block.error >= 0) { - self->block.error = -2; - sprintf(self->exc, "Missing closing string somewhere in block"); - #ifdef DEBUG - fprintf(stderr, "\t%s\n", self->exc); - #endif - } - } else if (self->depth > 0) { - if (self->block.error >= 0) { - self->block.error = -3; - sprintf(self->exc, "Missing closing string somewhere in block"); - #ifdef DEBUG - fprintf(stderr, "\t%s\n", self->exc); - #endif - } - if (self->init < codestr_end) { - c = '}'; - goto repeat; - } - } - if (self->init < codestr_end) { - self->init = codestr_end; - c = 0; - goto repeat; - } - - BlockLocator_rewind(self); - - return &self->block; -} diff --git a/libs/scss/src/block_locator.h b/libs/scss/src/block_locator.h deleted file mode 100644 index c59afbc6..00000000 --- a/libs/scss/src/block_locator.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -* pyScss, a Scss compiler for Python -* SCSS blocks scanner. -* -* German M. Bravo (Kronuz) -* https://github.com/Kronuz/pyScss -* -* MIT license (http://www.opensource.org/licenses/mit-license.php) -* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. -*/ -#ifndef BLOCK_LOCATOR_H -#define BLOCK_LOCATOR_H - -#define MAX_EXC_STRING 4096 - -typedef struct { - int error; - int lineno; - char *selprop; - int selprop_sz; - char *codestr; - int codestr_sz; -} Block; - -typedef struct { - char exc[MAX_EXC_STRING]; - char *_codestr; - char *codestr; - char *codestr_ptr; - int codestr_sz; - int lineno; - int par; - char instr; - int depth; - int skip; - char *thin; - char *init; - char *safe; - char *lose; - char *start; - char *end; - Block block; -} BlockLocator; - -void BlockLocator_initialize(void); -void BlockLocator_finalize(void); - -Block* BlockLocator_iternext(BlockLocator *self); -BlockLocator *BlockLocator_new(char *codestr, int codestr_sz); -void BlockLocator_del(BlockLocator *self); - -#endif diff --git a/libs/scss/src/scanner.c b/libs/scss/src/scanner.c deleted file mode 100644 index b8fedeff..00000000 --- a/libs/scss/src/scanner.c +++ /dev/null @@ -1,463 +0,0 @@ -/* -* pyScss, a Scss compiler for Python -* SCSS blocks scanner. -* -* German M. Bravo (Kronuz) -* https://github.com/Kronuz/pyScss -* -* MIT license (http://www.opensource.org/licenses/mit-license.php) -* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. -*/ -#include - -#include -#include -#include "scanner.h" - -#include "utils.h" - -int Pattern_patterns_sz = 0; -int Pattern_patterns_bsz = 0; -Pattern *Pattern_patterns = NULL; -int Pattern_patterns_initialized = 0; - -Pattern* -Pattern_regex(char *tok, char *expr) { - int j; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - for (j = 0; j < Pattern_patterns_sz; j++) { - if (strcmp(Pattern_patterns[j].tok, tok) == 0) { - return &Pattern_patterns[j]; - } - } - if (expr) { - if (j >= Pattern_patterns_bsz) { - /* Needs to expand block */ - Pattern_patterns_bsz = Pattern_patterns_bsz + BLOCK_SIZE_PATTERNS; - PyMem_Resize(Pattern_patterns, Pattern, Pattern_patterns_bsz); - } - Pattern_patterns[j].tok = PyMem_Strdup(tok); - Pattern_patterns[j].expr = PyMem_Strdup(expr); - Pattern_patterns[j].pattern = NULL; - Pattern_patterns_sz = j + 1; - return &Pattern_patterns[j]; - } - return NULL; -} - -static int -Pattern_match(Pattern *regex, char *string, int string_sz, int start_at, Token *p_token) { - int options = PCRE_ANCHORED; - const char *errptr; - int ret, erroffset, ovector[3]; - pcre *p_pattern = regex->pattern; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (p_pattern == NULL) { - #ifdef DEBUG - fprintf(stderr, "\tpcre_compile %s\n", repr(regex->expr)); - #endif - p_pattern = regex->pattern = pcre_compile(regex->expr, options, &errptr, &erroffset, NULL); - } - ret = pcre_exec( - p_pattern, - NULL, /* no extra data */ - string, - string_sz, - start_at, - PCRE_ANCHORED, /* default options */ - ovector, /* output vector for substring information */ - 3 /* number of elements in the output vector */ - ); - if (ret >= 0) { - if (p_token) { - p_token->regex = regex; - p_token->string = string + ovector[0]; - p_token->string_sz = ovector[1] - ovector[0]; - } - return 1; - } - return 0; -} - -static void Pattern_initialize(Pattern *, int); -static void Pattern_setup(Pattern *, int); -static void Pattern_finalize(void); - - -static void -Pattern_initialize(Pattern *patterns, int patterns_sz) { - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (!Pattern_patterns_initialized) { - if (patterns_sz) { - Pattern_patterns_initialized = 1; - Pattern_setup(patterns, patterns_sz); - } - } -} - -static void -Pattern_setup(Pattern *patterns, int patterns_sz) { - int i; - Pattern *regex; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (!Pattern_patterns_initialized) { - Pattern_initialize(patterns, patterns_sz); - } else { - for (i = 0; i < patterns_sz; i++) { - regex = Pattern_regex(patterns[i].tok, patterns[i].expr); - #ifdef DEBUG - if (regex) { - fprintf(stderr, "\tAdded regex pattern %s: %s\n", repr(regex->tok), repr(regex->expr)); - } - #endif - } - } -} - -static void -Pattern_finalize(void) { - int j; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (Pattern_patterns_initialized) { - for (j = 0; j < Pattern_patterns_sz; j++) { - PyMem_Del(Pattern_patterns[j].tok); - PyMem_Del(Pattern_patterns[j].expr); - if (Pattern_patterns[j].pattern != NULL) { - pcre_free(Pattern_patterns[j].pattern); - } - } - PyMem_Del(Pattern_patterns); - Pattern_patterns = NULL; - Pattern_patterns_sz = 0; - Pattern_patterns_bsz = 0; - Pattern_patterns_initialized = 0; - } -} - -/* Scanner */ - - -static long -_Scanner_scan(Scanner *self, Pattern *restrictions, int restrictions_sz) -{ - Token best_token, *p_token; - Restriction *p_restriction; - Pattern *regex; - int j, k, max, skip; - size_t len; - char *aux; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - while (1) { - regex = NULL; - best_token.regex = NULL; - /* Search the patterns for a match, with earlier - tokens in the list having preference */ - for (j = 0; j < Pattern_patterns_sz; j++) { - regex = &Pattern_patterns[j]; - #ifdef DEBUG - fprintf(stderr, "\tTrying %s: %s at pos %d -> %s\n", repr(regex->tok), repr(regex->expr), self->pos, repr(self->input)); - #endif - /* First check to see if we're restricting to this token */ - skip = restrictions_sz; - if (skip) { - max = (restrictions_sz > self->ignore_sz) ? restrictions_sz : self->ignore_sz; - for (k = 0; k < max; k++) { - if (k < restrictions_sz && strcmp(regex->tok, restrictions[k].tok) == 0) { - skip = 0; - break; - } - if (k < self->ignore_sz && regex == self->ignore[k]) { - skip = 0; - break; - } - } - if (skip) { - continue; - #ifdef DEBUG - fprintf(stderr, "\tSkipping %s!\n", repr(regex->tok)); - #endif - } - } - if (Pattern_match( - regex, - self->input, - self->input_sz, - self->pos, - &best_token - )) { - #ifdef DEBUG - fprintf(stderr, "Match OK! %s: %s at pos %d\n", repr(regex->tok), repr(regex->expr), self->pos); - #endif - break; - } - } - /* If we didn't find anything, raise an error */ - if (best_token.regex == NULL) { - if (restrictions_sz) { - sprintf(self->exc, "SyntaxError[@ char %d: Bad token found while trying to find one of the restricted tokens: ", self->pos); - aux = self->exc + strlen(self->exc); - for (k=0; k self->exc + sizeof(self->exc) - 10) { - sprintf(aux, (k > 0) ? ", ..." : "..."); - break; - } - sprintf(aux, (k > 0) ? ", %s" : "%s", repr(restrictions[k].tok)); - aux += len + 2; - } - sprintf(aux, "]"); - return SCANNER_EXC_RESTRICTED; - } - sprintf(self->exc, "SyntaxError[@ char %d: Bad token found]", self->pos); - return SCANNER_EXC_BAD_TOKEN; - } - /* If we found something that isn't to be ignored, return it */ - skip = 0; - for (k = 0; k < self->ignore_sz; k++) { - if (best_token.regex == self->ignore[k]) { - /* This token should be ignored... */ - self->pos += best_token.string_sz; - skip = 1; - break; - } - } - if (!skip) { - break; - } - } - if (best_token.regex) { - self->pos = (int)(best_token.string - self->input + best_token.string_sz); - /* Only add this token if it's not in the list (to prevent looping) */ - p_token = &self->tokens[self->tokens_sz - 1]; - if (self->tokens_sz == 0 || - p_token->regex != best_token.regex || - p_token->string != best_token.string || - p_token->string_sz != best_token.string_sz - ) { - if (self->tokens_sz >= self->tokens_bsz) { - /* Needs to expand block */ - self->tokens_bsz = self->tokens_bsz + BLOCK_SIZE_PATTERNS; - PyMem_Resize(self->tokens, Token, self->tokens_bsz); - PyMem_Resize(self->restrictions, Restriction, self->tokens_bsz); - } - memcpy(&self->tokens[self->tokens_sz], &best_token, sizeof(Token)); - p_restriction = &self->restrictions[self->tokens_sz]; - if (restrictions_sz) { - p_restriction->patterns = PyMem_New(Pattern *, restrictions_sz); - p_restriction->patterns_sz = 0; - for (j = 0; j < restrictions_sz; j++) { - regex = Pattern_regex(restrictions[j].tok, restrictions[j].expr); - if (regex) { - p_restriction->patterns[p_restriction->patterns_sz++] = regex; - } - } - } else { - p_restriction->patterns = NULL; - p_restriction->patterns_sz = 0; - } - self->tokens_sz++; - return 1; - } - } - return 0; -} - - -/* Scanner public interface */ - -void -Scanner_reset(Scanner *self, char *input, int input_sz) { - int i; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - for (i = 0; i < self->tokens_sz; i++) { - PyMem_Del(self->restrictions[i].patterns); - self->restrictions[i].patterns = NULL; - self->restrictions[i].patterns_sz = 0; - } - self->tokens_sz = 0; - - if (self->input != NULL) { - PyMem_Del(self->input); - } - self->input = PyMem_Strndup(input, input_sz); - self->input_sz = input_sz; - #ifdef DEBUG - fprintf(stderr, "Scanning in %s\n", repr(self->input)); - #endif - - self->pos = 0; -} - -void -Scanner_del(Scanner *self) { - int i; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (self->ignore != NULL) { - PyMem_Del(self->ignore); - } - - if (self->tokens != NULL) { - for (i = 0; i < self->tokens_sz; i++) { - PyMem_Del(self->restrictions[i].patterns); - } - PyMem_Del(self->tokens); - PyMem_Del(self->restrictions); - } - - if (self->input != NULL) { - PyMem_Del(self->input); - } - - PyMem_Del(self); -} - -Scanner* -Scanner_new(Pattern patterns[], int patterns_sz, Pattern ignore[], int ignore_sz, char *input, int input_sz) -{ - int i; - Scanner *self; - Pattern *regex; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - self = PyMem_New(Scanner, 1); - memset(self, 0, sizeof(Scanner)); - if (self) { - for (i = 0; i < patterns_sz; i++) { - regex = Pattern_regex(patterns[i].tok, patterns[i].expr); - #ifdef DEBUG - if (regex) { - fprintf(stderr, "\tAdded regex pattern %s: %s\n", repr(regex->tok), repr(regex->expr)); - } - #endif - } - if (ignore_sz) { - self->ignore = PyMem_New(Pattern *, ignore_sz); - for (i = 0; i < ignore_sz; i++) { - regex = Pattern_regex(ignore[i].tok, ignore[i].expr); - if (regex) { - self->ignore[self->ignore_sz++] = regex; - #ifdef DEBUG - fprintf(stderr, "\tIgnoring token %s\n", repr(regex->tok)); - #endif - } - } - } else { - self->ignore = NULL; - } - Scanner_reset(self, input, input_sz); - } - return self; -} - -int -Scanner_initialized(void) -{ - return Pattern_patterns_initialized; -} - -void -Scanner_initialize(Pattern patterns[], int patterns_sz) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - Pattern_initialize(patterns, patterns_sz); -} - -void -Scanner_finalize(void) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - Pattern_finalize(); -} - -Token* -Scanner_token(Scanner *self, int i, Pattern restrictions[], int restrictions_sz) -{ - int j, k, found; - Pattern *regex; - long result; - - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (i == self->tokens_sz) { - result = _Scanner_scan(self, restrictions, restrictions_sz); - if (result < 0) { - return (Token *)result; - } - } else if (i >= 0 && i < self->tokens_sz) { - if (self->restrictions[i].patterns_sz) { - for (j = 0; j < restrictions_sz; j++) { - found = 0; - for (k = 0; k < self->restrictions[i].patterns_sz; k++) { - regex = Pattern_regex(restrictions[j].tok, restrictions[j].expr); - if (strcmp(restrictions[j].tok, self->restrictions[i].patterns[k]->tok) == 0) { - found = 1; - break; - } - } - if (!found) { - sprintf(self->exc, "Unimplemented: restriction set changed"); - return (Token *)SCANNER_EXC_UNIMPLEMENTED; - } - } - } - } - if (i >= 0 && i < self->tokens_sz) { - return &self->tokens[i]; - } - return (Token *)SCANNER_EXC_NO_MORE_TOKENS; -} - -void -Scanner_rewind(Scanner *self, int i) -{ - #ifdef DEBUG - fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); - #endif - - if (i >= 0 && i < self->tokens_sz) { - self->tokens_sz = i; - self->pos = (int)(self->tokens[i].string - self->input); - } -} diff --git a/libs/scss/src/scanner.h b/libs/scss/src/scanner.h deleted file mode 100644 index df9fb629..00000000 --- a/libs/scss/src/scanner.h +++ /dev/null @@ -1,68 +0,0 @@ -/* -* pyScss, a Scss compiler for Python -* SCSS blocks scanner. -* -* German M. Bravo (Kronuz) -* https://github.com/Kronuz/pyScss -* -* MIT license (http://www.opensource.org/licenses/mit-license.php) -* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. -*/ -#ifndef SCANNER_H -#define SCANNER_H - -#define PCRE_STATIC -#include - -#define BLOCK_SIZE_PATTERNS 50 -#define BLOCK_SIZE_TOKENS 50 - -#define MAX_EXC_STRING 4096 - -#define SCANNER_EXC_BAD_TOKEN (long)-1 -#define SCANNER_EXC_RESTRICTED (long)-2 -#define SCANNER_EXC_UNIMPLEMENTED (long)-3 -#define SCANNER_EXC_NO_MORE_TOKENS (long)-4 - -typedef struct { - char *tok; - char *expr; - pcre *pattern; -} Pattern; - -typedef struct { - Pattern *regex; - char *string; - int string_sz; -} Token; - -typedef struct { - int patterns_sz; - Pattern **patterns; -} Restriction; - -typedef struct { - char exc[MAX_EXC_STRING]; - int ignore_sz; - Pattern **ignore; - int tokens_sz; - int tokens_bsz; - Token *tokens; - Restriction *restrictions; - int input_sz; - char *input; - int pos; -} Scanner; - -int Scanner_initialized(void); -void Scanner_initialize(Pattern *, int); -void Scanner_finalize(void); - -void Scanner_reset(Scanner *self, char *input, int input_sz); -Scanner *Scanner_new(Pattern *, int, Pattern *, int, char *, int); -void Scanner_del(Scanner *); - -Token* Scanner_token(Scanner *, int, Pattern *, int); -void Scanner_rewind(Scanner *, int); - -#endif diff --git a/libs/scss/src/utils.h b/libs/scss/src/utils.h deleted file mode 100644 index ee5401e1..00000000 --- a/libs/scss/src/utils.h +++ /dev/null @@ -1,98 +0,0 @@ -#include - -#include - -char * -PyMem_Strndup(const char *str, size_t len) -{ - if (str != NULL) { - char *copy = PyMem_New(char, len + 1); - if (copy != NULL) - memcpy(copy, str, len); - copy[len] = '\0'; - return copy; - } - return NULL; -} - -char * -PyMem_Strdup(const char *str) -{ - return PyMem_Strndup(str, strlen(str)); -} - -char * -reprn(char *str, size_t len) { - static char strings[10240]; - static size_t current = 0; - size_t reqlen = 2; - char c, - *out, - *write, - *begin = str, - *end = str + len; - while (begin < end) { - c = *begin; - if (c == '\'') { - reqlen += 2; - } else if (c == '\r') { - reqlen += 2; - } else if (c == '\n') { - reqlen += 2; - } else if (c == '\t') { - reqlen += 2; - } else if (c < ' ') { - reqlen += 3; - } else { - reqlen++; - } - begin++; - } - if (reqlen > 10240) { - reqlen = 10240; - } - if (current + reqlen > 10240) { - current = 0; - } - begin = str; - end = str + len; - out = write = strings + current; - *write++ = '\''; - while (begin < end) { - c = *begin; - if (c == '\'') { - if (write + 5 >= strings + 10240) break; - sprintf(write, "\\'"); - write += 2; - } else if (c == '\r') { - if (write + 5 >= strings + 10240) break; - sprintf(write, "\\r"); - write += 2; - } else if (c == '\n') { - if (write + 5 >= strings + 10240) break; - sprintf(write, "\\n"); - write += 2; - } else if (c == '\t') { - if (write + 5 >= strings + 10240) break; - sprintf(write, "\\t"); - write += 2; - } else if (c < ' ') { - if (write + 6 >= strings + 10240) break; - sprintf(write, "\\x%02x", c); - write += 3; - } else { - if (write + 4 >= strings + 10240) break; - *write++ = c; - } - begin++; - } - *write++ = '\''; - *write++ = '\0'; - current += (size_t)(write - out); - return out; -} - -char * -repr(char *str) { - return reprn(str, strlen(str)); -} diff --git a/libs/scss/tool.py b/libs/scss/tool.py deleted file mode 100644 index 205bb817..00000000 --- a/libs/scss/tool.py +++ /dev/null @@ -1,403 +0,0 @@ -#!/usr/bin/env python -from __future__ import absolute_import -from __future__ import print_function - -from contextlib import contextmanager -import logging -import os -import re -import sys -from collections import deque - -from scss import config -from scss.util import profiling -from scss import Scss, SourceFile, log -from scss import _prop_split_re -from scss.rule import SassRule -from scss.rule import UnparsedBlock -from scss.expression import Calculator -from scss.scss_meta import BUILD_INFO -from scss.errors import SassEvaluationError - -try: - raw_input -except NameError: - raw_input = input - -log.setLevel(logging.INFO) - - -def main(): - logging.basicConfig(format="%(levelname)s: %(message)s") - - from optparse import OptionGroup, OptionParser, SUPPRESS_HELP - - parser = OptionParser(usage="Usage: %prog [options] [file]", - description="Converts Scss files to CSS.", - add_help_option=False) - parser.add_option("-i", "--interactive", action="store_true", - help="Run an interactive Scss shell") - parser.add_option("-w", "--watch", metavar="DIR", - help="Watch the files in DIR, and recompile when they change") - parser.add_option("-r", "--recursive", action="store_true", default=False, - help="Also watch directories inside of the watch directory") - parser.add_option("-o", "--output", metavar="PATH", - help="Write output to PATH (a directory if using watch, a file otherwise)") - parser.add_option("-s", "--suffix", metavar="STRING", - help="If using watch, a suffix added to the output filename (i.e. filename.STRING.css)") - parser.add_option("--time", action="store_true", - help="Display compliation times") - parser.add_option("--debug-info", action="store_true", - help="Turns on scss's debugging information") - parser.add_option("--no-debug-info", action="store_false", - dest="debug_info", default=False, - help="Turns off scss's debugging information") - parser.add_option("-T", "--test", action="store_true", help=SUPPRESS_HELP) - parser.add_option("-t", "--style", metavar="NAME", - dest="style", default='nested', - help="Output style. Can be nested (default), compact, compressed, or expanded.") - parser.add_option("-C", "--no-compress", action="store_false", dest="style", default=True, - help="Don't minify outputted CSS") - 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") - - paths_group = OptionGroup(parser, "Resource Paths") - paths_group.add_option("-I", "--load-path", metavar="PATH", - action="append", dest="load_paths", - help="Add a scss import path, may be given multiple times") - paths_group.add_option("-S", "--static-root", metavar="PATH", dest="static_root", - help="Static root path (Where images and static resources are located)") - paths_group.add_option("-A", "--assets-root", metavar="PATH", dest="assets_root", - help="Assets root path (Sprite images will be created here)") - paths_group.add_option("-a", "--assets-url", metavar="URL", dest="assets_url", - help="URL to reach the files in your assets_root") - paths_group.add_option("-F", "--fonts-root", metavar="PATH", dest="fonts_root", - help="Fonts root path (Where fonts are located)") - paths_group.add_option("-f", "--fonts-url", metavar="PATH", dest="fonts_url", - help="URL to reach the fonts in your fonts_root") - paths_group.add_option("--images-root", metavar="PATH", dest="images_root", - help="Images root path (Where images are located)") - paths_group.add_option("--images-url", metavar="PATH", dest="images_url", - help="URL to reach the images in your images_root") - paths_group.add_option("--cache-root", metavar="PATH", dest="cache_root", - help="Cache root path (Cache files will be created here)") - parser.add_option_group(paths_group) - - parser.add_option("--sass", action="store_true", - dest="is_sass", default=None, - help="Sass mode") - - (options, args) = parser.parse_args() - - # General runtime configuration - config.VERBOSITY = 0 - if options.time: - config.VERBOSITY = 2 - - if options.static_root is not None: - config.STATIC_ROOT = options.static_root - if options.assets_root is not None: - config.ASSETS_ROOT = options.assets_root - - if options.fonts_root is not None: - config.FONTS_ROOT = options.fonts_root - if options.fonts_url is not None: - config.FONTS_URL = options.fonts_url - - if options.images_root is not None: - config.IMAGES_ROOT = options.images_root - if options.images_url is not None: - config.IMAGES_URL = options.images_url - - if options.cache_root is not None: - config.CACHE_ROOT = options.cache_root - if options.load_paths is not None: - # TODO: Convert global LOAD_PATHS to a list. Use it directly. - # Doing the above will break backwards compatibility! - if hasattr(config.LOAD_PATHS, 'split'): - load_path_list = [p.strip() for p in config.LOAD_PATHS.split(',')] - else: - load_path_list = list(config.LOAD_PATHS) - - for path_param in options.load_paths: - for p in path_param.replace(os.pathsep, ',').replace(';', ',').split(','): - p = p.strip() - if p and p not in load_path_list: - load_path_list.append(p) - - # TODO: Remove this once global LOAD_PATHS is a list. - if hasattr(config.LOAD_PATHS, 'split'): - config.LOAD_PATHS = ','.join(load_path_list) - else: - config.LOAD_PATHS = load_path_list - if options.assets_url is not None: - config.ASSETS_URL = options.assets_url - - # Execution modes - if options.test: - run_tests() - elif options.version: - print_version() - elif options.interactive: - run_repl(options) - elif options.watch: - watch_sources(options) - else: - do_build(options, args) - - -def print_version(): - print(BUILD_INFO) - - -def run_tests(): - try: - import pytest - except ImportError: - raise ImportError("You need py.test installed to run the test suite.") - pytest.main("") # don't let py.test re-consume our arguments - - -def do_build(options, args): - if options.output is not None: - output = open(options.output, 'wt') - else: - output = sys.stdout - - css = Scss(scss_opts={ - 'style': options.style, - 'debug_info': options.debug_info, - }) - if args: - for path in args: - output.write(css.compile(scss_file=path, is_sass=options.is_sass)) - else: - output.write(css.compile(sys.stdin.read(), is_sass=options.is_sass)) - - for f, t in profiling.items(): - sys.stderr.write("%s took %03fs" % (f, t)) - - -def watch_sources(options): - import time - try: - from watchdog.observers import Observer - from watchdog.events import PatternMatchingEventHandler - except ImportError: - sys.stderr.write("Using watch functionality requires the `watchdog` library: http://pypi.python.org/pypi/watchdog/") - sys.exit(1) - if options.output and not os.path.isdir(options.output): - sys.stderr.write("watch file output directory is invalid: '%s'" % (options.output)) - sys.exit(2) - - class ScssEventHandler(PatternMatchingEventHandler): - def __init__(self, *args, **kwargs): - super(ScssEventHandler, self).__init__(*args, **kwargs) - self.css = Scss(scss_opts={ - 'style': options.style, - 'debug_info': options.debug_info, - }) - self.output = options.output - self.suffix = options.suffix - - def is_valid(self, path): - return os.path.isfile(path) and (path.endswith('.scss') or path.endswith('.sass')) and not os.path.basename(path).startswith('_') - - def process(self, path): - if os.path.isdir(path): - for f in os.listdir(path): - full = os.path.join(path, f) - if self.is_valid(full): - self.compile(full) - elif self.is_valid(path): - self.compile(path) - - def compile(self, src_path): - fname = os.path.basename(src_path) - if fname.endswith('.scss') or fname.endswith('.sass'): - fname = fname[:-5] - if self.suffix: - fname += '.' + self.suffix - fname += '.css' - else: - # you didn't give me a file of the correct type! - return False - - if self.output: - dest_path = os.path.join(self.output, fname) - else: - dest_path = os.path.join(os.path.dirname(src_path), fname) - - print("Compiling %s => %s" % (src_path, dest_path)) - dest_file = open(dest_path, 'w') - dest_file.write(self.css.compile(scss_file=src_path)) - - def on_moved(self, event): - super(ScssEventHandler, self).on_moved(event) - self.process(event.dest_path) - - def on_created(self, event): - super(ScssEventHandler, self).on_created(event) - self.process(event.src_path) - - def on_modified(self, event): - super(ScssEventHandler, self).on_modified(event) - self.process(event.src_path) - - event_handler = ScssEventHandler(patterns=['*.scss', '*.sass']) - observer = Observer() - observer.schedule(event_handler, path=options.watch, recursive=options.recursive) - observer.start() - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - observer.stop() - observer.join() - - -@contextmanager -def readline_history(fn): - try: - import readline - except ImportError: - yield - return - - try: - readline.read_history_file(fn) - except IOError: - pass - - try: - yield - finally: - try: - readline.write_history_file(fn) - except IOError: - pass - - -def run_repl(is_sass=False): - repl = SassRepl() - - with readline_history(os.path.expanduser('~/.scss-history')): - print("Welcome to %s interactive shell" % (BUILD_INFO,)) - while True: - try: - in_ = raw_input('>>> ').strip() - for output in repl(in_): - print(output) - except (EOFError, KeyboardInterrupt): - print("Bye!") - return - - -class SassRepl(object): - def __init__(self, is_sass=False): - self.css = Scss() - self.namespace = self.css.root_namespace - self.options = self.css.scss_opts - self.source_file = SourceFile.from_string('', '', line_numbers=False, is_sass=is_sass) - self.calculator = Calculator(self.namespace) - - def __call__(self, s): - from pprint import pformat - - if s in ('exit', 'quit'): - raise KeyboardInterrupt - - for s in s.split(';'): - s = self.source_file.prepare_source(s.strip()) - if not s: - continue - elif s.startswith('@'): - scope = None - properties = [] - children = deque() - rule = SassRule(self.source_file, namespace=self.namespace, options=self.options, properties=properties) - block = UnparsedBlock(rule, 1, s, None) - code, name = (s.split(None, 1) + [''])[:2] - if code == '@option': - self.css._settle_options(rule, children, scope, block) - continue - elif code == '@import': - self.css._do_import(rule, children, scope, block) - continue - elif code == '@include': - final_cont = '' - self.css._do_include(rule, children, scope, block) - code = self.css._print_properties(properties).rstrip('\n') - if code: - final_cont += code - if children: - self.css.children.extendleft(children) - self.css.parse_children() - code = self.css._create_css(self.css.rules).rstrip('\n') - if code: - final_cont += code - yield final_cont - continue - elif s == 'ls' or s.startswith('show(') or s.startswith('show ') or s.startswith('ls(') or s.startswith('ls '): - m = re.match(r'(?:show|ls)(\()?\s*([^,/\\) ]*)(?:[,/\\ ]([^,/\\ )]+))*(?(1)\))', s, re.IGNORECASE) - if m: - name = m.group(2) - code = m.group(3) - name = name and name.strip().rstrip('s') # remove last 's' as in functions - code = code and code.strip() - ns = self.namespace - if not name: - yield pformat(sorted(['vars', 'options', 'mixins', 'functions'])) - elif name in ('v', 'var', 'variable'): - variables = dict(ns._variables) - if code == '*': - pass - elif code: - variables = dict((k, v) for k, v in variables.items() if code in k) - else: - variables = dict((k, v) for k, v in variables.items() if not k.startswith('$--')) - yield pformat(variables) - - elif name in ('o', 'opt', 'option'): - opts = self.options - if code == '*': - pass - elif code: - opts = dict((k, v) for k, v in opts.items() if code in k) - else: - opts = dict((k, v) for k, v in opts.items() if not k.startswith('@')) - yield pformat(opts) - - elif name in ('m', 'mix', 'mixin', 'f', 'func', 'funct', 'function'): - if name.startswith('m'): - funcs = dict(ns._mixins) - elif name.startswith('f'): - funcs = dict(ns._functions) - if code == '*': - pass - elif code: - funcs = dict((k, v) for k, v in funcs.items() if code in k[0]) - else: - pass - # TODO print source when possible - yield pformat(funcs) - continue - elif s.startswith('$') and (':' in s or '=' in s): - prop, value = [a.strip() for a in _prop_split_re.split(s, 1)] - prop = self.calculator.do_glob_math(prop) - value = self.calculator.calculate(value) - self.namespace.set_variable(prop, value) - continue - - # TODO respect compress? - try: - yield(self.calculator.calculate(s).render()) - except (SyntaxError, SassEvaluationError) as e: - print("%s" % e, file=sys.stderr) - - -if __name__ == "__main__": - main() diff --git a/libs/scss/types.py b/libs/scss/types.py deleted file mode 100644 index aafbab4e..00000000 --- a/libs/scss/types.py +++ /dev/null @@ -1,1128 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import colorsys -import operator - -import six - -from scss.cssdefs import COLOR_LOOKUP, COLOR_NAMES, ZEROABLE_UNITS, convert_units_to_base_units, cancel_base_units, count_base_units -from scss.util import escape - - -################################################################################ -# pyScss data types: - -class Value(object): - is_null = False - sass_type_name = u'unknown' - - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.value)) - - # Sass values are all true, except for booleans and nulls - def __bool__(self): - return True - - def __nonzero__(self): - # Py 2's name for __bool__ - return self.__bool__() - - # All Sass scalars also act like one-element spaced lists - use_comma = False - - def __iter__(self): - return iter((self,)) - - def __len__(self): - return 1 - - def __getitem__(self, key): - if key not in (-1, 0): - raise IndexError(key) - - return self - - def __contains__(self, item): - return self == item - - ### NOTE: From here on down, the operators are exposed to Sass code and - ### thus should ONLY return Sass types - - # Reasonable default for equality - def __eq__(self, other): - return Boolean( - type(self) == type(other) and self.value == other.value) - - def __ne__(self, other): - return Boolean(not self.__eq__(other)) - - # Only numbers support ordering - def __lt__(self, other): - raise TypeError("Can't compare %r with %r" % (self, other)) - - def __le__(self, other): - raise TypeError("Can't compare %r with %r" % (self, other)) - - def __gt__(self, other): - raise TypeError("Can't compare %r with %r" % (self, other)) - - def __ge__(self, other): - raise TypeError("Can't compare %r with %r" % (self, other)) - - # Math ops - def __add__(self, other): - # Default behavior is to treat both sides like strings - if isinstance(other, String): - return String(self.render() + other.value, quotes=other.quotes) - return String(self.render() + other.render()) - - def __sub__(self, other): - # Default behavior is to treat the whole expression like one string - return String(self.render() + "-" + other.render()) - - def __div__(self, other): - return String(self.render() + "/" + other.render()) - - # Sass types have no notion of floor vs true division - def __truediv__(self, other): - return self.__div__(other) - - def __floordiv__(self, other): - return self.__div__(other) - - def __mul__(self, other): - return NotImplemented - - def __pos__(self): - return String("+" + self.render()) - - def __neg__(self): - return String("-" + self.render()) - - def to_dict(self): - """Return the Python dict equivalent of this map. - - If this type can't be expressed as a map, raise. - """ - return dict(self.to_pairs()) - - def to_pairs(self): - """Return the Python list-of-tuples equivalent of this map. Note that - this is different from ``self.to_dict().items()``, because Sass maps - preserve order. - - If this type can't be expressed as a map, raise. - """ - raise ValueError("Not a map: {0!r}".format(self)) - - def render(self, compress=False): - return self.__str__() - - -class Null(Value): - is_null = True - sass_type_name = u'null' - - def __init__(self, value=None): - pass - - def __str__(self): - return self.sass_type_name - - def __repr__(self): - return "<%s()>" % (self.__class__.__name__,) - - def __hash__(self): - return hash(None) - - def __bool__(self): - return False - - def __eq__(self, other): - return Boolean(isinstance(other, Null)) - - def __ne__(self, other): - return Boolean(not self.__eq__(other)) - - def render(self, compress=False): - return self.sass_type_name - - -class Undefined(Null): - sass_type_name = u'undefined' - - def __init__(self, value=None): - pass - - def __add__(self, other): - return self - - def __radd__(self, other): - return self - - def __sub__(self, other): - return self - - def __rsub__(self, other): - return self - - def __div__(self, other): - return self - - def __rdiv__(self, other): - return self - - def __truediv__(self, other): - return self - - def __rtruediv__(self, other): - return self - - def __floordiv__(self, other): - return self - - def __rfloordiv__(self, other): - return self - - def __mul__(self, other): - return self - - def __rmul__(self, other): - return self - - def __pos__(self): - return self - - def __neg__(self): - return self - - -class Boolean(Value): - sass_type_name = u'bool' - - def __init__(self, value): - self.value = bool(value) - - def __str__(self): - return 'true' if self.value else 'false' - - def __hash__(self): - return hash(self.value) - - def __bool__(self): - return self.value - - def render(self, compress=False): - if self.value: - return 'true' - else: - return 'false' - - -class Number(Value): - sass_type_name = u'number' - - def __init__(self, amount, unit=None, unit_numer=(), unit_denom=()): - if isinstance(amount, Number): - assert not unit and not unit_numer and not unit_denom - self.value = amount.value - self.unit_numer = amount.unit_numer - self.unit_denom = amount.unit_denom - return - - if not isinstance(amount, (int, float)): - raise TypeError("Expected number, got %r" % (amount,)) - - if unit is not None: - unit_numer = unit_numer + (unit.lower(),) - - # Cancel out any convertable units on the top and bottom - numerator_base_units = count_base_units(unit_numer) - denominator_base_units = count_base_units(unit_denom) - - # Count which base units appear both on top and bottom - cancelable_base_units = {} - for unit, count in numerator_base_units.items(): - cancelable_base_units[unit] = min( - count, denominator_base_units.get(unit, 0)) - - # Actually remove the units - numer_factor, unit_numer = cancel_base_units(unit_numer, cancelable_base_units) - denom_factor, unit_denom = cancel_base_units(unit_denom, cancelable_base_units) - - # And we're done - self.unit_numer = tuple(unit_numer) - self.unit_denom = tuple(unit_denom) - self.value = amount * (numer_factor / denom_factor) - - def __repr__(self): - full_unit = ' * '.join(self.unit_numer) - if self.unit_denom: - full_unit += ' / ' - full_unit += ' * '.join(self.unit_denom) - - if full_unit: - full_unit = ' ' + full_unit - - return '<%s(%r%s)>' % (self.__class__.__name__, self.value, full_unit) - - def __hash__(self): - return hash((self.value, self.unit_numer, self.unit_denom)) - - def __int__(self): - return int(self.value) - - def __float__(self): - return float(self.value) - - def __pos__(self): - return self - - def __neg__(self): - return self * Number(-1) - - def __str__(self): - return self.render() - - def __eq__(self, other): - if not isinstance(other, Number): - return Boolean(False) - return self._compare(other, operator.__eq__, soft_fail=True) - - def __lt__(self, other): - return self._compare(other, operator.__lt__) - - def __le__(self, other): - return self._compare(other, operator.__le__) - - def __gt__(self, other): - return self._compare(other, operator.__gt__) - - def __ge__(self, other): - return self._compare(other, operator.__ge__) - - def _compare(self, other, op, soft_fail=False): - if not isinstance(other, Number): - raise TypeError("Can't compare %r and %r" % (self, other)) - - # A unitless operand is treated as though it had the other operand's - # units, and zero values can cast to anything, so in both cases the - # units can be ignored - if (self.is_unitless or other.is_unitless or - self.value == 0 or other.value == 0): - left = self - right = other - else: - left = self.to_base_units() - right = other.to_base_units() - - if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom: - if soft_fail: - # Used for equality only, where == should never fail - return Boolean(False) - else: - raise ValueError("Can't reconcile units: %r and %r" % (self, other)) - - return Boolean(op(round(left.value, 5), round(right.value, 5))) - - def __pow__(self, exp): - if not isinstance(exp, Number): - raise TypeError("Can't raise %r to power %r" % (self, exp)) - if not exp.is_unitless: - raise TypeError("Exponent %r cannot have units" % (exp,)) - - if self.is_unitless: - return Number(self.value ** exp.value) - - # Units can only be exponentiated to integral powers -- what's the - # square root of 'px'? (Well, it's sqrt(px), but supporting that is - # a bit out of scope.) - if exp.value != int(exp.value): - raise ValueError("Can't raise units of %r to non-integral power %r" % (self, exp)) - - return Number( - self.value ** int(exp.value), - unit_numer=self.unit_numer * int(exp.value), - unit_denom=self.unit_denom * int(exp.value), - ) - - def __mul__(self, other): - if not isinstance(other, Number): - return NotImplemented - - amount = self.value * other.value - numer = self.unit_numer + other.unit_numer - denom = self.unit_denom + other.unit_denom - - return Number(amount, unit_numer=numer, unit_denom=denom) - - def __div__(self, other): - if not isinstance(other, Number): - return NotImplemented - - amount = self.value / other.value - numer = self.unit_numer + other.unit_denom - denom = self.unit_denom + other.unit_numer - - return Number(amount, unit_numer=numer, unit_denom=denom) - - def __add__(self, other): - # Numbers auto-cast to strings when added to other strings - if isinstance(other, String): - return String(self.render(), quotes=None) + other - - return self._add_sub(other, operator.add) - - def __sub__(self, other): - return self._add_sub(other, operator.sub) - - def _add_sub(self, other, op): - """Implements both addition and subtraction.""" - if not isinstance(other, Number): - return NotImplemented - - # If either side is unitless, inherit the other side's units. Skip all - # the rest of the conversion math, too. - if self.is_unitless or other.is_unitless: - return Number( - op(self.value, other.value), - unit_numer=self.unit_numer or other.unit_numer, - unit_denom=self.unit_denom or other.unit_denom, - ) - - # Likewise, if either side is zero, it can auto-cast to any units - if self.value == 0: - return Number( - op(self.value, other.value), - unit_numer=other.unit_numer, - unit_denom=other.unit_denom, - ) - elif other.value == 0: - return Number( - op(self.value, other.value), - unit_numer=self.unit_numer, - unit_denom=self.unit_denom, - ) - - # Reduce both operands to the same units - left = self.to_base_units() - right = other.to_base_units() - - if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom: - raise ValueError("Can't reconcile units: %r and %r" % (self, other)) - - new_amount = op(left.value, right.value) - - # Convert back to the left side's units - if left.value != 0: - new_amount = new_amount * self.value / left.value - - return Number(new_amount, unit_numer=self.unit_numer, unit_denom=self.unit_denom) - - ### Helper methods, mostly used internally - - def to_base_units(self): - """Convert to a fixed set of "base" units. The particular units are - arbitrary; what's important is that they're consistent. - - Used for addition and comparisons. - """ - # Convert to "standard" units, as defined by the conversions dict above - amount = self.value - - numer_factor, numer_units = convert_units_to_base_units(self.unit_numer) - denom_factor, denom_units = convert_units_to_base_units(self.unit_denom) - - return Number( - amount * numer_factor / denom_factor, - unit_numer=numer_units, - unit_denom=denom_units, - ) - - ### Utilities for public consumption - - @classmethod - def wrap_python_function(cls, fn): - """Wraps an unary Python math function, translating the argument from - Sass to Python on the way in, and vice versa for the return value. - - Used to wrap simple Python functions like `ceil`, `floor`, etc. - """ - def wrapped(sass_arg): - # TODO enforce no units for trig? - python_arg = sass_arg.value - python_ret = fn(python_arg) - sass_ret = cls( - python_ret, - unit_numer=sass_arg.unit_numer, - unit_denom=sass_arg.unit_denom) - return sass_ret - - return wrapped - - def to_python_index(self, length, check_bounds=True, circular=False): - """Return a plain Python integer appropriate for indexing a sequence of - the given length. Raise if this is impossible for any reason - whatsoever. - """ - if not self.is_unitless: - raise ValueError("Index cannot have units: {0!r}".format(self)) - - ret = int(self.value) - if ret != self.value: - raise ValueError("Index must be an integer: {0!r}".format(ret)) - - if ret == 0: - raise ValueError("Index cannot be zero") - - if check_bounds and not circular and abs(ret) > length: - raise ValueError("Index {0!r} out of bounds for length {1}".format(ret, length)) - - if ret > 0: - ret -= 1 - - if circular: - ret = ret % length - - return ret - - @property - def has_simple_unit(self): - """Returns True iff the unit is expressible in CSS, i.e., has no - denominator and at most one unit in the numerator. - """ - return len(self.unit_numer) <= 1 and not self.unit_denom - - def is_simple_unit(self, unit): - """Return True iff the unit is simple (as above) and matches the given - unit. - """ - if self.unit_denom or len(self.unit_numer) > 1: - return False - - if not self.unit_numer: - # Empty string historically means no unit - return unit == '' - - return self.unit_numer[0] == unit - - @property - def is_unitless(self): - return not self.unit_numer and not self.unit_denom - - def render(self, compress=False): - if not self.has_simple_unit: - raise ValueError("Can't express compound units in CSS: %r" % (self,)) - - if self.unit_numer: - unit = self.unit_numer[0] - else: - unit = '' - - value = self.value - if compress and unit in ZEROABLE_UNITS and value == 0: - return '0' - - if value == 0: # -0.0 is plain 0 - value = 0 - - val = "%0.05f" % round(value, 5) - val = val.rstrip('0').rstrip('.') - - if compress and val.startswith('0.'): - # Strip off leading zero when compressing - val = val[1:] - - return val + unit - - -class List(Value): - """A list of other values. May be delimited by commas or spaces. - - Lists of one item don't make much sense in CSS, but can exist in Sass. Use ...... - - Lists may also contain zero items, but these are forbidden from appearing - in CSS output. - """ - - sass_type_name = u'list' - - def __init__(self, iterable, separator=None, use_comma=None, is_literal=False): - if isinstance(iterable, List): - iterable = iterable.value - - if not isinstance(iterable, (list, tuple)): - raise TypeError("Expected list, got %r" % (iterable,)) - - self.value = list(iterable) - - for item in self.value: - if not isinstance(item, Value): - raise TypeError("Expected a Sass type, got %r" % (item,)) - - # TODO remove separator argument entirely - if use_comma is None: - self.use_comma = separator == "," - else: - self.use_comma = use_comma - - self.is_literal = is_literal - - @classmethod - def maybe_new(cls, values, use_comma=True): - """If `values` contains only one item, return that item. Otherwise, - return a List as normal. - """ - if len(values) == 1: - return values[0] - else: - return cls(values, use_comma=use_comma) - - def maybe(self): - """If this List contains only one item, return it. Otherwise, return - the List. - """ - if len(self.value) == 1: - return self.value[0] - else: - return self - - @classmethod - def from_maybe(cls, values, use_comma=True): - """If `values` appears to not be a list, return a list containing it. - Otherwise, return a List as normal. - """ - if values is None: - values = [] - return values - - @classmethod - def from_maybe_starargs(cls, args, use_comma=True): - """If `args` has one element which appears to be a list, return it. - Otherwise, return a list as normal. - - Mainly used by Sass function implementations that predate `...` - support, so they can accept both a list of arguments and a single list - stored in a variable. - """ - if len(args) == 1: - if isinstance(args[0], cls): - return args[0] - elif isinstance(args[0], (list, tuple)): - return cls(args[0], use_comma=use_comma) - - return cls(args, use_comma=use_comma) - - def __repr__(self): - return "" % ( - self.value, - self.delimiter(compress=True), - ) - - def __hash__(self): - return hash((tuple(self.value), self.use_comma)) - - def delimiter(self, compress=False): - if self.use_comma: - if compress: - return ',' - else: - return ', ' - else: - return ' ' - - def __len__(self): - return len(self.value) - - def __str__(self): - return self.render() - - def __iter__(self): - return iter(self.value) - - def __contains__(self, item): - return item in self.value - - def __getitem__(self, key): - return self.value[key] - - def to_pairs(self): - pairs = [] - for item in self: - if len(item) != 2: - return super(List, self).to_pairs() - - pairs.append(tuple(item)) - - return pairs - - def render(self, compress=False): - if not self.value: - raise ValueError("Can't render empty list as CSS") - - delim = self.delimiter(compress) - - if self.is_literal: - value = self.value - else: - # Non-literal lists have nulls stripped - value = [item for item in self.value if not item.is_null] - # Non-empty lists containing only nulls become nothing, just like - # single nulls - if not value: - return '' - - return delim.join( - item.render(compress=compress) - for item in value - ) - - # DEVIATION: binary ops on lists and scalars act element-wise - def __add__(self, other): - if isinstance(other, List): - max_list, min_list = (self, other) if len(self) > len(other) else (other, self) - return List([item + max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) - - elif isinstance(other, String): - # UN-DEVIATION: adding a string should fall back to canonical - # behavior of string addition - return super(List, self).__add__(other) - - else: - return List([item + other for item in self], use_comma=self.use_comma) - - def __sub__(self, other): - if isinstance(other, List): - max_list, min_list = (self, other) if len(self) > len(other) else (other, self) - return List([item - max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) - - return List([item - other for item in self], use_comma=self.use_comma) - - def __mul__(self, other): - if isinstance(other, List): - max_list, min_list = (self, other) if len(self) > len(other) else (other, self) - max_list, min_list = (self, other) if len(self) > len(other) else (other, self) - return List([item * max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) - - return List([item * other for item in self], use_comma=self.use_comma) - - def __div__(self, other): - if isinstance(other, List): - max_list, min_list = (self, other) if len(self) > len(other) else (other, self) - return List([item / max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) - - return List([item / other for item in self], use_comma=self.use_comma) - - def __pos__(self): - return self - - def __neg__(self): - return List([-item for item in self], use_comma=self.use_comma) - - -def _constrain(value, lb=0, ub=1): - """Helper for Color constructors. Constrains a value to a range.""" - if value < lb: - return lb - elif value > ub: - return ub - else: - return value - - -class Color(Value): - sass_type_name = u'color' - original_literal = None - - def __init__(self, tokens): - self.tokens = tokens - self.value = (0, 0, 0, 1) - if tokens is None: - self.value = (0, 0, 0, 1) - elif isinstance(tokens, Color): - self.value = tokens.value - else: - raise TypeError("Can't make Color from %r" % (tokens,)) - - ### Alternate constructors - - @classmethod - def from_rgb(cls, red, green, blue, alpha=1.0, original_literal=None): - red = _constrain(red) - green = _constrain(green) - blue = _constrain(blue) - alpha = _constrain(alpha) - - self = cls.__new__(cls) # TODO - self.tokens = None - # TODO really should store these things internally as 0-1, but can't - # until stuff stops examining .value directly - self.value = (red * 255.0, green * 255.0, blue * 255.0, alpha) - - if original_literal is not None: - self.original_literal = original_literal - - return self - - @classmethod - def from_hsl(cls, hue, saturation, lightness, alpha=1.0): - hue = _constrain(hue) - saturation = _constrain(saturation) - lightness = _constrain(lightness) - alpha = _constrain(alpha) - - r, g, b = colorsys.hls_to_rgb(hue, lightness, saturation) - return cls.from_rgb(r, g, b, alpha) - - @classmethod - def from_hex(cls, hex_string, literal=False): - if not hex_string.startswith('#'): - raise ValueError("Expected #abcdef, got %r" % (hex_string,)) - - if literal: - original_literal = hex_string - else: - original_literal = None - - hex_string = hex_string[1:] - - # Always include the alpha channel - if len(hex_string) == 3: - hex_string += 'f' - elif len(hex_string) == 6: - hex_string += 'ff' - - # Now there should be only two possibilities. Normalize to a list of - # two hex digits - if len(hex_string) == 4: - chunks = [ch * 2 for ch in hex_string] - elif len(hex_string) == 8: - chunks = [ - hex_string[0:2], hex_string[2:4], hex_string[4:6], hex_string[6:8] - ] - - rgba = [int(ch, 16) / 255 for ch in chunks] - return cls.from_rgb(*rgba, original_literal=original_literal) - - @classmethod - def from_name(cls, name): - """Build a Color from a CSS color name.""" - self = cls.__new__(cls) # TODO - self.original_literal = name - - r, g, b, a = COLOR_NAMES[name] - - self.value = r, g, b, a - return self - - ### Accessors - - @property - def rgb(self): - # TODO: deprecate, relies on internals - return tuple(self.value[:3]) - - @property - def rgba(self): - return ( - self.value[0] / 255, - self.value[1] / 255, - self.value[2] / 255, - self.value[3], - ) - - @property - def hsl(self): - rgba = self.rgba - h, l, s = colorsys.rgb_to_hls(*rgba[:3]) - return h, s, l - - @property - def alpha(self): - return self.value[3] - - @property - def rgba255(self): - return ( - int(self.value[0] * 1 + 0.5), - int(self.value[1] * 1 + 0.5), - int(self.value[2] * 1 + 0.5), - int(self.value[3] * 255 + 0.5), - ) - - def __repr__(self): - return '<%s(%s)>' % (self.__class__.__name__, repr(self.value)) - - def __hash__(self): - return hash(self.value) - - def __eq__(self, other): - if not isinstance(other, Color): - return Boolean(False) - - # Scale channels to 255 and round to integers; this allows only 8-bit - # color, but Ruby sass makes the same assumption, and otherwise it's - # easy to get lots of float errors for HSL colors. - left = tuple(round(n) for n in self.rgba255) - right = tuple(round(n) for n in other.rgba255) - return Boolean(left == right) - - def __add__(self, other): - if isinstance(other, (Color, Number)): - return self._operate(other, operator.add) - else: - return super(Color, self).__add__(other) - - def __sub__(self, other): - if isinstance(other, (Color, Number)): - return self._operate(other, operator.sub) - else: - return super(Color, self).__sub__(other) - - def __mul__(self, other): - if isinstance(other, (Color, Number)): - return self._operate(other, operator.mul) - else: - return super(Color, self).__mul__(other) - - def __div__(self, other): - if isinstance(other, (Color, Number)): - return self._operate(other, operator.div) - else: - return super(Color, self).__div__(other) - - def _operate(self, other, op): - if isinstance(other, Number): - if not other.is_unitless: - raise ValueError("Expected unitless Number, got %r" % (other,)) - - other_rgb = (other.value,) * 3 - elif isinstance(other, Color): - if self.alpha != other.alpha: - raise ValueError("Alpha channels must match between %r and %r" - % (self, other)) - - other_rgb = other.rgb - else: - raise TypeError("Expected Color or Number, got %r" % (other,)) - - new_rgb = [ - min(255., max(0., op(left, right))) - # for from_rgb - / 255. - for (left, right) in zip(self.rgb, other_rgb) - ] - - return Color.from_rgb(*new_rgb, alpha=self.alpha) - - def render(self, compress=False): - """Return a rendered representation of the color. If `compress` is - true, the shortest possible representation is used; otherwise, named - colors are rendered as names and all others are rendered as hex (or - with the rgba function). - """ - - if not compress and self.original_literal: - return self.original_literal - - candidates = [] - - # TODO this assumes CSS resolution is 8-bit per channel, but so does - # Ruby. - r, g, b, a = self.value - r, g, b = int(round(r)), int(round(g)), int(round(b)) - - # Build a candidate list in order of preference. If `compress` is - # True, the shortest candidate is used; otherwise, the first candidate - # is used. - - # Try color name - key = r, g, b, a - if key in COLOR_LOOKUP: - candidates.append(COLOR_LOOKUP[key]) - - if a == 1: - # Hex is always shorter than function notation - if all(ch % 17 == 0 for ch in (r, g, b)): - candidates.append("#%1x%1x%1x" % (r // 17, g // 17, b // 17)) - else: - candidates.append("#%02x%02x%02x" % (r, g, b)) - else: - # Can't use hex notation for RGBA - if compress: - sp = '' - else: - sp = ' ' - candidates.append("rgba(%d,%s%d,%s%d,%s%.2g)" % (r, sp, g, sp, b, sp, a)) - - if compress: - return min(candidates, key=len) - else: - return candidates[0] - - -# TODO be unicode-clean and delete this nonsense -DEFAULT_STRING_ENCODING = "utf8" - - -class String(Value): - """Represents both CSS quoted string values and CSS identifiers (such as - `left`). - - Makes no distinction between single and double quotes, except that the same - quotes are preserved on string literals that pass through unmodified. - Otherwise, double quotes are used. - """ - - sass_type_name = u'string' - - def __init__(self, value, quotes='"'): - if isinstance(value, String): - # TODO unclear if this should be here, but many functions rely on - # it - value = value.value - elif isinstance(value, Number): - # TODO this may only be necessary in the case of __radd__ and - # number values - value = str(value) - - if isinstance(value, six.binary_type): - value = value.decode(DEFAULT_STRING_ENCODING) - - if not isinstance(value, six.text_type): - raise TypeError("Expected string, got {0!r}".format(value)) - - # TODO probably disallow creating an unquoted string outside a - # set of chars like [-a-zA-Z0-9]+ - - if six.PY3: - self.value = value - else: - # TODO well, at least 3 uses unicode everywhere - self.value = value.encode(DEFAULT_STRING_ENCODING) - self.quotes = quotes - - @classmethod - def unquoted(cls, value): - """Helper to create a string with no quotes.""" - return cls(value, quotes=None) - - def __hash__(self): - return hash(self.value) - - def __str__(self): - if self.quotes: - return self.quotes + escape(self.value) + self.quotes - else: - return self.value - - def __repr__(self): - if self.quotes != '"': - quotes = ', quotes=%r' % self.quotes - else: - quotes = '' - return '<%s(%s%s)>' % (self.__class__.__name__, repr(self.value), quotes) - - def __eq__(self, other): - return Boolean(isinstance(other, String) and self.value == other.value) - - def __add__(self, other): - if isinstance(other, String): - other_value = other.value - else: - other_value = other.render() - - return String( - self.value + other_value, - quotes='"' if self.quotes else None) - - def __mul__(self, other): - # DEVIATION: Ruby Sass doesn't do this, because Ruby doesn't. But - # Python does, and in Ruby Sass it's just fatal anyway. - if not isinstance(other, Number): - return super(String, self).__mul__(other) - - if not other.is_unitless: - raise TypeError("Can only multiply strings by unitless numbers") - - n = other.value - if n != int(n): - raise ValueError("Can only multiply strings by integers") - - return String(self.value * int(other.value), quotes=self.quotes) - - def render(self, compress=False): - return self.__str__() - - -class Map(Value): - sass_type_name = u'map' - - def __init__(self, pairs, index=None): - self.pairs = tuple(pairs) - - if index is None: - self.index = {} - for key, value in pairs: - self.index[key] = value - else: - self.index = index - - def __repr__(self): - return "" % (", ".join("%s: %s" % pair for pair in self.pairs),) - - def __hash__(self): - return hash(self.pairs) - - def __len__(self): - return len(self.pairs) - - def __iter__(self): - return iter(self.pairs) - - def __getitem__(self, index): - return List(self.pairs[index], use_comma=True) - - def __eq__(self, other): - try: - return self.pairs == other.to_pairs() - except ValueError: - return NotImplemented - - def to_dict(self): - return self.index - - def to_pairs(self): - return self.pairs - - def render(self, compress=False): - raise TypeError("Cannot render map %r as CSS" % (self,)) - - -def expect_type(value, types, unit=any): - if not isinstance(value, types): - if isinstance(types, type): - types = (type,) - sass_type_names = list(set(t.sass_type_name for t in types)) - sass_type_names.sort() - - # Join with commas in English fashion - if len(sass_type_names) == 1: - sass_type = sass_type_names[0] - elif len(sass_type_names) == 2: - sass_type = u' or '.join(sass_type_names) - else: - sass_type = u', '.join(sass_type_names[:-1]) - sass_type += u', or ' + sass_type_names[-1] - - raise TypeError("Expected %s, got %r" % (sass_type, value)) - - if unit is not any and isinstance(value, Number): - if unit is None and not value.is_unitless: - raise ValueError("Expected unitless number, got %r" % (value,)) - - elif unit == '%' and not ( - value.is_unitless or value.is_simple_unit('%')): - raise ValueError("Expected unitless number or percentage, got %r" % (value,)) diff --git a/libs/scss/util.py b/libs/scss/util.py deleted file mode 100644 index 0bcb13eb..00000000 --- a/libs/scss/util.py +++ /dev/null @@ -1,146 +0,0 @@ -from __future__ import absolute_import -from __future__ import print_function - -import re -import sys -import time - -import six - -from scss import config - - -def split_params(params): - params = params.split(',') or [] - if params: - final_params = [] - param = params.pop(0) - try: - while True: - while param.count('(') != param.count(')'): - try: - param = param + ',' + params.pop(0) - except IndexError: - break - final_params.append(param) - param = params.pop(0) - except IndexError: - pass - params = final_params - return params - - -def dequote(s): - if s and s[0] in ('"', "'") and s[-1] == s[0]: - s = s[1:-1] - s = unescape(s) - return s - - -def depar(s): - while s and s[0] == '(' and s[-1] == ')': - s = s[1:-1] - return s - - -def to_str(num): - try: - render = num.render - except AttributeError: - pass - else: - return render() - - if isinstance(num, dict): - s = sorted(num.items()) - sp = num.get('_', '') - return (sp + ' ').join(to_str(v) for n, v in s if n != '_') - elif isinstance(num, float): - num = ('%0.05f' % round(num, 5)).rstrip('0').rstrip('.') - return num - elif isinstance(num, bool): - return 'true' if num else 'false' - elif num is None: - return '' - return str(num) - - -def to_float(num): - if isinstance(num, (float, int)): - return float(num) - num = to_str(num) - if num and num[-1] == '%': - return float(num[:-1]) / 100.0 - else: - return float(num) - - -def escape(s): - return re.sub(r'''(["'])''', r'\\\1', s) - - -def unescape(s): - return re.sub(r'''\\(['"])''', r'\1', s) - - -def normalize_var(var): - """Sass defines `foo_bar` and `foo-bar` as being identical, both in - variable names and functions/mixins. This normalizes everything to use - dashes. - """ - return var.replace('_', '-') - - -################################################################################ -# Function timing decorator -profiling = {} - - -def print_timing(level=0): - def _print_timing(func): - if config.VERBOSITY: - def wrapper(*args, **kwargs): - if config.VERBOSITY >= level: - t1 = time.time() - res = func(*args, **kwargs) - t2 = time.time() - profiling.setdefault(func.func_name, 0) - profiling[func.func_name] += (t2 - t1) - return res - else: - return func(*args, **kwargs) - return wrapper - else: - return func - return _print_timing - - -################################################################################ -# Profiler decorator -def profile(fn): - import cProfile - import pstats - def wrapper(*args, **kwargs): - profiler = cProfile.Profile() - stream = six.StringIO() - profiler.enable() - try: - res = fn(*args, **kwargs) - finally: - profiler.disable() - stats = pstats.Stats(profiler, stream=stream) - stats.sort_stats('time') - print >>stream, "" - print >>stream, "=" * 100 - print >>stream, "Stats:" - stats.print_stats() - print >>stream, "=" * 100 - print >>stream, "Callers:" - stats.print_callers() - print >>stream, "=" * 100 - print >>stream, "Callees:" - stats.print_callees() - print >>sys.stderr, stream.getvalue() - stream.close() - return res - return wrapper