diff --git a/pysass.cpp b/pysass.cpp index 5cbf6fc7..3e36c0b6 100644 --- a/pysass.cpp +++ b/pysass.cpp @@ -505,6 +505,17 @@ static void _add_custom_importers( sass_option_set_c_importers(options, importer_list); } +static void _add_custom_import_extensions( + struct Sass_Options* options, PyObject* custom_import_extensions +) { + Py_ssize_t i; + + for (i = 0; i < PyList_GET_SIZE(custom_import_extensions); i += 1) { + PyObject* ext = PyList_GET_ITEM(custom_import_extensions, i); + sass_option_push_import_extension(options, PyBytes_AS_STRING(ext)); + } +} + static PyObject * PySass_compile_string(PyObject *self, PyObject *args) { struct Sass_Context *ctx; @@ -516,13 +527,15 @@ PySass_compile_string(PyObject *self, PyObject *args) { int source_comments, error_status, precision, indented; PyObject *custom_functions; PyObject *custom_importers; + PyObject *custom_import_extensions; PyObject *result; if (!PyArg_ParseTuple(args, - PySass_IF_PY3("yiiyiOiO", "siisiOiO"), + PySass_IF_PY3("yiiyiOiOO", "siisiOiOO"), &string, &output_style, &source_comments, &include_paths, &precision, - &custom_functions, &indented, &custom_importers)) { + &custom_functions, &indented, &custom_importers, + &custom_import_extensions)) { return NULL; } @@ -535,6 +548,7 @@ PySass_compile_string(PyObject *self, PyObject *args) { sass_option_set_is_indented_syntax_src(options, indented); _add_custom_functions(options, custom_functions); _add_custom_importers(options, custom_importers); + _add_custom_import_extensions(options, custom_import_extensions); sass_compile_data_context(context); ctx = sass_data_context_get_context(context); @@ -560,14 +574,15 @@ PySass_compile_filename(PyObject *self, PyObject *args) { Sass_Output_Style output_style; int source_comments, error_status, precision; PyObject *source_map_filename, *custom_functions, *custom_importers, - *result, *output_filename_hint; + *result, *output_filename_hint, *custom_import_extensions; if (!PyArg_ParseTuple(args, - PySass_IF_PY3("yiiyiOOOO", "siisiOOOO"), + PySass_IF_PY3("yiiyiOOOOO", "siisiOOOOO"), &filename, &output_style, &source_comments, &include_paths, &precision, &source_map_filename, &custom_functions, - &custom_importers, &output_filename_hint)) { + &custom_importers, &output_filename_hint, + &custom_import_extensions)) { return NULL; } @@ -594,6 +609,7 @@ PySass_compile_filename(PyObject *self, PyObject *args) { sass_option_set_precision(options, precision); _add_custom_functions(options, custom_functions); _add_custom_importers(options, custom_importers); + _add_custom_import_extensions(options, custom_import_extensions); sass_compile_file_context(context); ctx = sass_file_context_get_context(context); diff --git a/sass.py b/sass.py index ba846a88..67ee6de3 100644 --- a/sass.py +++ b/sass.py @@ -222,7 +222,7 @@ def _raise(e): def compile_dirname( search_path, output_path, output_style, source_comments, include_paths, - precision, custom_functions, importers + precision, custom_functions, importers, custom_import_extensions ): fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() for dirpath, _, filenames in os.walk(search_path, onerror=_raise): @@ -240,6 +240,7 @@ def compile_dirname( s, v, _ = _sass.compile_filename( input_filename, output_style, source_comments, include_paths, precision, None, custom_functions, importers, None, + custom_import_extensions, ) if s: v = v.decode('UTF-8') @@ -292,6 +293,9 @@ def compile(**kwargs): :type custom_functions: :class:`set`, :class:`collections.abc.Sequence`, :class:`collections.abc.Mapping` + :param custom_import_extensions: optional extra file extensions which + allow can be imported, eg. ``['.css']`` + :type custom_import_extensions: :class:`list`, :class:`tuple` :param indented: optional declaration that the string is Sass, not SCSS formatted. :const:`False` by default :type indented: :class:`bool` @@ -332,6 +336,9 @@ def compile(**kwargs): :type custom_functions: :class:`set`, :class:`collections.abc.Sequence`, :class:`collections.abc.Mapping` + :param custom_import_extensions: optional extra file extensions which + allow can be imported, eg. ``['.css']`` + :type custom_import_extensions: :class:`list`, :class:`tuple` :param importers: optional callback functions. see also below `importer callbacks `_ description @@ -374,6 +381,9 @@ def compile(**kwargs): :type custom_functions: :class:`set`, :class:`collections.abc.Sequence`, :class:`collections.abc.Mapping` + :param custom_import_extensions: optional extra file extensions which + allow can be imported, eg. ``['.css']`` + :type custom_import_extensions: :class:`list`, :class:`tuple` :raises sass.CompileError: when it fails for any reason (for example the given Sass has broken syntax) @@ -584,6 +594,14 @@ def _get_file_arg(key): 'not {1!r}'.format(SassFunction, custom_functions) ) + _custom_exts = kwargs.pop('custom_import_extensions', []) or [] + if not isinstance(_custom_exts, (list, tuple)): + raise TypeError( + 'custom_import_extensions must be a list of strings ' + 'not {}'.format(type(_custom_exts)) + ) + custom_import_extensions = [ext.encode('utf-8') for ext in _custom_exts] + importers = _validate_importers(kwargs.pop('importers', None)) if 'string' in modes: @@ -597,7 +615,7 @@ def _get_file_arg(key): _check_no_remaining_kwargs(compile, kwargs) s, v = _sass.compile_string( string, output_style, source_comments, include_paths, precision, - custom_functions, indented, importers, + custom_functions, indented, importers, custom_import_extensions, ) if s: return v.decode('utf-8') @@ -613,7 +631,7 @@ def _get_file_arg(key): s, v, source_map = _sass.compile_filename( filename, output_style, source_comments, include_paths, precision, source_map_filename, custom_functions, importers, - output_filename_hint, + output_filename_hint, custom_import_extensions, ) if s: v = v.decode('utf-8') @@ -631,6 +649,7 @@ def _get_file_arg(key): s, v = compile_dirname( search_path, output_path, output_style, source_comments, include_paths, precision, custom_functions, importers, + custom_import_extensions ) if s: return diff --git a/sassc.py b/sassc.py index 893f1ac4..47b2fdf6 100755 --- a/sassc.py +++ b/sassc.py @@ -91,7 +91,7 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): '(output css filename).') parser.add_option('-I', '--include-path', metavar='DIR', dest='include_paths', action='append', - help='Path to find "@import"ed (S)CSS source files. ' + help='Path to find "@import"ed (S)CSS source files. ' 'Can be multiply used.') parser.add_option( '-p', '--precision', action='store', type='int', default=5, @@ -101,6 +101,10 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): '--source-comments', action='store_true', default=False, help='Include debug info in output', ) + parser.add_option('--import-extensions', + dest='custom_import_extensions', action='append', + help='Extra extensions allowed for sass imports. ' + 'Can be multiply used.') options, args = parser.parse_args(argv[1:]) error = functools.partial(print, parser.get_prog_name() + ': error:', @@ -130,7 +134,8 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): source_map_filename=source_map_filename, output_filename_hint=args[1], include_paths=options.include_paths, - precision=options.precision + precision=options.precision, + custom_import_extensions=options.custom_import_extensions, ) else: source_map_filename = None @@ -140,7 +145,8 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): output_style=options.style, source_comments=options.source_comments, include_paths=options.include_paths, - precision=options.precision + precision=options.precision, + custom_import_extensions=options.custom_import_extensions, ) except (IOError, OSError) as e: error(e) diff --git a/sasstests.py b/sasstests.py index 93f8a0b6..c87c3c1d 100644 --- a/sasstests.py +++ b/sasstests.py @@ -1409,3 +1409,59 @@ def test_imports_from_cwd(tmpdir): with tmpdir.as_cwd(): out = sass.compile(filename=main_scss.strpath) assert out == '' + + +def test_import_no_css(tmpdir): + tmpdir.join('other.css').write('body {color: green}') + main_scss = tmpdir.join('main.scss') + main_scss.write("@import 'other';") + with pytest.raises(sass.CompileError): + sass.compile(filename=main_scss.strpath) + + +@pytest.mark.parametrize('exts', [ + ('.css',), + ['.css'], + ['.foobar', '.css'], +]) +def test_import_css(exts, tmpdir): + tmpdir.join('other.css').write('body {color: green}') + main_scss = tmpdir.join('main.scss') + main_scss.write("@import 'other';") + out = sass.compile( + filename=main_scss.strpath, + custom_import_extensions=exts, + ) + assert out == 'body {\n color: green; }\n' + + +def test_import_css_error(tmpdir): + tmpdir.join('other.css').write('body {color: green}') + main_scss = tmpdir.join('main.scss') + main_scss.write("@import 'other';") + with pytest.raises(TypeError): + sass.compile( + filename=main_scss.strpath, + custom_import_extensions='.css', + ) + + +def test_import_css_string(tmpdir): + tmpdir.join('other.css').write('body {color: green}') + with tmpdir.as_cwd(): + out = sass.compile( + string="@import 'other';", + custom_import_extensions=['.css'], + ) + assert out == 'body {\n color: green; }\n' + + +def test_import_ext_other(tmpdir): + tmpdir.join('other.foobar').write('body {color: green}') + main_scss = tmpdir.join('main.scss') + main_scss.write("@import 'other';") + out = sass.compile( + filename=main_scss.strpath, + custom_import_extensions=['.foobar'], + ) + assert out == 'body {\n color: green; }\n'