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