1""":mod:`sass` --- Binding of ``libsass``
2~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
4This simple C extension module provides a very simple binding of ``libsass``,
5which is written in C/C++.  It contains only one function and one exception
6type.
7
8>>> import sass
9>>> sass.compile(string='a { b { color: blue; } }')
10'a b {\n  color: blue; }\n'
11
12"""
13from __future__ import absolute_import
14
15import collections
16import inspect
17import io
18import os
19import os.path
20import re
21import sys
22import warnings
23
24from six import string_types, text_type, PY2
25
26import _sass
27from sassutils._compat import collections_abc
28
29__all__ = (
30    'MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError', 'SassColor',
31    'SassError', 'SassFunction', 'SassList', 'SassMap', 'SassNumber',
32    'SassWarning', 'and_join', 'compile', 'libsass_version',
33)
34__version__ = '0.21.0'
35libsass_version = _sass.libsass_version
36
37
38#: (:class:`collections.abc.Mapping`) The dictionary of output styles.
39#: Keys are output name strings, and values are flag integers.
40OUTPUT_STYLES = _sass.OUTPUT_STYLES
41
42#: (:class:`collections.abc.Mapping`) The dictionary of source comments styles.
43#: Keys are mode names, and values are corresponding flag integers.
44#:
45#: .. versionadded:: 0.4.0
46#:
47#: .. deprecated:: 0.6.0
48SOURCE_COMMENTS = {'none': 0, 'line_numbers': 1, 'default': 1, 'map': 2}
49
50#: (:class:`frozenset`) The set of keywords :func:`compile()` can take.
51MODES = frozenset(('string', 'filename', 'dirname'))
52
53
54def to_native_s(s):
55    if isinstance(s, bytes) and not PY2:  # pragma: no cover (py3)
56        s = s.decode('UTF-8')
57    elif isinstance(s, text_type) and PY2:  # pragma: no cover (py2)
58        s = s.encode('UTF-8')
59    return s
60
61
62class CompileError(ValueError):
63    """The exception type that is raised by :func:`compile()`.
64    It is a subtype of :exc:`exceptions.ValueError`.
65    """
66    def __init__(self, msg):
67        super(CompileError, self).__init__(to_native_s(msg))
68
69
70def mkdirp(path):
71    try:
72        os.makedirs(path)
73    except OSError:
74        if os.path.isdir(path):
75            return
76        raise
77
78
79class SassFunction(object):
80    """Custom function for Sass.  It can be instantiated using
81    :meth:`from_lambda()` and :meth:`from_named_function()` as well.
82
83    :param name: the function name
84    :type name: :class:`str`
85    :param arguments: the argument names
86    :type arguments: :class:`collections.abc.Sequence`
87    :param callable_: the actual function to be called
88    :type callable_: :class:`collections.abc.Callable`
89
90    .. versionadded:: 0.7.0
91
92    """
93
94    __slots__ = 'name', 'arguments', 'callable_'
95
96    @classmethod
97    def from_lambda(cls, name, lambda_):
98        """Make a :class:`SassFunction` object from the given ``lambda_``
99        function.  Since lambda functions don't have their name, it need
100        its ``name`` as well.  Arguments are automatically inspected.
101
102        :param name: the function name
103        :type name: :class:`str`
104        :param lambda_: the actual lambda function to be called
105        :type lambda_: :class:`types.LambdaType`
106        :returns: a custom function wrapper of the ``lambda_`` function
107        :rtype: :class:`SassFunction`
108
109        """
110        if PY2:  # pragma: no cover
111            a = inspect.getargspec(lambda_)
112            varargs, varkw, defaults, kwonlyargs = (
113                a.varargs, a.keywords, a.defaults, None,
114            )
115        else:  # pragma: no cover
116            a = inspect.getfullargspec(lambda_)
117            varargs, varkw, defaults, kwonlyargs = (
118                a.varargs, a.varkw, a.defaults, a.kwonlyargs,
119            )
120
121        if varargs or varkw or defaults or kwonlyargs:
122            raise TypeError(
123                'functions cannot have starargs or defaults: {} {}'.format(
124                    name, lambda_,
125                ),
126            )
127        return cls(name, a.args, lambda_)
128
129    @classmethod
130    def from_named_function(cls, function):
131        """Make a :class:`SassFunction` object from the named ``function``.
132        Function name and arguments are automatically inspected.
133
134        :param function: the named function to be called
135        :type function: :class:`types.FunctionType`
136        :returns: a custom function wrapper of the ``function``
137        :rtype: :class:`SassFunction`
138
139        """
140        if not getattr(function, '__name__', ''):
141            raise TypeError('function must be named')
142        return cls.from_lambda(function.__name__, function)
143
144    def __init__(self, name, arguments, callable_):
145        if not isinstance(name, string_types):
146            raise TypeError('name must be a string, not ' + repr(name))
147        elif not isinstance(arguments, collections_abc.Sequence):
148            raise TypeError(
149                'arguments must be a sequence, not ' +
150                repr(arguments),
151            )
152        elif not callable(callable_):
153            raise TypeError(repr(callable_) + ' is not callable')
154        self.name = name
155        self.arguments = tuple(
156            arg if arg.startswith('$') else '$' + arg
157            for arg in arguments
158        )
159        self.callable_ = callable_
160
161    @property
162    def signature(self):
163        """Signature string of the function."""
164        return '{}({})'.format(self.name, ', '.join(self.arguments))
165
166    def __call__(self, *args, **kwargs):
167        return self.callable_(*args, **kwargs)
168
169    def __str__(self):
170        return self.signature
171
172
173def _normalize_importer_return_value(result):
174    # An importer must return an iterable of iterables of 1-3 stringlike
175    # objects
176    if result is None:
177        return result
178
179    def _to_importer_result(single_result):
180        single_result = tuple(single_result)
181        if len(single_result) not in (1, 2, 3):
182            raise ValueError(
183                'Expected importer result to be a tuple of length (1, 2, 3) '
184                'but got {}: {!r}'.format(len(single_result), single_result),
185            )
186
187        def _to_bytes(obj):
188            if not isinstance(obj, bytes):
189                return obj.encode('UTF-8')
190            else:
191                return obj
192
193        return tuple(_to_bytes(s) for s in single_result)
194
195    return tuple(_to_importer_result(x) for x in result)
196
197
198def _importer_callback_wrapper(func):
199    def inner(path, prev):
200        path, prev = path.decode('UTF-8'), prev.decode('UTF-8')
201        num_args = getattr(inner, '_num_args', None)
202        if num_args is None:
203            try:
204                ret = func(path, prev)
205            except TypeError:
206                inner._num_args = 1
207                ret = func(path)
208            else:
209                inner._num_args = 2
210        elif num_args == 2:
211            ret = func(path, prev)
212        else:
213            ret = func(path)
214        return _normalize_importer_return_value(ret)
215    return inner
216
217
218def _validate_importers(importers):
219    """Validates the importers and decorates the callables with our output
220    formatter.
221    """
222    # They could have no importers, that's chill
223    if importers is None:
224        return None
225
226    def _to_importer(priority, func):
227        assert isinstance(priority, int), priority
228        assert callable(func), func
229        return (priority, _importer_callback_wrapper(func))
230
231    # Our code assumes tuple of tuples
232    return tuple(_to_importer(priority, func) for priority, func in importers)
233
234
235def _raise(e):
236    raise e
237
238
239def compile_dirname(
240    search_path, output_path, output_style, source_comments, include_paths,
241    precision, custom_functions, importers, source_map_contents,
242    source_map_embed, omit_source_map_url, source_map_root,
243):
244    fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
245    for dirpath, _, filenames in os.walk(search_path, onerror=_raise):
246        filenames = [
247            filename for filename in filenames
248            if filename.endswith(('.scss', '.sass')) and
249            not filename.startswith('_')
250        ]
251        for filename in filenames:
252            input_filename = os.path.join(dirpath, filename)
253            relpath_to_file = os.path.relpath(input_filename, search_path)
254            output_filename = os.path.join(output_path, relpath_to_file)
255            output_filename = re.sub('.s[ac]ss$', '.css', output_filename)
256            input_filename = input_filename.encode(fs_encoding)
257            s, v, _ = _sass.compile_filename(
258                input_filename, output_style, source_comments, include_paths,
259                precision, None, custom_functions, importers, None,
260                source_map_contents, source_map_embed, omit_source_map_url,
261                source_map_root,
262            )
263            if s:
264                v = v.decode('UTF-8')
265                mkdirp(os.path.dirname(output_filename))
266                with io.open(
267                    output_filename, 'w', encoding='UTF-8', newline='',
268                ) as output_file:
269                    output_file.write(v)
270            else:
271                return False, v
272    return True, None
273
274
275def _check_no_remaining_kwargs(func, kwargs):
276    if kwargs:
277        raise TypeError(
278            '{}() got unexpected keyword argument(s) {}'.format(
279                func.__name__,
280                ', '.join("'{}'".format(arg) for arg in sorted(kwargs)),
281            ),
282        )
283
284
285def compile(**kwargs):
286    r"""There are three modes of parameters :func:`compile()` can take:
287    ``string``, ``filename``, and ``dirname``.
288
289    The ``string`` parameter is the most basic way to compile Sass.
290    It simply takes a string of Sass code, and then returns a compiled
291    CSS string.
292
293    :param string: Sass source code to compile.  it's exclusive to
294                   ``filename`` and ``dirname`` parameters
295    :type string: :class:`str`
296    :param output_style: an optional coding style of the compiled result.
297                         choose one of: ``'nested'`` (default), ``'expanded'``,
298                         ``'compact'``, ``'compressed'``
299    :type output_style: :class:`str`
300    :param source_comments: whether to add comments about source lines.
301                            :const:`False` by default
302    :type source_comments: :class:`bool`
303    :param source_map_contents: embed include contents in map
304    :type source_map_contents: :class:`bool`
305    :param source_map_embed: embed sourceMappingUrl as data URI
306    :type source_map_embed: :class:`bool`
307    :param omit_source_map_url: omit source map URL comment from output
308    :type omit_source_map_url: :class:`bool`
309    :param source_map_root: base path, will be emitted in source map as is
310    :type source_map_root: :class:`str`
311    :param include_paths: an optional list of paths to find ``@import``\ ed
312                          Sass/CSS source files
313    :type include_paths: :class:`collections.abc.Sequence`
314    :param precision: optional precision for numbers. :const:`5` by default.
315    :type precision: :class:`int`
316    :param custom_functions: optional mapping of custom functions.
317                             see also below `custom functions
318                             <custom-functions_>`_ description
319    :type custom_functions: :class:`set`,
320                            :class:`collections.abc.Sequence`,
321                            :class:`collections.abc.Mapping`
322    :param custom_import_extensions: (ignored, for backward compatibility)
323    :param indented: optional declaration that the string is Sass, not SCSS
324                     formatted. :const:`False` by default
325    :type indented: :class:`bool`
326    :returns: the compiled CSS string
327    :param importers: optional callback functions.
328                     see also below `importer callbacks
329                     <importer-callbacks_>`_ description
330    :type importers: :class:`collections.abc.Callable`
331    :rtype: :class:`str`
332    :raises sass.CompileError: when it fails for any reason
333                               (for example the given Sass has broken syntax)
334
335    The ``filename`` is the most commonly used way.  It takes a string of
336    Sass filename, and then returns a compiled CSS string.
337
338    :param filename: the filename of Sass source code to compile.
339                     it's exclusive to ``string`` and ``dirname`` parameters
340    :type filename: :class:`str`
341    :param output_style: an optional coding style of the compiled result.
342                         choose one of: ``'nested'`` (default), ``'expanded'``,
343                         ``'compact'``, ``'compressed'``
344    :type output_style: :class:`str`
345    :param source_comments: whether to add comments about source lines.
346                            :const:`False` by default
347    :type source_comments: :class:`bool`
348    :param source_map_filename: use source maps and indicate the source map
349                                output filename.  :const:`None` means not
350                                using source maps.  :const:`None` by default.
351    :type source_map_filename: :class:`str`
352    :param source_map_contents: embed include contents in map
353    :type source_map_contents: :class:`bool`
354    :param source_map_embed: embed sourceMappingUrl as data URI
355    :type source_map_embed: :class:`bool`
356    :param omit_source_map_url: omit source map URL comment from output
357    :type omit_source_map_url: :class:`bool`
358    :param source_map_root: base path, will be emitted in source map as is
359    :type source_map_root: :class:`str`
360    :param include_paths: an optional list of paths to find ``@import``\ ed
361                          Sass/CSS source files
362    :type include_paths: :class:`collections.abc.Sequence`
363    :param precision: optional precision for numbers. :const:`5` by default.
364    :type precision: :class:`int`
365    :param custom_functions: optional mapping of custom functions.
366                             see also below `custom functions
367                             <custom-functions_>`_ description
368    :type custom_functions: :class:`set`,
369                            :class:`collections.abc.Sequence`,
370                            :class:`collections.abc.Mapping`
371    :param custom_import_extensions: (ignored, for backward compatibility)
372    :param importers: optional callback functions.
373                     see also below `importer callbacks
374                     <importer-callbacks_>`_ description
375    :type importers: :class:`collections.abc.Callable`
376    :returns: the compiled CSS string, or a pair of the compiled CSS string
377              and the source map string if ``source_map_filename`` is set
378    :rtype: :class:`str`, :class:`tuple`
379    :raises sass.CompileError: when it fails for any reason
380                               (for example the given Sass has broken syntax)
381    :raises exceptions.IOError: when the ``filename`` doesn't exist or
382                                cannot be read
383
384    The ``dirname`` is useful for automation.  It takes a pair of paths.
385    The first of the ``dirname`` pair refers the source directory, contains
386    several Sass source files to compiled.  Sass source files can be nested
387    in directories.  The second of the pair refers the output directory
388    that compiled CSS files would be saved.  Directory tree structure of
389    the source directory will be maintained in the output directory as well.
390    If ``dirname`` parameter is used the function returns :const:`None`.
391
392    :param dirname: a pair of ``(source_dir, output_dir)``.
393                    it's exclusive to ``string`` and ``filename``
394                    parameters
395    :type dirname: :class:`tuple`
396    :param output_style: an optional coding style of the compiled result.
397                         choose one of: ``'nested'`` (default), ``'expanded'``,
398                         ``'compact'``, ``'compressed'``
399    :type output_style: :class:`str`
400    :param source_comments: whether to add comments about source lines.
401                            :const:`False` by default
402    :type source_comments: :class:`bool`
403    :param source_map_contents: embed include contents in map
404    :type source_map_contents: :class:`bool`
405    :param source_map_embed: embed sourceMappingUrl as data URI
406    :type source_map_embed: :class:`bool`
407    :param omit_source_map_url: omit source map URL comment from output
408    :type omit_source_map_url: :class:`bool`
409    :param source_map_root: base path, will be emitted in source map as is
410    :type source_map_root: :class:`str`
411    :param include_paths: an optional list of paths to find ``@import``\ ed
412                          Sass/CSS source files
413    :type include_paths: :class:`collections.abc.Sequence`
414    :param precision: optional precision for numbers. :const:`5` by default.
415    :type precision: :class:`int`
416    :param custom_functions: optional mapping of custom functions.
417                             see also below `custom functions
418                             <custom-functions_>`_ description
419    :type custom_functions: :class:`set`,
420                            :class:`collections.abc.Sequence`,
421                            :class:`collections.abc.Mapping`
422    :param custom_import_extensions: (ignored, for backward compatibility)
423    :raises sass.CompileError: when it fails for any reason
424                               (for example the given Sass has broken syntax)
425
426    .. _custom-functions:
427
428    The ``custom_functions`` parameter can take three types of forms:
429
430    :class:`~set`/:class:`~collections.abc.Sequence` of \
431    :class:`SassFunction`\ s
432       It is the most general form.  Although pretty verbose, it can take
433       any kind of callables like type objects, unnamed functions,
434       and user-defined callables.
435
436       .. code-block:: python
437
438          sass.compile(
439              ...,
440              custom_functions={
441                  sass.SassFunction('func-name', ('$a', '$b'), some_callable),
442                  ...
443              }
444          )
445
446    :class:`~collections.abc.Mapping` of names to functions
447       Less general, but easier-to-use form.  Although it's not it can take
448       any kind of callables, it can take any kind of *functions* defined
449       using :keyword:`def`/:keyword:`lambda` syntax.
450       It cannot take callables other than them since inspecting arguments
451       is not always available for every kind of callables.
452
453       .. code-block:: python
454
455          sass.compile(
456              ...,
457              custom_functions={
458                  'func-name': lambda a, b: ...,
459                  ...
460              }
461          )
462
463    :class:`~set`/:class:`~collections.abc.Sequence` of \
464    named functions
465       Not general, but the easiest-to-use form for *named* functions.
466       It can take only named functions, defined using :keyword:`def`.
467       It cannot take lambdas sinc names are unavailable for them.
468
469       .. code-block:: python
470
471          def func_name(a, b):
472              return ...
473
474          sass.compile(
475              ...,
476              custom_functions={func_name}
477          )
478
479    .. _importer-callbacks:
480
481    Newer versions of ``libsass`` allow developers to define callbacks to be
482    called and given a chance to process ``@import`` directives. You can
483    define yours by passing in a list of callables via the ``importers``
484    parameter. The callables must be passed as 2-tuples in the form:
485
486    .. code-block:: python
487
488        (priority_int, callback_fn)
489
490    A priority of zero is acceptable; priority determines the order callbacks
491    are attempted.
492
493    These callbacks can accept one or two string arguments. The first argument
494    is the path that was passed to the ``@import`` directive; the second
495    (optional) argument is the previous resolved path, where the ``@import``
496    directive was found. The callbacks must either return ``None`` to
497    indicate the path wasn't handled by that callback (to continue with others
498    or fall back on internal ``libsass`` filesystem behaviour) or a list of
499    one or more tuples, each in one of three forms:
500
501    * A 1-tuple representing an alternate path to handle internally; or,
502    * A 2-tuple representing an alternate path and the content that path
503      represents; or,
504    * A 3-tuple representing the same as the 2-tuple with the addition of a
505      "sourcemap".
506
507    All tuple return values must be strings. As a not overly realistic
508    example:
509
510    .. code-block:: python
511
512        def my_importer(path, prev):
513            return [(path, '#' + path + ' { color: red; }')]
514
515        sass.compile(
516                ...,
517                importers=[(0, my_importer)]
518            )
519
520    Now, within the style source, attempting to ``@import 'button';`` will
521    instead attach ``color: red`` as a property of an element with the
522    imported name.
523
524    .. versionadded:: 0.4.0
525       Added ``source_comments`` and ``source_map_filename`` parameters.
526
527    .. versionchanged:: 0.6.0
528       The ``source_comments`` parameter becomes to take only :class:`bool`
529       instead of :class:`str`.
530
531    .. deprecated:: 0.6.0
532       Values like ``'none'``, ``'line_numbers'``, and ``'map'`` for
533       the ``source_comments`` parameter are deprecated.
534
535    .. versionadded:: 0.7.0
536       Added ``precision`` parameter.
537
538    .. versionadded:: 0.7.0
539       Added ``custom_functions`` parameter.
540
541    .. versionadded:: 0.11.0
542       ``source_map_filename`` no longer implies ``source_comments``.
543
544    .. versionadded:: 0.17.0
545       Added ``source_map_contents``, ``source_map_embed``,
546       ``omit_source_map_url``, and ``source_map_root`` parameters.
547
548    .. versionadded:: 0.18.0
549        The importer callbacks can now take a second argument, the previously-
550        resolved path, so that importers can do relative path resolution.
551
552    """
553    modes = set()
554    for mode_name in MODES:
555        if mode_name in kwargs:
556            modes.add(mode_name)
557    if not modes:
558        raise TypeError('choose one at least in ' + and_join(MODES))
559    elif len(modes) > 1:
560        raise TypeError(
561            and_join(modes) + ' are exclusive each other; '
562            'cannot be used at a time',
563        )
564    precision = kwargs.pop('precision', 5)
565    output_style = kwargs.pop('output_style', 'nested')
566    if not isinstance(output_style, string_types):
567        raise TypeError(
568            'output_style must be a string, not ' +
569            repr(output_style),
570        )
571    try:
572        output_style = OUTPUT_STYLES[output_style]
573    except KeyError:
574        raise CompileError(
575            '{} is unsupported output_style; choose one of {}'
576            ''.format(output_style, and_join(OUTPUT_STYLES)),
577        )
578    source_comments = kwargs.pop('source_comments', False)
579    if source_comments in SOURCE_COMMENTS:
580        if source_comments == 'none':
581            deprecation_message = (
582                'you can simply pass False to '
583                "source_comments instead of 'none'"
584            )
585            source_comments = False
586        elif source_comments in ('line_numbers', 'default'):
587            deprecation_message = (
588                'you can simply pass True to '
589                "source_comments instead of " +
590                repr(source_comments)
591            )
592            source_comments = True
593        else:
594            deprecation_message = (
595                "you don't have to pass 'map' to "
596                'source_comments but just need to '
597                'specify source_map_filename'
598            )
599            source_comments = False
600        warnings.warn(
601            "values like 'none', 'line_numbers', and 'map' for "
602            'the source_comments parameter are deprecated; ' +
603            deprecation_message,
604            FutureWarning,
605        )
606    if not isinstance(source_comments, bool):
607        raise TypeError(
608            'source_comments must be bool, not ' +
609            repr(source_comments),
610        )
611    fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
612
613    def _get_file_arg(key):
614        ret = kwargs.pop(key, None)
615        if ret is not None and not isinstance(ret, string_types):
616            raise TypeError('{} must be a string, not {!r}'.format(key, ret))
617        elif isinstance(ret, text_type):
618            ret = ret.encode(fs_encoding)
619        if ret and 'filename' not in modes:
620            raise CompileError(
621                '{} is only available with filename= keyword argument since '
622                'has to be aware of it'.format(key),
623            )
624        return ret
625
626    source_map_filename = _get_file_arg('source_map_filename')
627    output_filename_hint = _get_file_arg('output_filename_hint')
628
629    source_map_contents = kwargs.pop('source_map_contents', False)
630    source_map_embed = kwargs.pop('source_map_embed', False)
631    omit_source_map_url = kwargs.pop('omit_source_map_url', False)
632    source_map_root = kwargs.pop('source_map_root', None)
633
634    if isinstance(source_map_root, text_type):
635        source_map_root = source_map_root.encode('utf-8')
636
637    # #208: cwd is always included in include paths
638    include_paths = (os.getcwd(),)
639    include_paths += tuple(kwargs.pop('include_paths', ()) or ())
640    include_paths = os.pathsep.join(include_paths)
641    if isinstance(include_paths, text_type):
642        include_paths = include_paths.encode(fs_encoding)
643
644    custom_functions = kwargs.pop('custom_functions', ())
645    if isinstance(custom_functions, collections_abc.Mapping):
646        custom_functions = [
647            SassFunction.from_lambda(name, lambda_)
648            for name, lambda_ in custom_functions.items()
649        ]
650    elif isinstance(
651            custom_functions,
652            (collections_abc.Set, collections_abc.Sequence),
653    ):
654        custom_functions = [
655            func if isinstance(func, SassFunction)
656            else SassFunction.from_named_function(func)
657            for func in custom_functions
658        ]
659    else:
660        raise TypeError(
661            'custom_functions must be one of:\n'
662            '- a set/sequence of {0.__module__}.{0.__name__} objects,\n'
663            '- a mapping of function name strings to lambda functions,\n'
664            '- a set/sequence of named functions,\n'
665            'not {1!r}'.format(SassFunction, custom_functions),
666        )
667
668    if kwargs.pop('custom_import_extensions', None) is not None:
669        warnings.warn(
670            '`custom_import_extensions` has no effect and will be removed in '
671            'a future version.',
672            FutureWarning,
673        )
674
675    importers = _validate_importers(kwargs.pop('importers', None))
676
677    if 'string' in modes:
678        string = kwargs.pop('string')
679        if isinstance(string, text_type):
680            string = string.encode('utf-8')
681        indented = kwargs.pop('indented', False)
682        if not isinstance(indented, bool):
683            raise TypeError(
684                'indented must be bool, not ' +
685                repr(source_comments),
686            )
687        _check_no_remaining_kwargs(compile, kwargs)
688        s, v = _sass.compile_string(
689            string, output_style, source_comments, include_paths, precision,
690            custom_functions, indented, importers,
691            source_map_contents, source_map_embed, omit_source_map_url,
692            source_map_root,
693        )
694        if s:
695            return v.decode('utf-8')
696    elif 'filename' in modes:
697        filename = kwargs.pop('filename')
698        if not isinstance(filename, string_types):
699            raise TypeError('filename must be a string, not ' + repr(filename))
700        elif not os.path.isfile(filename):
701            raise IOError('{!r} seems not a file'.format(filename))
702        elif isinstance(filename, text_type):
703            filename = filename.encode(fs_encoding)
704        _check_no_remaining_kwargs(compile, kwargs)
705        s, v, source_map = _sass.compile_filename(
706            filename, output_style, source_comments, include_paths, precision,
707            source_map_filename, custom_functions, importers,
708            output_filename_hint,
709            source_map_contents, source_map_embed, omit_source_map_url,
710            source_map_root,
711        )
712        if s:
713            v = v.decode('utf-8')
714            if source_map_filename:
715                source_map = source_map.decode('utf-8')
716                v = v, source_map
717            return v
718    elif 'dirname' in modes:
719        try:
720            search_path, output_path = kwargs.pop('dirname')
721        except ValueError:
722            raise ValueError(
723                'dirname must be a pair of (source_dir, '
724                'output_dir)',
725            )
726        _check_no_remaining_kwargs(compile, kwargs)
727        s, v = compile_dirname(
728            search_path, output_path, output_style, source_comments,
729            include_paths, precision, custom_functions, importers,
730            source_map_contents, source_map_embed, omit_source_map_url,
731            source_map_root,
732        )
733        if s:
734            return
735    else:
736        raise TypeError('something went wrong')
737    assert not s
738    raise CompileError(v)
739
740
741def and_join(strings):
742    """Join the given ``strings`` by commas with last `' and '` conjunction.
743
744    >>> and_join(['Korea', 'Japan', 'China', 'Taiwan'])
745    'Korea, Japan, China, and Taiwan'
746
747    :param strings: a list of words to join
748    :type string: :class:`collections.abc.Sequence`
749    :returns: a joined string
750    :rtype: :class:`str`, :class:`basestring`
751
752    """
753    last = len(strings) - 1
754    if last == 0:
755        return strings[0]
756    elif last < 0:
757        return ''
758    iterator = enumerate(strings)
759    return ', '.join('and ' + s if i == last else s for i, s in iterator)
760
761
762"""
763This module provides datatypes to be used in custom sass functions.
764
765The following mappings from sass types to python types are used:
766
767SASS_NULL: ``None``
768SASS_BOOLEAN: ``True`` or ``False``
769SASS_STRING: class:`str`
770SASS_NUMBER: class:`SassNumber`
771SASS_COLOR: class:`SassColor`
772SASS_LIST: class:`SassList`
773SASS_MAP: class:`dict` or class:`SassMap`
774SASS_ERROR: class:`SassError`
775SASS_WARNING: class:`SassWarning`
776"""
777
778
779class SassNumber(collections.namedtuple('SassNumber', ('value', 'unit'))):
780
781    def __new__(cls, value, unit):
782        value = float(value)
783        if not isinstance(unit, text_type):
784            unit = unit.decode('UTF-8')
785        return super(SassNumber, cls).__new__(cls, value, unit)
786
787
788class SassColor(collections.namedtuple('SassColor', ('r', 'g', 'b', 'a'))):
789
790    def __new__(cls, r, g, b, a):
791        r = float(r)
792        g = float(g)
793        b = float(b)
794        a = float(a)
795        return super(SassColor, cls).__new__(cls, r, g, b, a)
796
797
798SASS_SEPARATOR_COMMA = collections.namedtuple('SASS_SEPARATOR_COMMA', ())()
799SASS_SEPARATOR_SPACE = collections.namedtuple('SASS_SEPARATOR_SPACE', ())()
800SEPARATORS = frozenset((SASS_SEPARATOR_COMMA, SASS_SEPARATOR_SPACE))
801
802
803class SassList(
804    collections.namedtuple(
805            'SassList', ('items', 'separator', 'bracketed'),
806    ),
807):
808
809    def __new__(cls, items, separator, bracketed=False):
810        items = tuple(items)
811        assert separator in SEPARATORS, separator
812        assert isinstance(bracketed, bool), bracketed
813        return super(SassList, cls).__new__(cls, items, separator, bracketed)
814
815
816class SassError(collections.namedtuple('SassError', ('msg',))):
817
818    def __new__(cls, msg):
819        if not isinstance(msg, text_type):
820            msg = msg.decode('UTF-8')
821        return super(SassError, cls).__new__(cls, msg)
822
823
824class SassWarning(collections.namedtuple('SassWarning', ('msg',))):
825
826    def __new__(cls, msg):
827        if not isinstance(msg, text_type):
828            msg = msg.decode('UTF-8')
829        return super(SassWarning, cls).__new__(cls, msg)
830
831
832class SassMap(collections_abc.Mapping):
833    """Because sass maps can have mapping types as keys, we need an immutable
834    hashable mapping type.
835
836    .. versionadded:: 0.7.0
837
838    """
839
840    __slots__ = '_dict', '_hash'
841
842    def __init__(self, *args, **kwargs):
843        self._dict = dict(*args, **kwargs)
844        # An assertion that all things are hashable
845        self._hash = hash(frozenset(self._dict.items()))
846
847    # Mapping interface
848
849    def __getitem__(self, key):
850        return self._dict[key]
851
852    def __iter__(self):
853        return iter(self._dict)
854
855    def __len__(self):
856        return len(self._dict)
857
858    # Our interface
859
860    def __repr__(self):
861        return '{}({})'.format(type(self).__name__, frozenset(self.items()))
862
863    def __hash__(self):
864        return self._hash
865
866    def _immutable(self, *_):
867        raise TypeError('SassMaps are immutable.')
868
869    __setitem__ = __delitem__ = _immutable
870