コード例 #1
0
ファイル: helpers.py プロジェクト: 4teamwork/pyScss
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)
コード例 #2
0
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')
コード例 #3
0
ファイル: helpers.py プロジェクト: hblanks/pyScss
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)
コード例 #4
0
ファイル: helpers.py プロジェクト: Foued123/qooxdoo
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)
コード例 #5
0
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)])
コード例 #6
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=',')
コード例 #7
0
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() == '"•"'
コード例 #8
0
ファイル: helpers.py プロジェクト: 4teamwork/pyScss
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=',')
コード例 #9
0
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)
コード例 #10
0
ファイル: core.py プロジェクト: Rehy0810/database
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='"')
コード例 #11
0
 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)
コード例 #12
0
ファイル: sprites.py プロジェクト: coinpayee/pyScss
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)])
コード例 #13
0
ファイル: helpers.py プロジェクト: Foued123/qooxdoo
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)
コード例 #14
0
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=',')
コード例 #15
0
ファイル: helpers.py プロジェクト: Foued123/qooxdoo
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=',')
コード例 #16
0
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)
コード例 #17
0
def test_parse(calc):
    # Tests for some general parsing.

    assert calc('foo !important bar') == List([
        String('foo'),
        String('!important'),
        String('bar'),
    ])
コード例 #18
0
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 == '"'
コード例 #19
0
ファイル: helpers.py プロジェクト: Foued123/qooxdoo
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
コード例 #20
0
ファイル: helpers.py プロジェクト: return42/pyScss
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
コード例 #21
0
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')
コード例 #22
0
ファイル: helpers.py プロジェクト: return42/pyScss
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)
コード例 #23
0
ファイル: test_expression.py プロジェクト: ziotoie00/pyScss
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
コード例 #24
0
ファイル: sprites.py プロジェクト: Rehy0810/database
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('')
コード例 #25
0
ファイル: sprites.py プロジェクト: coinpayee/pyScss
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('')
コード例 #26
0
ファイル: helpers.py プロジェクト: 4teamwork/pyScss
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)
コード例 #27
0
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('')
コード例 #28
0
ファイル: fonts.py プロジェクト: coinpayee/pyScss
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])
コード例 #29
0
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)
コード例 #30
0
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='"'))
コード例 #31
0
ファイル: sprites.py プロジェクト: Rehy0810/database
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('')
コード例 #32
0
ファイル: test_expression.py プロジェクト: doug-fish/pyScss
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)
コード例 #33
0
ファイル: sprites.py プロジェクト: coinpayee/pyScss
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('')
コード例 #34
0
ファイル: core.py プロジェクト: PhilipGarnero/pyScss
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)
コード例 #35
0
ファイル: core.py プロジェクト: PhilipGarnero/pyScss
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)
コード例 #36
0
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)])
コード例 #37
0
ファイル: legacy.py プロジェクト: Alpus/Eth
    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
コード例 #38
0
ファイル: sprites.py プロジェクト: tkhyn/pyScss
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])
コード例 #39
0
ファイル: legacy.py プロジェクト: tkhyn/pyScss
    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
コード例 #40
0
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)
コード例 #41
0
ファイル: test_expression.py プロジェクト: doug-fish/pyScss
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() == '"•"'
コード例 #42
0
    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)
コード例 #43
0
ファイル: expression.py プロジェクト: 7c00/staticpycon
    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
コード例 #44
0
ファイル: images.py プロジェクト: balagopalraj/clearlinux
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")
コード例 #45
0
ファイル: sprites.py プロジェクト: Rehy0810/database
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('*'))))
コード例 #46
0
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)
コード例 #47
0
ファイル: webchat.py プロジェクト: tesla1856/LalkaChat
    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)
コード例 #48
0
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)
コード例 #49
0
ファイル: core.py プロジェクト: MengYing/BibTexParser
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)
コード例 #50
0
ファイル: helpers.py プロジェクト: hblanks/pyScss
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)
コード例 #51
0
    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
コード例 #52
0
ファイル: ast.py プロジェクト: kan3dev/pyScss
    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)
コード例 #53
0
ファイル: images.py プロジェクト: asfaltboy/pyScss
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')
コード例 #54
0
ファイル: images.py プロジェクト: lamby/31bananas
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')
コード例 #55
0
ファイル: images.py プロジェクト: 4teamwork/pyScss
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')
コード例 #56
0
ファイル: sprites.py プロジェクト: coinpayee/pyScss
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)])
コード例 #57
0
ファイル: fonts.py プロジェクト: coinpayee/pyScss
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))
コード例 #58
0
ファイル: helpers.py プロジェクト: Foued123/qooxdoo
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)
コード例 #59
0
ファイル: sprites.py プロジェクト: coinpayee/pyScss
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))
コード例 #60
0
ファイル: sprites.py プロジェクト: coinpayee/pyScss
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('')