diff --git a/.travis.yml b/.travis.yml index 5380f0af..a1bd3871 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python +dist: trusty python: -- pypy -- pypy3 +- pypy-5.4.1 - 2.7 - 3.4 - 3.5 diff --git a/pysass.cpp b/pysass.cpp index 51f4c8c8..7f5706a6 100644 --- a/pysass.cpp +++ b/pysass.cpp @@ -560,14 +560,14 @@ 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; + *result, *output_filename_hint; if (!PyArg_ParseTuple(args, - PySass_IF_PY3("yiiyiOOO", "siisiOOO"), + PySass_IF_PY3("yiiyiOOOO", "siisiOOOO"), &filename, &output_style, &source_comments, &include_paths, &precision, &source_map_filename, &custom_functions, - &custom_importers)) { + &custom_importers, &output_filename_hint)) { return NULL; } @@ -575,12 +575,17 @@ PySass_compile_filename(PyObject *self, PyObject *args) { options = sass_file_context_get_options(context); if (PyBytes_Check(source_map_filename)) { - size_t source_map_file_len = PyBytes_GET_SIZE(source_map_filename); - if (source_map_file_len) { - char *source_map_file = sass_copy_c_string( - PyBytes_AS_STRING(source_map_filename) + if (PyBytes_GET_SIZE(source_map_filename)) { + sass_option_set_source_map_file( + options, PyBytes_AS_STRING(source_map_filename) + ); + } + } + if (PyBytes_Check(output_filename_hint)) { + if (PyBytes_GET_SIZE(output_filename_hint)) { + sass_option_set_output_path( + options, PyBytes_AS_STRING(output_filename_hint) ); - sass_option_set_source_map_file(options, source_map_file); } } sass_option_set_output_style(options, output_style); diff --git a/sass.py b/sass.py index ff18885a..49a120b4 100644 --- a/sass.py +++ b/sass.py @@ -239,7 +239,7 @@ def compile_dirname( input_filename = input_filename.encode(fs_encoding) s, v, _ = _sass.compile_filename( input_filename, output_style, source_comments, include_paths, - precision, None, custom_functions, importers + precision, None, custom_functions, importers, None, ) if s: v = v.decode('UTF-8') @@ -539,30 +539,32 @@ def my_importer(path): raise TypeError('source_comments must be bool, not ' + repr(source_comments)) fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - source_map_filename = kwargs.pop('source_map_filename', None) - if not (source_map_filename is None or - isinstance(source_map_filename, string_types)): - raise TypeError('source_map_filename must be a string, not ' + - repr(source_map_filename)) - elif isinstance(source_map_filename, text_type): - source_map_filename = source_map_filename.encode(fs_encoding) - if not ('filename' in modes or source_map_filename is None): - raise CompileError('source_map_filename is only available with ' - 'filename= keyword argument since it has to be ' - 'aware of it') - try: - include_paths = kwargs.pop('include_paths') or b'' - except KeyError: - include_paths = b'' - else: - if isinstance(include_paths, collections.Sequence): - include_paths = os.pathsep.join(include_paths) - elif not isinstance(include_paths, string_types): - raise TypeError('include_paths must be a sequence of strings, or ' - 'a colon-separated (or semicolon-separated if ' - 'Windows) string, not ' + repr(include_paths)) - if isinstance(include_paths, text_type): - include_paths = include_paths.encode(fs_encoding) + + def _get_file_arg(key): + ret = kwargs.pop(key, None) + if ret is not None and not isinstance(ret, string_types): + raise TypeError('{} must be a string, not {!r}'.format(key, ret)) + elif isinstance(ret, text_type): + ret = ret.encode(fs_encoding) + if ret and 'filename' not in modes: + raise CompileError( + '{} is only available with filename= keyword argument since ' + 'has to be aware of it'.format(key) + ) + return ret + + source_map_filename = _get_file_arg('source_map_filename') + output_filename_hint = _get_file_arg('output_filename_hint') + + include_paths = kwargs.pop('include_paths', b'') or b'' + if isinstance(include_paths, collections.Sequence): + include_paths = os.pathsep.join(include_paths) + elif not isinstance(include_paths, string_types): + raise TypeError('include_paths must be a sequence of strings, or ' + 'a colon-separated (or semicolon-separated if ' + 'Windows) string, not ' + repr(include_paths)) + if isinstance(include_paths, text_type): + include_paths = include_paths.encode(fs_encoding) custom_functions = kwargs.pop('custom_functions', ()) if isinstance(custom_functions, collections.Mapping): @@ -614,6 +616,7 @@ def my_importer(path): s, v, source_map = _sass.compile_filename( filename, output_style, source_comments, include_paths, precision, source_map_filename, custom_functions, importers, + output_filename_hint, ) if s: v = v.decode('utf-8') diff --git a/sassc.py b/sassc.py index 6de67d18..15461a8a 100755 --- a/sassc.py +++ b/sassc.py @@ -153,6 +153,7 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): output_style=options.style, source_comments=options.source_comments, source_map_filename=source_map_filename, + output_filename_hint=args[1], include_paths=options.include_paths, precision=options.precision ) diff --git a/sasstests.py b/sasstests.py index 440d448a..a50b5f5f 100644 --- a/sasstests.py +++ b/sasstests.py @@ -773,27 +773,6 @@ def test_sassc_source_map_without_css_filename(self): 'actual error message is: ' + repr(err) assert self.out.getvalue() == '' - def test_sassc_sourcemap(self): - with tempdir() as tmp_dir: - src_dir = os.path.join(tmp_dir, 'test') - shutil.copytree('test', src_dir) - src_filename = os.path.join(src_dir, 'a.scss') - out_filename = os.path.join(tmp_dir, 'a.scss.css') - exit_code = sassc.main( - ['sassc', '-m', src_filename, out_filename], - self.out, self.err - ) - assert exit_code == 0 - assert self.err.getvalue() == '' - assert self.out.getvalue() == '' - with open(out_filename) as f: - assert A_EXPECTED_CSS_WITH_MAP == f.read().strip() - with open(out_filename + '.map') as f: - self.assert_source_map_equal( - dict(A_EXPECTED_MAP, sources=None), - dict(json.load(f), sources=None) - ) - @contextlib.contextmanager def tempdir(): @@ -1399,3 +1378,32 @@ def test_stack_trace_formatting(): def test_source_comments(): out = sass.compile(string='a{color: red}', source_comments=True) assert out == '/* line 1, stdin */\na {\n color: red; }\n' + + +def test_sassc_sourcemap(tmpdir): + src_file = tmpdir.join('src').ensure_dir().join('a.scss') + out_file = tmpdir.join('a.scss.css') + out_map_file = tmpdir.join('a.scss.css.map') + + src_file.write('.c { font-size: 5px + 5px; }') + + exit_code = sassc.main([ + 'sassc', '-m', src_file.strpath, out_file.strpath, + ]) + assert exit_code == 0 + + contents = out_file.read() + assert contents == ( + '.c {\n' + ' font-size: 10px; }\n' + '\n' + '/*# sourceMappingURL=a.scss.css.map */' + ) + source_map_json = json.loads(out_map_file.read()) + assert source_map_json == { + 'sources': ['src/a.scss'], + 'version': 3, + 'names': [], + 'file': 'a.scss.css', + 'mappings': 'AAAA,AAAA,EAAE,CAAC;EAAE,SAAS,EAAE,IAAS,GAAI', + }