|
24 | 24 | from _sass import OUTPUT_STYLES, compile_filename, compile_string
|
25 | 25 |
|
26 | 26 | __all__ = ('MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError',
|
27 |
| - 'and_join', 'compile') |
| 27 | + 'SassColor', 'SassError', 'SassFunction', 'SassList', 'SassMap', |
| 28 | + 'SassNumber', 'SassWarning', 'and_join', 'compile') |
28 | 29 | __version__ = '0.7.0'
|
29 | 30 |
|
30 | 31 |
|
@@ -59,21 +60,86 @@ def mkdirp(path):
|
59 | 60 | raise
|
60 | 61 |
|
61 | 62 |
|
62 |
| -def _prepare_custom_function_list(custom_functions): |
63 |
| - # (signature, function_reference) |
64 |
| - custom_function_list = [] |
65 |
| - for func_name, func in sorted(custom_functions.items()): |
66 |
| - argspec = inspect.getargspec(func) |
| 63 | +class SassFunction(object): |
| 64 | + """Custom function for Sass. It can be instantiated using |
| 65 | + :meth:`from_lambda()` and :meth:`from_named_function()` as well. |
| 66 | +
|
| 67 | + :param name: the function name |
| 68 | + :type name: :class:`str` |
| 69 | + :param arguments: the argument names |
| 70 | + :type arguments: :class:`collections.Sequence` |
| 71 | + :param callable_: the actual function to be called |
| 72 | + :type callable_: :class:`collections.Callable` |
| 73 | +
|
| 74 | + .. versionadded:: 0.7.0 |
| 75 | +
|
| 76 | + """ |
| 77 | + |
| 78 | + __slots__ = 'name', 'arguments', 'callable_' |
| 79 | + |
| 80 | + @classmethod |
| 81 | + def from_lambda(cls, name, lambda_): |
| 82 | + """Make a :class:`SassFunction` object from the given ``lambda_`` |
| 83 | + function. Since lambda functions don't have their name, it need |
| 84 | + its ``name`` as well. Arguments are automatically inspected. |
| 85 | +
|
| 86 | + :param name: the function name |
| 87 | + :type name: :class:`str` |
| 88 | + :param lambda_: the actual lambda function to be called |
| 89 | + :type lambda_: :class:`types.LambdaType` |
| 90 | + :returns: a custom function wrapper of the ``lambda_`` function |
| 91 | + :rtype: :class:`SassFunction` |
| 92 | +
|
| 93 | + """ |
| 94 | + argspec = inspect.getargspec(lambda_) |
67 | 95 | if argspec.varargs or argspec.keywords or argspec.defaults:
|
68 | 96 | raise TypeError(
|
69 |
| - 'Functions cannot have starargs or defaults: {0} {1}'.format( |
70 |
| - func_name, func, |
| 97 | + 'functions cannot have starargs or defaults: {0} {1}'.format( |
| 98 | + name, lambda_ |
71 | 99 | )
|
72 | 100 | )
|
73 |
| - blinged_args = ['$' + arg for arg in argspec.args] |
74 |
| - signature = '{0}({1})'.format(func_name, ', '.join(blinged_args)) |
75 |
| - custom_function_list.append((signature.encode('UTF-8'), func)) |
76 |
| - return custom_function_list |
| 101 | + return cls(name, argspec.args, lambda_) |
| 102 | + |
| 103 | + @classmethod |
| 104 | + def from_named_function(cls, function): |
| 105 | + """Make a :class:`SassFunction` object from the named ``function``. |
| 106 | + Function name and arguments are automatically inspected. |
| 107 | +
|
| 108 | + :param function: the named function to be called |
| 109 | + :type function: :class:`types.FunctionType` |
| 110 | + :returns: a custom function wrapper of the ``function`` |
| 111 | + :rtype: :class:`SassFunction` |
| 112 | +
|
| 113 | + """ |
| 114 | + if not getattr(function, '__name__', ''): |
| 115 | + raise TypeError('function must be named') |
| 116 | + return cls.from_lambda(function.__name__, function) |
| 117 | + |
| 118 | + def __init__(self, name, arguments, callable_): |
| 119 | + if not isinstance(name, string_types): |
| 120 | + raise TypeError('name must be a string, not ' + repr(name)) |
| 121 | + elif not isinstance(arguments, collections.Sequence): |
| 122 | + raise TypeError('arguments must be a sequence, not ' + |
| 123 | + repr(arguments)) |
| 124 | + elif not callable(callable_): |
| 125 | + raise TypeError(repr(callable_) + ' is not callable') |
| 126 | + self.name = name |
| 127 | + self.arguments = tuple( |
| 128 | + arg if arg.startswith('$') else '$' + arg |
| 129 | + for arg in arguments |
| 130 | + ) |
| 131 | + self.callable_ = callable_ |
| 132 | + |
| 133 | + @property |
| 134 | + def signature(self): |
| 135 | + """Signature string of the function.""" |
| 136 | + return '{0}({1})'.format(self.name, ', '.join(self.arguments)) |
| 137 | + |
| 138 | + def __call__(self, *args, **kwargs): |
| 139 | + return self.callable_(*args, **kwargs) |
| 140 | + |
| 141 | + def __str__(self): |
| 142 | + return self.signature |
77 | 143 |
|
78 | 144 |
|
79 | 145 | def compile_dirname(
|
@@ -130,8 +196,12 @@ def compile(**kwargs):
|
130 | 196 | :type image_path: :class:`str`
|
131 | 197 | :param precision: optional precision for numbers. :const:`5` by default.
|
132 | 198 | :type precision: :class:`int`
|
133 |
| - :param custom_functions: optional mapping of custom functions |
134 |
| - :type custom_functions: :class:`collections.Mapping` |
| 199 | + :param custom_functions: optional mapping of custom functions. |
| 200 | + see also below `custom functions |
| 201 | + <custom-functions>`_ description |
| 202 | + :type custom_functions: :class:`collections.Set`, |
| 203 | + :class:`collections.Sequence`, |
| 204 | + :class:`collections.Mapping` |
135 | 205 | :returns: the compiled CSS string
|
136 | 206 | :rtype: :class:`str`
|
137 | 207 | :raises sass.CompileError: when it fails for any reason
|
@@ -163,8 +233,12 @@ def compile(**kwargs):
|
163 | 233 | :type image_path: :class:`str`
|
164 | 234 | :param precision: optional precision for numbers. :const:`5` by default.
|
165 | 235 | :type precision: :class:`int`
|
166 |
| - :param custom_functions: optional mapping of custom functions |
167 |
| - :type custom_functions: :class:`collections.Mapping` |
| 236 | + :param custom_functions: optional mapping of custom functions. |
| 237 | + see also below `custom functions |
| 238 | + <custom-functions>`_ description |
| 239 | + :type custom_functions: :class:`collections.Set`, |
| 240 | + :class:`collections.Sequence`, |
| 241 | + :class:`collections.Mapping` |
168 | 242 | :returns: the compiled CSS string, or a pair of the compiled CSS string
|
169 | 243 | and the source map string if ``source_comments='map'``
|
170 | 244 | :rtype: :class:`str`, :class:`tuple`
|
@@ -199,11 +273,68 @@ def compile(**kwargs):
|
199 | 273 | :type image_path: :class:`str`
|
200 | 274 | :param precision: optional precision for numbers. :const:`5` by default.
|
201 | 275 | :type precision: :class:`int`
|
202 |
| - :param custom_functions: optional mapping of custom functions |
203 |
| - :type custom_functions: :class:`collections.Mapping` |
| 276 | + :param custom_functions: optional mapping of custom functions. |
| 277 | + see also below `custom functions |
| 278 | + <custom-functions>`_ description |
| 279 | + :type custom_functions: :class:`collections.Set`, |
| 280 | + :class:`collections.Sequence`, |
| 281 | + :class:`collections.Mapping` |
204 | 282 | :raises sass.CompileError: when it fails for any reason
|
205 | 283 | (for example the given SASS has broken syntax)
|
206 | 284 |
|
| 285 | + .. _custom-functions: |
| 286 | +
|
| 287 | + The ``custom_functions`` parameter can take three types of forms: |
| 288 | +
|
| 289 | + :class:`~collections.Set`/:class:`~collections.Sequence` of \ |
| 290 | + :class:`SassFunction`\ s |
| 291 | + It is the most general form. Although pretty verbose, it can take |
| 292 | + any kind of callables like type objects, unnamed functions, |
| 293 | + and user-defined callables. |
| 294 | +
|
| 295 | + .. code-block:: python |
| 296 | +
|
| 297 | + sass.compile( |
| 298 | + ..., |
| 299 | + custom_functions={ |
| 300 | + sass.SassFunction('func-name', ('$a', '$b'), some_callable), |
| 301 | + ... |
| 302 | + } |
| 303 | + ) |
| 304 | +
|
| 305 | + :class:`~collections.Mapping` of names to functions |
| 306 | + Less general, but easier-to-use form. Although it's not it can take |
| 307 | + any kind of callables, it can take any kind of *functions* defined |
| 308 | + using :keyword:`def`/:keyword:`lambda` syntax. |
| 309 | + It cannot take callables other than them since inspecting arguments |
| 310 | + is not always available for every kind of callables. |
| 311 | +
|
| 312 | + .. code-block:: python |
| 313 | +
|
| 314 | + sass.compile( |
| 315 | + ..., |
| 316 | + custom_functions={ |
| 317 | + 'func-name': lambda a, b: ..., |
| 318 | + ... |
| 319 | + } |
| 320 | + ) |
| 321 | +
|
| 322 | + :class:`~collections.Set`/:class:`~collections.Sequence` of \ |
| 323 | + named functions |
| 324 | + Not general, but the easiest-to-use form for *named* functions. |
| 325 | + It can take only named functions, defined using :keyword:`def`. |
| 326 | + It cannot take lambdas sinc names are unavailable for them. |
| 327 | +
|
| 328 | + .. code-block:: python |
| 329 | +
|
| 330 | + def func_name(a, b): |
| 331 | + return ... |
| 332 | +
|
| 333 | + sass.compile( |
| 334 | + ..., |
| 335 | + custom_functions={func_name} |
| 336 | + ) |
| 337 | +
|
207 | 338 | .. versionadded:: 0.4.0
|
208 | 339 | Added ``source_comments`` and ``source_map_filename`` parameters.
|
209 | 340 |
|
@@ -304,8 +435,26 @@ def compile(**kwargs):
|
304 | 435 | elif isinstance(image_path, text_type):
|
305 | 436 | image_path = image_path.encode(fs_encoding)
|
306 | 437 |
|
307 |
| - custom_functions = dict(kwargs.pop('custom_functions', {})) |
308 |
| - custom_functions = _prepare_custom_function_list(custom_functions) |
| 438 | + custom_functions = kwargs.pop('custom_functions', ()) |
| 439 | + if isinstance(custom_functions, collections.Mapping): |
| 440 | + custom_functions = [ |
| 441 | + SassFunction.from_lambda(name, lambda_) |
| 442 | + for name, lambda_ in custom_functions.items() |
| 443 | + ] |
| 444 | + elif isinstance(custom_functions, (collections.Set, collections.Sequence)): |
| 445 | + custom_functions = [ |
| 446 | + func if isinstance(func, SassFunction) |
| 447 | + else SassFunction.from_named_function(func) |
| 448 | + for func in custom_functions |
| 449 | + ] |
| 450 | + else: |
| 451 | + raise TypeError( |
| 452 | + 'custom_functions must be one of:\n' |
| 453 | + '- a set/sequence of {0.__module__}.{0.__name__} objects,\n' |
| 454 | + '- a mapping of function name strings to lambda functions,\n' |
| 455 | + '- a set/sequence of named functions,\n' |
| 456 | + 'not {1!r}'.format(SassFunction, custom_functions) |
| 457 | + ) |
309 | 458 |
|
310 | 459 | if 'string' in modes:
|
311 | 460 | string = kwargs.pop('string')
|
|
0 commit comments