From 202d65768921379d519c4b133a10040a8dea837f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 30 Jan 2015 13:24:53 -0800 Subject: [PATCH 1/2] Upgrade to libsass==3.1.0. Resolves #36. Resolves #38. --- libsass | 2 +- pysass.cpp | 122 +++++++++++++++++++++------------------------------ sass.py | 46 +++++++++++++++---- sasstests.py | 73 +++++++++++++++++++++++++++--- setup.py | 4 +- tox.ini | 2 +- 6 files changed, 161 insertions(+), 88 deletions(-) diff --git a/libsass b/libsass index 030e267e..31521ef3 160000 --- a/libsass +++ b/libsass @@ -1 +1 @@ -Subproject commit 030e267efc50bf829bbb6fda025f5ca265248742 +Subproject commit 31521ef3ece636892f395a80392448ceae449b90 diff --git a/pysass.cpp b/pysass.cpp index 30f0e6e2..480f6a99 100644 --- a/pysass.cpp +++ b/pysass.cpp @@ -2,7 +2,7 @@ #include #include #include -#include "sass_interface.h" +#include "sass_context.h" #if PY_MAJOR_VERSION >= 3 #define PySass_IF_PY3(three, two) (three) @@ -37,9 +37,13 @@ static struct PySass_Pair PySass_output_style_enum[] = { static PyObject * PySass_compile_string(PyObject *self, PyObject *args) { - struct sass_context *context; + struct Sass_Context *ctx; + struct Sass_Data_Context *context; + struct Sass_Options *options; char *string, *include_paths, *image_path; - int output_style, source_comments, precision; + const char *error_message, *output_string; + Sass_Output_Style output_style; + int source_comments, error_status, precision; PyObject *result; if (!PyArg_ParseTuple(args, @@ -49,30 +53,38 @@ PySass_compile_string(PyObject *self, PyObject *args) { return NULL; } - context = sass_new_context(); - context->source_string = string; - context->options.output_style = output_style; - context->options.source_comments = source_comments; - context->options.include_paths = include_paths; - context->options.image_path = image_path; - context->options.precision = precision; + context = sass_make_data_context(string); + options = sass_data_context_get_options(context); + sass_option_set_output_style(options, output_style); + sass_option_set_source_comments(options, source_comments); + sass_option_set_include_path(options, include_paths); + sass_option_set_image_path(options, image_path); + sass_option_set_precision(options, precision); - sass_compile(context); + sass_compile_data_context(context); + ctx = sass_data_context_get_context(context); + error_status = sass_context_get_error_status(ctx); + error_message = sass_context_get_error_message(ctx); + output_string = sass_context_get_output_string(ctx); result = Py_BuildValue( PySass_IF_PY3("hy", "hs"), - (short int) !context->error_status, - context->error_status ? context->error_message : context->output_string + (short int) !error_status, + error_status ? error_message : output_string ); - sass_free_context(context); + sass_delete_data_context(context); return result; } static PyObject * PySass_compile_filename(PyObject *self, PyObject *args) { - struct sass_file_context *context; + struct Sass_Context *ctx; + struct Sass_File_Context *context; + struct Sass_Options *options; char *filename, *include_paths, *image_path; - int output_style, source_comments, error_status, precision; + const char *error_message, *output_string, *source_map_string; + Sass_Output_Style output_style; + int source_comments, error_status, precision; PyObject *source_map_filename, *result; if (!PyArg_ParseTuple(args, @@ -82,73 +94,41 @@ PySass_compile_filename(PyObject *self, PyObject *args) { return NULL; } - context = sass_new_file_context(); - context->input_path = filename; + context = sass_make_file_context(filename); + options = sass_file_context_get_options(context); + if (source_comments && PySass_Bytes_Check(source_map_filename)) { size_t source_map_file_len = PySass_Bytes_GET_SIZE(source_map_filename); if (source_map_file_len) { char *source_map_file = (char *) malloc(source_map_file_len + 1); strncpy( - source_map_file, + source_map_file, PySass_Bytes_AS_STRING(source_map_filename), source_map_file_len + 1 ); - context->options.source_map_file = source_map_file; + sass_option_set_source_map_file(options, source_map_file); } } - context->options.output_style = output_style; - context->options.source_comments = source_comments; - context->options.include_paths = include_paths; - context->options.image_path = image_path; - context->options.precision = precision; - - sass_compile_file(context); - - error_status = context->error_status; + sass_option_set_output_style(options, output_style); + sass_option_set_source_comments(options, source_comments); + sass_option_set_include_path(options, include_paths); + sass_option_set_image_path(options, image_path); + sass_option_set_precision(options, precision); + + sass_compile_file_context(context); + + ctx = sass_file_context_get_context(context); + error_status = sass_context_get_error_status(ctx); + error_message = sass_context_get_error_message(ctx); + output_string = sass_context_get_output_string(ctx); + source_map_string = sass_context_get_source_map_string(ctx); result = Py_BuildValue( PySass_IF_PY3("hyy", "hss"), - (short int) !context->error_status, - error_status ? context->error_message : context->output_string, - error_status || context->source_map_string == NULL - ? "" - : context->source_map_string - ); - sass_free_file_context(context); - return result; -} - -static PyObject * -PySass_compile_dirname(PyObject *self, PyObject *args) { - struct sass_folder_context *context; - char *search_path, *output_path, *include_paths, *image_path; - int output_style, source_comments, precision; - PyObject *result; - - if (!PyArg_ParseTuple(args, - PySass_IF_PY3("yyiiyyi", "ssiissi"), - &search_path, &output_path, - &output_style, &source_comments, - &include_paths, &image_path, precision)) { - return NULL; - } - - context = sass_new_folder_context(); - context->search_path = search_path; - context->output_path = output_path; - context->options.output_style = output_style; - context->options.source_comments = source_comments; - context->options.include_paths = include_paths; - context->options.image_path = image_path; - context->options.precision = precision; - - sass_compile_folder(context); - - result = Py_BuildValue( - PySass_IF_PY3("hy", "hs"), - (short int) !context->error_status, - context->error_status ? context->error_message : NULL + (short int) !error_status, + error_status ? error_message : output_string, + error_status || source_map_string == NULL ? "" : source_map_string ); - sass_free_folder_context(context); + sass_delete_file_context(context); return result; } @@ -157,8 +137,6 @@ static PyMethodDef PySass_methods[] = { "Compile a SASS string."}, {"compile_filename", PySass_compile_filename, METH_VARARGS, "Compile a SASS file."}, - {"compile_dirname", PySass_compile_dirname, METH_VARARGS, - "Compile several SASS files."}, {NULL, NULL, 0, NULL} }; diff --git a/sass.py b/sass.py index 0b60784e..3676a240 100644 --- a/sass.py +++ b/sass.py @@ -19,8 +19,7 @@ from six import string_types, text_type -from _sass import (OUTPUT_STYLES, compile_dirname, - compile_filename, compile_string) +from _sass import OUTPUT_STYLES, compile_filename, compile_string __all__ = ('MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError', 'and_join', 'compile') @@ -46,10 +45,46 @@ class CompileError(ValueError): """The exception type that is raised by :func:`compile()`. It is a subtype of :exc:`exceptions.ValueError`. - """ +def mkdirp(path): + try: + os.makedirs(path) + except OSError: + if os.path.isdir(path): + return + raise + + +def compile_dirname( + search_path, output_path, output_style, source_comments, include_paths, + image_path, precision, +): + fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + for dirpath, _, filenames in os.walk(search_path): + filenames = [ + filename for filename in filenames if filename.endswith('.scss') + ] + for filename in filenames: + input_filename = os.path.join(dirpath, filename) + relpath_to_file = os.path.relpath(input_filename, search_path) + output_filename = os.path.join(output_path, relpath_to_file) + output_filename = re.sub('.scss$', '.css', output_filename) + input_filename = input_filename.encode(fs_encoding) + s, v, _ = compile_filename( + input_filename, output_style, source_comments, include_paths, + image_path, precision, None, + ) + if s: + v = v.decode('UTF-8') + mkdirp(os.path.dirname(output_filename)) + with open(output_filename, 'w') as output_file: + output_file.write(v) + else: + return False, v + return True, None + def compile(**kwargs): """There are three modes of parameters :func:`compile()` can take: ``string``, ``filename``, and ``dirname``. @@ -302,11 +337,6 @@ def compile(**kwargs): except ValueError: raise ValueError('dirname must be a pair of (source_dir, ' 'output_dir)') - else: - if isinstance(search_path, text_type): - search_path = search_path.encode(fs_encoding) - if isinstance(output_path, text_type): - output_path = output_path.encode(fs_encoding) s, v = compile_dirname(search_path, output_path, output_style, source_comments, include_paths, image_path, precision) diff --git a/sasstests.py b/sasstests.py index b3f11aff..e78432d3 100644 --- a/sasstests.py +++ b/sasstests.py @@ -2,6 +2,7 @@ from __future__ import with_statement import collections +import contextlib import glob import json import os @@ -56,7 +57,7 @@ def normalize_path(path): 'sources': ['test/a.scss'], 'sourcesContent': [], 'names': [], - 'mappings': ';AAKA;EAHE,kBAAkB;;EAIpB,KAAK;IAED,OAAO' + 'mappings': ';AAKA;EAHE,AAAkB;;EAKpB,AAAK;IACD,AAAO', } B_EXPECTED_CSS = '''\ @@ -82,6 +83,7 @@ def normalize_path(path): ''' D_EXPECTED_CSS = '''\ +@charset "UTF-8"; body { background-color: green; } body a { @@ -89,6 +91,7 @@ def normalize_path(path): ''' D_EXPECTED_CSS_WITH_MAP = '''\ +@charset "UTF-8"; /* line 6, SOURCE */ body { background-color: green; } @@ -240,7 +243,8 @@ def test_compile_string(self): ''' actual = sass.compile(string=u'a { color: blue; } /* 유니코드 */') self.assertEqual( - u'''a { + u'''@charset "UTF-8"; +a { color: blue; } /* 유니코드 */''', @@ -458,7 +462,7 @@ def test_build_one(self): 'sources': ['../test/b.scss'], 'sourcesContent': [], 'names': [], - 'mappings': ';AAAA,EAAE;EAEE,WAAW' + 'mappings': ';AACA,AAAE;EACE,AAAW', }, os.path.join(d, 'css', 'b.scss.css.map') ) @@ -476,7 +480,7 @@ def test_build_one(self): 'sources': ['../test/d.scss'], 'sourcesContent': [], 'names': [], - 'mappings': ';AAKA;EAHE,kBAAkB;;EAIpB,KAAK;IAED,MAAM' + 'mappings': ';AAKA;EAHE,AAAkB;;EAKpB,AAAK;IACD,AAAM', }, os.path.join(d, 'css', 'd.scss.css.map') ) @@ -673,6 +677,64 @@ def test_sassc_sourcemap(self): shutil.rmtree(tmp_dir) +@contextlib.contextmanager +def tempdir(): + tmpdir = tempfile.mkdtemp() + try: + yield tmpdir + finally: + shutil.rmtree(tmpdir) + + +def write_file(filename, contents): + with open(filename, 'w') as f: + f.write(contents) + + +class CompileDirectoriesTest(unittest.TestCase): + + def test_successful(self): + with tempdir() as tmpdir: + input_dir = os.path.join(tmpdir, 'input') + output_dir = os.path.join(tmpdir, 'output') + os.makedirs(os.path.join(input_dir, 'foo')) + write_file(os.path.join(input_dir, 'f1.scss'), 'a { b { width: 100%; } }') + write_file(os.path.join(input_dir, 'foo/f2.scss'), 'foo { width: 100%; }') + # Make sure we don't compile non-scss files + write_file(os.path.join(input_dir, 'baz.txt'), 'Hello der') + + # the api for this is weird, why does it need source? + sass.compile(dirname=(input_dir, output_dir)) + assert os.path.exists(output_dir) + assert os.path.exists(os.path.join(output_dir, 'foo')) + assert os.path.exists(os.path.join(output_dir, 'f1.css')) + assert os.path.exists(os.path.join(output_dir, 'foo/f2.css')) + assert not os.path.exists(os.path.join(output_dir, 'baz.txt')) + + contentsf1 = open(os.path.join(output_dir, 'f1.css')).read() + contentsf2 = open(os.path.join(output_dir, 'foo/f2.css')).read() + self.assertEqual(contentsf1, 'a b {\n width: 100%; }\n') + self.assertEqual(contentsf2, 'foo {\n width: 100%; }\n') + + def test_error(self): + with tempdir() as tmpdir: + input_dir = os.path.join(tmpdir, 'input') + os.makedirs(input_dir) + write_file(os.path.join(input_dir, 'bad.scss'), 'a {') + + try: + sass.compile(dirname=(input_dir, os.path.join(tmpdir, 'output'))) + assert False, 'Expected to raise' + except sass.CompileError as e: + msg, = e.args + assert msg.decode('UTF-8').endswith( + 'bad.scss:1: invalid property name\n' + ), msg + return + except Exception as e: + assert False, 'Expected to raise CompileError but got {0!r}'.format(e) + + test_cases = [ SassTestCase, CompileTestCase, @@ -680,7 +742,8 @@ def test_sassc_sourcemap(self): ManifestTestCase, WsgiTestCase, DistutilsTestCase, - SasscTestCase + SasscTestCase, + CompileDirectoriesTest, ] loader = unittest.defaultTestLoader suite = unittest.TestSuite() diff --git a/setup.py b/setup.py index 590d9833..7a310230 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,9 @@ sources_match = MAKEFILE_SOURCES_LIST_RE.search(makefile.read()) sources_list = sources_match.group('sources').replace('\\\n', ' ') libsass_sources.update(sources_list.split()) -libsass_sources = list(libsass_sources) +libsass_sources = set( + x for x in libsass_sources if not x.endswith('.hpp') and not x.endswith('.h') +) libsass_headers = [ os.path.join(LIBSASS_DIR, 'sass_interface.h'), diff --git a/tox.ini b/tox.ini index adfeea84..eaf45db0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,4 +5,4 @@ envlist = pypy, py26, py27, py33, py34 deps = Werkzeug >= 0.9 commands = python -c 'from shutil import *; rmtree("build", True)' - python setup.py test + python -m unittest sasstests From 15df8fcceadaf642b29c242ca2b05e1b58cda2c0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 30 Jan 2015 13:31:07 -0800 Subject: [PATCH 2/2] Point at sass/libsass --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index f378a73a..975b971f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libsass"] path = libsass -url = git://github.com/dahlia/libsass-python.git +url = git://github.com/sass/libsass.git pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy