~corey.bryant/ubuntu/wily/python-pyscss/thedac

« back to all changes in this revision

Viewing changes to scss/functions/compass/helpers.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2014-06-26 12:10:36 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20140626121036-3dv13zn5zptk9fpx
Tags: 1.2.0.post3-1
* Team upload.
* New upstream release (Closes: #738776).
* Added a debian/gbp.conf.
* Added missing ${python:Depends}
* Added Python 3 support.
* Removed duplicate debhelper build-depends.
* Cannonical VCS URLs.
* Standards-Version: is now 3.9.5.
* Added a watch file.
* override dh helpers which the package doesn't use.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Miscellaneous helper functions ported from Compass.
 
2
 
 
3
See: http://compass-style.org/reference/compass/helpers/
 
4
 
 
5
This collection is not necessarily complete or up-to-date.
 
6
"""
 
7
 
 
8
from __future__ import absolute_import
 
9
 
 
10
import base64
 
11
import logging
 
12
import math
 
13
import os.path
 
14
import time
 
15
 
 
16
import six
 
17
 
 
18
from scss import config
 
19
from scss.functions.library import FunctionLibrary
 
20
from scss.types import Boolean, List, Null, Number, String
 
21
from scss.util import escape, to_str
 
22
import re
 
23
 
 
24
log = logging.getLogger(__name__)
 
25
 
 
26
 
 
27
COMPASS_HELPERS_LIBRARY = FunctionLibrary()
 
28
register = COMPASS_HELPERS_LIBRARY.register
 
29
 
 
30
FONT_TYPES = {
 
31
    'woff': 'woff',
 
32
    'otf': 'opentype',
 
33
    'opentype': 'opentype',
 
34
    'ttf': 'truetype',
 
35
    'truetype': 'truetype',
 
36
    'svg': 'svg',
 
37
    'eot': 'embedded-opentype'
 
38
}
 
39
 
 
40
 
 
41
def add_cache_buster(url, mtime):
 
42
    fragment = url.split('#')
 
43
    query = fragment[0].split('?')
 
44
    if len(query) > 1 and query[1] != '':
 
45
        cb = '&_=%s' % (mtime)
 
46
        url = '?'.join(query) + cb
 
47
    else:
 
48
        cb = '?_=%s' % (mtime)
 
49
        url = query[0] + cb
 
50
    if len(fragment) > 1:
 
51
        url += '#' + fragment[1]
 
52
    return url
 
53
 
 
54
 
 
55
# ------------------------------------------------------------------------------
 
56
# Data manipulation
 
57
 
 
58
@register('blank')
 
59
def blank(*objs):
 
60
    """Returns true when the object is false, an empty string, or an empty list"""
 
61
    for o in objs:
 
62
        if isinstance(o, Boolean):
 
63
            is_blank = not o
 
64
        elif isinstance(o, String):
 
65
            is_blank = not len(o.value.strip())
 
66
        elif isinstance(o, List):
 
67
            is_blank = all(blank(el) for el in o)
 
68
        else:
 
69
            is_blank = False
 
70
 
 
71
        if not is_blank:
 
72
            return Boolean(False)
 
73
 
 
74
    return Boolean(True)
 
75
 
 
76
 
 
77
@register('compact')
 
78
def compact(*args):
 
79
    """Returns a new list after removing any non-true values"""
 
80
    use_comma = True
 
81
    if len(args) == 1 and isinstance(args[0], List):
 
82
        use_comma = args[0].use_comma
 
83
        args = args[0]
 
84
 
 
85
    return List(
 
86
        [arg for arg in args if arg],
 
87
        use_comma=use_comma,
 
88
    )
 
89
 
 
90
 
 
91
@register('reject')
 
92
def reject(lst, *values):
 
93
    """Removes the given values from the list"""
 
94
    lst = List.from_maybe(lst)
 
95
    values = frozenset(List.from_maybe_starargs(values))
 
96
 
 
97
    ret = []
 
98
    for item in lst:
 
99
        if item not in values:
 
100
            ret.append(item)
 
101
    return List(ret, use_comma=lst.use_comma)
 
102
 
 
103
 
 
104
@register('first-value-of')
 
105
def first_value_of(*args):
 
106
    if len(args) == 1 and isinstance(args[0], String):
 
107
        first = args[0].value.split()[0]
 
108
        return type(args[0])(first)
 
109
 
 
110
    args = List.from_maybe_starargs(args)
 
111
    if len(args):
 
112
        return args[0]
 
113
    else:
 
114
        return Null()
 
115
 
 
116
 
 
117
@register('-compass-list')
 
118
def dash_compass_list(*args):
 
119
    return List.from_maybe_starargs(args)
 
120
 
 
121
 
 
122
@register('-compass-space-list')
 
123
def dash_compass_space_list(*lst):
 
124
    """
 
125
    If the argument is a list, it will return a new list that is space delimited
 
126
    Otherwise it returns a new, single element, space-delimited list.
 
127
    """
 
128
    ret = dash_compass_list(*lst)
 
129
    ret.value.pop('_', None)
 
130
    return ret
 
131
 
 
132
 
 
133
@register('-compass-slice', 3)
 
134
def dash_compass_slice(lst, start_index, end_index=None):
 
135
    start_index = Number(start_index).value
 
136
    end_index = Number(end_index).value if end_index is not None else None
 
137
    ret = {}
 
138
    lst = List(lst)
 
139
    if end_index:
 
140
        # This function has an inclusive end, but Python slicing is exclusive
 
141
        end_index += 1
 
142
    ret = lst.value[start_index:end_index]
 
143
    return List(ret, use_comma=lst.use_comma)
 
144
 
 
145
 
 
146
# ------------------------------------------------------------------------------
 
147
# Property prefixing
 
148
 
 
149
@register('prefixed')
 
150
def prefixed(prefix, *args):
 
151
    to_fnct_str = 'to_' + to_str(prefix).replace('-', '_')
 
152
    for arg in List.from_maybe_starargs(args):
 
153
        if hasattr(arg, to_fnct_str):
 
154
            return Boolean(True)
 
155
    return Boolean(False)
 
156
 
 
157
 
 
158
@register('prefix')
 
159
def prefix(prefix, *args):
 
160
    to_fnct_str = 'to_' + to_str(prefix).replace('-', '_')
 
161
    args = list(args)
 
162
    for i, arg in enumerate(args):
 
163
        if isinstance(arg, List):
 
164
            _value = []
 
165
            for iarg in arg:
 
166
                to_fnct = getattr(iarg, to_fnct_str, None)
 
167
                if to_fnct:
 
168
                    _value.append(to_fnct())
 
169
                else:
 
170
                    _value.append(iarg)
 
171
            args[i] = List(_value)
 
172
        else:
 
173
            to_fnct = getattr(arg, to_fnct_str, None)
 
174
            if to_fnct:
 
175
                args[i] = to_fnct()
 
176
 
 
177
    return List.maybe_new(args, use_comma=True)
 
178
 
 
179
 
 
180
@register('-moz')
 
181
def dash_moz(*args):
 
182
    return prefix('_moz', *args)
 
183
 
 
184
 
 
185
@register('-svg')
 
186
def dash_svg(*args):
 
187
    return prefix('_svg', *args)
 
188
 
 
189
 
 
190
@register('-css2')
 
191
def dash_css2(*args):
 
192
    return prefix('_css2', *args)
 
193
 
 
194
 
 
195
@register('-pie')
 
196
def dash_pie(*args):
 
197
    return prefix('_pie', *args)
 
198
 
 
199
 
 
200
@register('-webkit')
 
201
def dash_webkit(*args):
 
202
    return prefix('_webkit', *args)
 
203
 
 
204
 
 
205
@register('-owg')
 
206
def dash_owg(*args):
 
207
    return prefix('_owg', *args)
 
208
 
 
209
 
 
210
@register('-khtml')
 
211
def dash_khtml(*args):
 
212
    return prefix('_khtml', *args)
 
213
 
 
214
 
 
215
@register('-ms')
 
216
def dash_ms(*args):
 
217
    return prefix('_ms', *args)
 
218
 
 
219
 
 
220
@register('-o')
 
221
def dash_o(*args):
 
222
    return prefix('_o', *args)
 
223
 
 
224
 
 
225
# ------------------------------------------------------------------------------
 
226
# Selector generation
 
227
 
 
228
@register('append-selector', 2)
 
229
def append_selector(selector, to_append):
 
230
    if isinstance(selector, List):
 
231
        lst = selector.value
 
232
    else:
 
233
        lst = String.unquoted(selector).value.split(',')
 
234
    to_append = String.unquoted(to_append).value.strip()
 
235
    ret = sorted(set(s.strip() + to_append for s in lst if s.strip()))
 
236
    ret = dict(enumerate(ret))
 
237
    ret['_'] = ','
 
238
    return ret
 
239
 
 
240
 
 
241
_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'
 
242
_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'
 
243
_elements_of_type_table = 'table'
 
244
_elements_of_type_list_item = 'li'
 
245
_elements_of_type_table_row_group = 'tbody'
 
246
_elements_of_type_table_header_group = 'thead'
 
247
_elements_of_type_table_footer_group = 'tfoot'
 
248
_elements_of_type_table_row = 'tr'
 
249
_elements_of_type_table_cel = 'td, th'
 
250
_elements_of_type_html5_block = 'article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary'
 
251
_elements_of_type_html5_inline = 'audio, canvas, command, datalist, embed, keygen, mark, meter, output, progress, rp, rt, ruby, time, video, wbr'
 
252
_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'
 
253
_elements_of_type = {
 
254
    'block': sorted(_elements_of_type_block.replace(' ', '').split(',')),
 
255
    'inline': sorted(_elements_of_type_inline.replace(' ', '').split(',')),
 
256
    'table': sorted(_elements_of_type_table.replace(' ', '').split(',')),
 
257
    'list-item': sorted(_elements_of_type_list_item.replace(' ', '').split(',')),
 
258
    'table-row-group': sorted(_elements_of_type_table_row_group.replace(' ', '').split(',')),
 
259
    'table-header-group': sorted(_elements_of_type_table_header_group.replace(' ', '').split(',')),
 
260
    'table-footer-group': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')),
 
261
    'table-row': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')),
 
262
    'table-cell': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')),
 
263
    'html5-block': sorted(_elements_of_type_html5_block.replace(' ', '').split(',')),
 
264
    'html5-inline': sorted(_elements_of_type_html5_inline.replace(' ', '').split(',')),
 
265
    'html5': sorted(_elements_of_type_html5.replace(' ', '').split(',')),
 
266
}
 
267
 
 
268
 
 
269
@register('elements-of-type', 1)
 
270
def elements_of_type(display):
 
271
    d = String.unquoted(display)
 
272
    ret = _elements_of_type.get(d.value, None)
 
273
    if ret is None:
 
274
        raise Exception("Elements of type '%s' not found!" % d.value)
 
275
    return List(ret, use_comma=True)
 
276
 
 
277
 
 
278
@register('enumerate', 3)
 
279
@register('enumerate', 4)
 
280
def enumerate_(prefix, frm, through, separator='-'):
 
281
    separator = String.unquoted(separator).value
 
282
    try:
 
283
        frm = int(getattr(frm, 'value', frm))
 
284
    except ValueError:
 
285
        frm = 1
 
286
    try:
 
287
        through = int(getattr(through, 'value', through))
 
288
    except ValueError:
 
289
        through = frm
 
290
    if frm > through:
 
291
        # DEVIATION: allow reversed enumerations (and ranges as range() uses enumerate, like '@for .. from .. through')
 
292
        frm, through = through, frm
 
293
        rev = reversed
 
294
    else:
 
295
        rev = lambda x: x
 
296
 
 
297
    ret = []
 
298
    for i in rev(range(frm, through + 1)):
 
299
        if prefix and prefix.value:
 
300
            ret.append(String.unquoted(prefix.value + separator + str(i)))
 
301
        else:
 
302
            ret.append(Number(i))
 
303
 
 
304
    return List(ret, use_comma=True)
 
305
 
 
306
 
 
307
@register('headers', 0)
 
308
@register('headers', 1)
 
309
@register('headers', 2)
 
310
@register('headings', 0)
 
311
@register('headings', 1)
 
312
@register('headings', 2)
 
313
def headers(frm=None, to=None):
 
314
    if frm and to is None:
 
315
        if isinstance(frm, String) and frm.value.lower() == 'all':
 
316
            frm = 1
 
317
            to = 6
 
318
        else:
 
319
            try:
 
320
                to = int(getattr(frm, 'value', frm))
 
321
            except ValueError:
 
322
                to = 6
 
323
            frm = 1
 
324
    else:
 
325
        try:
 
326
            frm = 1 if frm is None else int(getattr(frm, 'value', frm))
 
327
        except ValueError:
 
328
            frm = 1
 
329
        try:
 
330
            to = 6 if to is None else int(getattr(to, 'value', to))
 
331
        except ValueError:
 
332
            to = 6
 
333
    ret = [String.unquoted('h' + str(i)) for i in range(frm, to + 1)]
 
334
    return List(ret, use_comma=True)
 
335
 
 
336
 
 
337
@register('nest')
 
338
def nest(*arguments):
 
339
    if isinstance(arguments[0], List):
 
340
        lst = arguments[0]
 
341
    elif isinstance(arguments[0], String):
 
342
        lst = arguments[0].value.split(',')
 
343
    else:
 
344
        raise TypeError("Expected list or string, got %r" % (arguments[0],))
 
345
 
 
346
    ret = []
 
347
    for s in lst:
 
348
        if isinstance(s, String):
 
349
            s = s.value
 
350
        elif isinstance(s, six.string_types):
 
351
            s = s
 
352
        else:
 
353
            raise TypeError("Expected string, got %r" % (s,))
 
354
 
 
355
        s = s.strip()
 
356
        if not s:
 
357
            continue
 
358
 
 
359
        ret.append(s)
 
360
 
 
361
    for arg in arguments[1:]:
 
362
        if isinstance(arg, List):
 
363
            lst = arg
 
364
        elif isinstance(arg, String):
 
365
            lst = arg.value.split(',')
 
366
        else:
 
367
            raise TypeError("Expected list or string, got %r" % (arg,))
 
368
 
 
369
        new_ret = []
 
370
        for s in lst:
 
371
            if isinstance(s, String):
 
372
                s = s.value
 
373
            elif isinstance(s, six.string_types):
 
374
                s = s
 
375
            else:
 
376
                raise TypeError("Expected string, got %r" % (s,))
 
377
 
 
378
            s = s.strip()
 
379
            if not s:
 
380
                continue
 
381
 
 
382
            for r in ret:
 
383
                if '&' in s:
 
384
                    new_ret.append(s.replace('&', r))
 
385
                else:
 
386
                    if not r or r[-1] in ('.', ':', '#'):
 
387
                        new_ret.append(r + s)
 
388
                    else:
 
389
                        new_ret.append(r + ' ' + s)
 
390
        ret = new_ret
 
391
 
 
392
    ret = [String.unquoted(s) for s in sorted(set(ret))]
 
393
    return List(ret, use_comma=True)
 
394
 
 
395
 
 
396
# This isn't actually from Compass, but it's just a shortcut for enumerate().
 
397
# DEVIATION: allow reversed ranges (range() uses enumerate() which allows reversed values, like '@for .. from .. through')
 
398
@register('range', 1)
 
399
@register('range', 2)
 
400
def range_(frm, through=None):
 
401
    if through is None:
 
402
        through = frm
 
403
        frm = 1
 
404
    return enumerate_(None, frm, through)
 
405
 
 
406
# ------------------------------------------------------------------------------
 
407
# Working with CSS constants
 
408
 
 
409
OPPOSITE_POSITIONS = {
 
410
    'top': String.unquoted('bottom'),
 
411
    'bottom': String.unquoted('top'),
 
412
    'left': String.unquoted('right'),
 
413
    'right': String.unquoted('left'),
 
414
    'center': String.unquoted('center'),
 
415
}
 
416
DEFAULT_POSITION = [String.unquoted('center'), String.unquoted('top')]
 
417
 
 
418
 
 
419
def _position(opposite, positions):
 
420
    if positions is None:
 
421
        positions = DEFAULT_POSITION
 
422
    else:
 
423
        positions = List.from_maybe(positions)
 
424
 
 
425
    ret = []
 
426
    for pos in positions:
 
427
        if isinstance(pos, (String, six.string_types)):
 
428
            pos_value = getattr(pos, 'value', pos)
 
429
            if pos_value in OPPOSITE_POSITIONS:
 
430
                if opposite:
 
431
                    ret.append(OPPOSITE_POSITIONS[pos_value])
 
432
                else:
 
433
                    ret.append(pos)
 
434
                continue
 
435
            elif pos_value == 'to':
 
436
                # Gradient syntax keyword; leave alone
 
437
                ret.append(pos)
 
438
                continue
 
439
 
 
440
        elif isinstance(pos, Number):
 
441
            if pos.is_simple_unit('%'):
 
442
                if opposite:
 
443
                    ret.append(Number(100 - pos.value, '%'))
 
444
                else:
 
445
                    ret.append(pos)
 
446
                continue
 
447
            elif pos.is_simple_unit('deg'):
 
448
                # TODO support other angle types?
 
449
                if opposite:
 
450
                    ret.append(Number((pos.value + 180) % 360, 'deg'))
 
451
                else:
 
452
                    ret.append(pos)
 
453
                continue
 
454
 
 
455
        if opposite:
 
456
            log.warn("Can't find opposite for position %r" % (pos,))
 
457
        ret.append(pos)
 
458
 
 
459
    return List(ret, use_comma=False).maybe()
 
460
 
 
461
 
 
462
@register('position')
 
463
def position(p):
 
464
    return _position(False, p)
 
465
 
 
466
 
 
467
@register('opposite-position')
 
468
def opposite_position(p):
 
469
    return _position(True, p)
 
470
 
 
471
 
 
472
# ------------------------------------------------------------------------------
 
473
# Math
 
474
 
 
475
@register('pi', 0)
 
476
def pi():
 
477
    return Number(math.pi)
 
478
 
 
479
 
 
480
@register('e', 0)
 
481
def e():
 
482
    return Number(math.e)
 
483
 
 
484
 
 
485
@register('log', 1)
 
486
@register('log', 2)
 
487
def log_(number, base=None):
 
488
    if not isinstance(number, Number):
 
489
        raise TypeError("Expected number, got %r" % (number,))
 
490
    elif not number.is_unitless:
 
491
        raise ValueError("Expected unitless number, got %r" % (number,))
 
492
 
 
493
    if base is None:
 
494
        pass
 
495
    elif not isinstance(base, Number):
 
496
        raise TypeError("Expected number, got %r" % (base,))
 
497
    elif not base.is_unitless:
 
498
        raise ValueError("Expected unitless number, got %r" % (base,))
 
499
 
 
500
    if base is None:
 
501
        ret = math.log(number.value)
 
502
    else:
 
503
        ret = math.log(number.value, base.value)
 
504
 
 
505
    return Number(ret)
 
506
 
 
507
 
 
508
@register('pow', 2)
 
509
def pow(number, exponent):
 
510
    return number ** exponent
 
511
 
 
512
 
 
513
COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.sqrt), 'sqrt', 1)
 
514
COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.sin), 'sin', 1)
 
515
COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.cos), 'cos', 1)
 
516
COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.tan), 'tan', 1)
 
517
 
 
518
 
 
519
# ------------------------------------------------------------------------------
 
520
# Fonts
 
521
 
 
522
def _fonts_root():
 
523
    return config.STATIC_ROOT if config.FONTS_ROOT is None else config.FONTS_ROOT
 
524
 
 
525
 
 
526
def _font_url(path, only_path=False, cache_buster=True, inline=False):
 
527
    filepath = String.unquoted(path).value
 
528
    file = None
 
529
    FONTS_ROOT = _fonts_root()
 
530
    if callable(FONTS_ROOT):
 
531
        try:
 
532
            _file, _storage = list(FONTS_ROOT(filepath))[0]
 
533
            d_obj = _storage.modified_time(_file)
 
534
            filetime = int(time.mktime(d_obj.timetuple()))
 
535
            if inline:
 
536
                file = _storage.open(_file)
 
537
        except:
 
538
            filetime = 'NA'
 
539
    else:
 
540
        _path = os.path.join(FONTS_ROOT, filepath.strip('/'))
 
541
        if os.path.exists(_path):
 
542
            filetime = int(os.path.getmtime(_path))
 
543
            if inline:
 
544
                file = open(_path, 'rb')
 
545
        else:
 
546
            filetime = 'NA'
 
547
 
 
548
    BASE_URL = config.FONTS_URL or config.STATIC_URL
 
549
    if file and inline:
 
550
        font_type = None
 
551
        if re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value):
 
552
            font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value).groups()[1]).value
 
553
 
 
554
        if not FONT_TYPES.get(font_type):
 
555
            raise Exception('Could not determine font type for "%s"' % path.value)
 
556
 
 
557
        mime = FONT_TYPES.get(font_type)
 
558
        if font_type == 'woff':
 
559
            mime = 'application/font-woff'
 
560
        elif font_type == 'eot':
 
561
            mime = 'application/vnd.ms-fontobject'
 
562
        url = 'data:' + (mime if '/' in mime else 'font/%s' % mime) + ';base64,' + base64.b64encode(file.read())
 
563
        file.close()
 
564
    else:
 
565
        url = '%s/%s' % (BASE_URL.rstrip('/'), filepath.lstrip('/'))
 
566
        if cache_buster and filetime != 'NA':
 
567
            url = add_cache_buster(url, filetime)
 
568
 
 
569
    if not only_path:
 
570
        url = 'url(%s)' % escape(url)
 
571
    return String.unquoted(url)
 
572
 
 
573
 
 
574
def _font_files(args, inline):
 
575
    if args == ():
 
576
        return String.unquoted("")
 
577
 
 
578
    fonts = []
 
579
    args_len = len(args)
 
580
    skip_next = False
 
581
    for index in range(len(args)):
 
582
        arg = args[index]
 
583
        if not skip_next:
 
584
            font_type = args[index + 1] if args_len > (index + 1) else None
 
585
            if font_type and font_type.value in FONT_TYPES:
 
586
                skip_next = True
 
587
            else:
 
588
                if re.match(r'^([^?]+)[.](.*)([?].*)?$', arg.value):
 
589
                    font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', arg.value).groups()[1])
 
590
 
 
591
            if font_type.value in FONT_TYPES:
 
592
                fonts.append(String.unquoted('%s format("%s")' % (_font_url(arg, inline=inline), String.unquoted(FONT_TYPES[font_type.value]).value)))
 
593
            else:
 
594
                raise Exception('Could not determine font type for "%s"' % arg.value)
 
595
        else:
 
596
            skip_next = False
 
597
 
 
598
    return List(fonts, separator=',')
 
599
 
 
600
 
 
601
@register('font-url', 1)
 
602
@register('font-url', 2)
 
603
def font_url(path, only_path=False, cache_buster=True):
 
604
    """
 
605
    Generates a path to an asset found relative to the project's font directory.
 
606
    Passing a true value as the second argument will cause the only the path to
 
607
    be returned instead of a `url()` function
 
608
    """
 
609
    return _font_url(path, only_path, cache_buster, False)
 
610
 
 
611
 
 
612
@register('font-files')
 
613
def font_files(*args):
 
614
    return _font_files(args, inline=False)
 
615
 
 
616
 
 
617
@register('inline-font-files')
 
618
def inline_font_files(*args):
 
619
    return _font_files(args, inline=True)
 
620
 
 
621
 
 
622
# ------------------------------------------------------------------------------
 
623
# External stylesheets
 
624
 
 
625
@register('stylesheet-url', 1)
 
626
@register('stylesheet-url', 2)
 
627
def stylesheet_url(path, only_path=False, cache_buster=True):
 
628
    """
 
629
    Generates a path to an asset found relative to the project's css directory.
 
630
    Passing a true value as the second argument will cause the only the path to
 
631
    be returned instead of a `url()` function
 
632
    """
 
633
    filepath = String.unquoted(path).value
 
634
    if callable(config.STATIC_ROOT):
 
635
        try:
 
636
            _file, _storage = list(config.STATIC_ROOT(filepath))[0]
 
637
            d_obj = _storage.modified_time(_file)
 
638
            filetime = int(time.mktime(d_obj.timetuple()))
 
639
        except:
 
640
            filetime = 'NA'
 
641
    else:
 
642
        _path = os.path.join(config.STATIC_ROOT, filepath.strip('/'))
 
643
        if os.path.exists(_path):
 
644
            filetime = int(os.path.getmtime(_path))
 
645
        else:
 
646
            filetime = 'NA'
 
647
    BASE_URL = config.STATIC_URL
 
648
 
 
649
    url = '%s%s' % (BASE_URL, filepath)
 
650
    if cache_buster:
 
651
        url = add_cache_buster(url, filetime)
 
652
    if not only_path:
 
653
        url = 'url("%s")' % (url)
 
654
    return String.unquoted(url)