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] except IndexError: filetime = None else: filetime = getmtime(_file, _storage) if filetime is None: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) filetime = getmtime(_path) if filetime is None: filetime = 'NA' BASE_URL = config.STATIC_URL url = '%s%s' % (BASE_URL, filepath) if cache_buster: url = add_cache_buster(url, filetime) if only_path: return String.unquoted(url) else: return Url.unquoted(url)
def test_comparison_stringerific(): abc = String('abc') xyz = String('xyz') assert abc == abc assert abc != xyz assert not abc == xyz assert not abc != abc # Interaction with other types assert Number(123) != String('123') assert String('123') != Number(123) # Sass strings don't support ordering with pytest.raises(TypeError): abc < xyz with pytest.raises(TypeError): abc <= xyz with pytest.raises(TypeError): abc > xyz with pytest.raises(TypeError): abc >= xyz with pytest.raises(TypeError): Number(123) < String('123')
def _font_url(path, only_path=False, cache_buster=True, inline=False): filepath = String.unquoted(path).value path = None 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())) if inline: path = _storage.open(_file) except: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) if os.path.exists(_path): filetime = int(os.path.getmtime(_path)) if inline: path = open(_path, 'rb') else: filetime = 'NA' BASE_URL = config.STATIC_URL if path and inline: mime_type = mimetypes.guess_type(filepath)[0] url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read()) else: url = '%s%s' % (BASE_URL, filepath) if cache_buster: url = add_cache_buster(url, filetime) if not only_path: url = 'url("%s")' % escape(url) return String.unquoted(url)
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)
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)])
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=',')
def test_parse_strings(calc): # Escapes in barewords are preserved. assert calc('auto\\9') == String.unquoted('auto\\9') # Escapes in quoted strings are expanded. assert calc('"\\2022"') == String("•", quotes='"') assert calc('"\\2022"').render() == '"•"'
def _font_files(args, inline): if args == (): return String.unquoted("") fonts = [] args_len = len(args) skip_next = False for index, arg in enumerate(args): 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(List([ _font_url(arg, inline=inline), Function(FONT_TYPES[font_type.value], 'format'), ], use_comma=False)) else: raise Exception('Could not determine font type for "%s"' % arg.value) else: skip_next = False return List(fonts, separator=',')
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 + six.text_type(i))) else: ret.append(Number(i)) return List(ret, use_comma=True)
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='"')
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)
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)
def _font_files(args, inline): if args == (): return String.unquoted("") fonts = [] args_len = len(args) skip_next = False for index, arg in enumerate(args): 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( List([ _font_url(arg, inline=inline), Function(FONT_TYPES[font_type.value], 'format'), ], use_comma=False)) else: raise Exception('Could not determine font type for "%s"' % arg.value) else: skip_next = False return List(fonts, separator=',')
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=',')
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] except IndexError: filetime = None else: filetime = getmtime(_file, _storage) if filetime is None: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) filetime = getmtime(_path) if filetime is None: 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)
def test_parse(calc): # Tests for some general parsing. assert calc('foo !important bar') == List([ String('foo'), String('!important'), String('bar'), ])
def test_quote(): # Examples from the Ruby docs ret = calc('quote("foo")') assert ret == String('foo') assert ret.quotes == '"' ret = calc('quote(foo)') assert ret == String('foo') assert ret.quotes == '"'
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
def test_unquote(): # Examples from the Ruby docs ret = calc('unquote("foo")') assert ret == String('foo') assert ret.quotes is None ret = calc('unquote(foo)') assert ret == String('foo') assert ret.quotes is None assert calc('unquote((one, two, three))') == String('one, two, three')
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] except IndexError: filetime = None else: filetime = getmtime(_file, _storage) if filetime is None: filetime = 'NA' elif inline: file = _storage.open(_file) else: _path = os.path.join(FONTS_ROOT, filepath.strip('/')) filetime = getmtime(_path) if filetime is None: filetime = 'NA' elif inline: file = open(_path, 'rb') 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 try: mime = FONT_TYPES[font_type] except KeyError: 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 = make_data_url((mime if '/' in mime else 'font/%s' % mime), 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 only_path: return String.unquoted(url) else: return Url.unquoted(url)
def test_functions(calc): calc = Calculator(CoreExtension.namespace).calculate assert calc('grayscale(red)') == Color.from_rgb(0.5, 0.5, 0.5) assert calc('grayscale(1)') == String( 'grayscale(1)', quotes=None) # Misusing css built-in functions (with scss counterpart) assert calc('skew(1)') == String( 'skew(1)', quotes=None) # Missing css-only built-in functions with pytest.raises(SassEvaluationError): calc('unitless("X")') # Misusing non-css built-in scss funtions
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('')
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] except IndexError: filetime = None else: filetime = getmtime(_file, _storage) if filetime is None: filetime = 'NA' elif inline: file = _storage.open(_file) else: _path = os.path.join(FONTS_ROOT, filepath.strip('/')) filetime = getmtime(_path) if filetime is None: filetime = 'NA' elif inline: file = open(_path, 'rb') 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 try: mime = FONT_TYPES[font_type] except KeyError: 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 = make_data_url( (mime if '/' in mime else 'font/%s' % mime), 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 only_path: return String.unquoted(url) else: return Url.unquoted(url)
def join_file_segments(*segments): """Join path parts into a single path, using the appropriate OS-specific delimiter. """ parts = [] for segment in segments: expect_type(segment, String) parts.append(segment.value) if parts: return String(os.path.join(*parts)) else: return String('')
def glyph_code(sheet, glyph): sheet = sheet.render() font_sheet = font_sheets.get(sheet) glyph_name = String.unquoted(glyph).value glyph = font_sheet and font_sheet.get(glyph_name) if not font_sheet: log.error("No font sheet found: %s", sheet, extra={'stack': True}) elif not glyph: log.error("No glyph found: %s in %s", glyph_name, font_sheet['*n*'], extra={'stack': True}) return String('\\%x' % glyph[1])
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 test_reference_operations(): """Test the example expressions in the reference document: http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#operations """ # TODO: break this into its own file and add the entire reference guide # Need to build the calculator manually to get at its namespace, and need # to use calculate() instead of evaluate_expression() so interpolation # works ns = CoreExtension.namespace.derive() calc = Calculator(ns).calculate # Simple example assert calc('1in + 8pt') == Number(1.1111111111111112, "in") # Division ns.set_variable('$width', Number(1000, "px")) ns.set_variable('$font-size', Number(12, "px")) ns.set_variable('$line-height', Number(30, "px")) assert calc('10px/8px') == String('10px / 8px') # plain CSS; no division assert calc('$width/2') == Number(500, "px") # uses a variable; does division assert calc('(500px/2)') == Number(250, "px") # uses parens; does division assert calc('5px + 8px/2px') == Number(9, "px") # uses +; does division # TODO, again: Ruby Sass correctly renders this without spaces assert calc('#{$font-size}/#{$line-height}') == String('12px / 30px') # uses #{}; does no division # Color operations ns.set_variable('$translucent-red', Color.from_rgb(1, 0, 0, 0.5)) ns.set_variable('$green', Color.from_name('lime')) assert calc('#010203 + #040506') == Color.from_hex('#050709') assert calc('#010203 * 2') == Color.from_hex('#020406') assert calc( 'rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75)') == Color.from_rgb( 1, 1, 0, 0.75) assert calc('opacify($translucent-red, 0.3)') == Color.from_rgb( 1, 0, 0, 0.8) assert calc('transparentize($translucent-red, 0.25)') == Color.from_rgb( 1, 0, 0, 0.25) assert calc( "progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#{ie-hex-str($green)}', endColorstr='#{ie-hex-str($translucent-red)}')" ).render( ) == "progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#FF00FF00', endColorstr='#80FF0000')" # String operations ns.set_variable('$value', Null()) assert_strict_string_eq(calc('e + -resize'), String('e-resize', quotes=None)) assert_strict_string_eq(calc('"Foo " + Bar'), String('Foo Bar', quotes='"')) assert_strict_string_eq(calc('sans- + "serif"'), String('sans-serif', quotes=None)) assert calc('3px + 4px auto') == List( [Number(7, "px"), String('auto', quotes=None)]) assert_strict_string_eq(calc('"I ate #{5 + 10} pies!"'), String('I ate 15 pies!', quotes='"')) assert_strict_string_eq(calc('"I ate #{$value} pies!"'), String('I ate pies!', quotes='"'))
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('')
def test_parse_bang_important(calc): # The !important flag is treated as part of a spaced list. assert calc('40px !important') == List([ Number(40, 'px'), String.unquoted('!important'), ], use_comma=False) # And is allowed anywhere in the string. assert calc('foo !important bar') == List([ String('foo'), String('!important'), String('bar'), ], use_comma=False) # And may have space before the !. assert calc('40px ! important') == List([ Number(40, 'px'), String.unquoted('!important'), ], use_comma=False)
def join(lst1, lst2, separator=String.unquoted('auto')): expect_type(separator, String) ret = [] ret.extend(List.from_maybe(lst1)) ret.extend(List.from_maybe(lst2)) if separator.value == 'comma': use_comma = True elif separator.value == 'space': use_comma = False elif separator.value == 'auto': # The Sass docs are slightly misleading here, but the algorithm is: use # the delimiter from the first list that has at least 2 items, or # default to spaces. if len(lst1) > 1: use_comma = lst1.use_comma elif len(lst2) > 1: use_comma = lst2.use_comma else: use_comma = False else: raise ValueError("separator for join() must be comma, space, or auto") return List(ret, use_comma=use_comma)
def grayscale(color): if isinstance(color, Number): # grayscale(n) and grayscale(n%) are CSS3 filters and should be left # intact, but only when using the "a" spelling return String.unquoted("grayscale(%s)" % (color.render(),)) else: return greyscale(color)
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)])
def __init__( self, scss_vars=None, scss_opts=None, scss_files=None, super_selector='', live_errors=False, library=None, func_registry=None, search_paths=None): self.super_selector = 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 or {} self._scss_files = scss_files 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
def sprite_does_not_have_parent(map, sprite): 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 there is no selector, the sprite does not have any parents return Boolean(not sprite[4])
def __init__( self, scss_vars=None, scss_opts=None, scss_files=None, super_selector='', live_errors=False, library=ALL_BUILTINS_LIBRARY, func_registry=None, search_paths=None): self.super_selector = 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 or {} 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
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 evaluate(self, calculator, divide=False): left = self.left.evaluate(calculator, divide=True) right = self.right.evaluate(calculator, divide=True) # Determine whether to actually evaluate, or just print the operator # literally. literal = False # If either operand starts with an interpolation, treat the whole # shebang as literal. if any(isinstance(operand, Interpolation) and operand.parts[0] == '' for operand in (self.left, self.right)): literal = True # Special handling of division: treat it as a literal slash if both # operands are literals, there are no parentheses, and this isn't 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. elif ( self.op is operator.truediv and not divide and isinstance(self.left, Literal) and isinstance(self.right, Literal) ): literal = True if literal: # TODO we don't currently preserve the spacing, whereas Sass # remembers whether there was space on either side op = " {0} ".format(self.OPERATORS[self.op]) return String.unquoted(left.render() + op + right.render()) return self.op(left, right)
def calculate(self, expression, divide=False): expression = self.evaluate_expression(expression, divide=divide) if expression is None: return String.unquoted(self.apply_vars(expression)) return expression
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")
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('*'))))
def style_scss(self, *path): css_namespace = Namespace() for key, value in self.settings['keys'].items(): if isinstance(value, LCText): css_value = String(value) elif isinstance(value, LCColour): css_value = Color.from_hex(value) elif isinstance(value, LCBool): css_value = Boolean(value.simple()) elif isinstance(value, LCSpin): css_value = Number(value.simple()) else: raise ValueError("Unable to find comparable values") css_namespace.set_variable('${}'.format(key), css_value) cherrypy.response.headers['Content-Type'] = 'text/css' with open(os.path.join(self.settings['location'], *path), 'r') as css: css_content = css.read() compiler = Compiler(namespace=css_namespace, output_style='nested') # Something wrong with PyScss, # Syntax error: Found u'100%' but expected one of ADD. # Doesn't happen on next attempt, so we are doing bad thing attempts = 0 while attempts < 100: try: attempts += 1 ret_string = compiler.compile_string(css_content) return ret_string except Exception as exc: if attempts == 100: log.debug(exc)
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)
def _font_files(args, inline): args = List.from_maybe_starargs(args) n = 0 params = [[], []] for arg in args: if isinstance(arg, List): if len(arg) == 2: if n % 2 == 1: params[1].append(None) n += 1 params[0].append(arg[0]) params[1].append(arg[1]) n += 2 else: for arg2 in arg: params[n % 2].append(arg2) n += 1 else: params[n % 2].append(arg) n += 1 len0 = len(params[0]) len1 = len(params[1]) if len1 < len0: params[1] += [None] * (len0 - len1) elif len0 < len1: params[0] += [None] * (len1 - len0) fonts = [] for font, format in zip(params[0], params[1]): if format: fonts.append('%s format("%s")' % (_font_url(font, inline=inline), String.unquoted(format).value)) else: fonts.append(_font_url(font, inline=inline)) return List(fonts)
def calculate(self, expression, divide=False): result = self.evaluate_expression(expression, divide=divide) if result is None: return String.unquoted(self.apply_vars(expression)) return result
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] except IndexError: pass else: path = _storage.open(_file) else: _path = os.path.join(IMAGES_ROOT, filepath.strip(os.sep)) 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')
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] except IndexError: pass else: path = _storage.open(_file) 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')
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 SassMissingDependency('PIL', 'image manipulation') image_size_cache = _get_cache('image_size_cache') 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] except IndexError: pass else: path = _storage.open(_file) else: _path = os.path.join(IMAGES_ROOT, filepath.strip(os.sep)) 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')
def has_glyph(sheet, glyph): sheet = sheet.render() font_sheet = font_sheets.get(sheet) glyph_name = String.unquoted(glyph).value glyph = font_sheet and font_sheet.get(glyph_name) if not font_sheet: log.error("No font sheet found: %s", sheet, extra={'stack': True}) return Boolean(bool(glyph))
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 has_sprite(map, sprite): 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}) return Boolean(bool(sprite))
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('')