Import Upstream version 0.21.0
This commit is contained in:
commit
7f76b7e4da
|
@ -0,0 +1,3 @@
|
|||
--ignore-dir=.tox
|
||||
--ignore-dir=build
|
||||
--ignore-dir=libsass.egg-info
|
|
@ -0,0 +1,28 @@
|
|||
[run]
|
||||
parallel = True
|
||||
branch = True
|
||||
source = $PWD
|
||||
data_file = $PWD/.coverage
|
||||
omit =
|
||||
*/.tox/*
|
||||
/usr/*
|
||||
*/setup.py
|
||||
|
||||
[report]
|
||||
show_missing = True
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma
|
||||
\#\s*pragma: no cover
|
||||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
^\s*raise AssertionError\b
|
||||
^\s*raise NotImplementedError\b
|
||||
^\s*return NotImplemented\b
|
||||
^\s*raise TypeError\b
|
||||
^\s*raise$
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
^if __name__ == ['"]__main__['"]:$
|
||||
|
||||
[html]
|
||||
directory = coverage-html
|
|
@ -0,0 +1,18 @@
|
|||
*.egg
|
||||
*.egg-info
|
||||
*.pyc
|
||||
*.pyd
|
||||
*.so
|
||||
.*.swp
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
.tox
|
||||
/.libsass-upstream-version
|
||||
build/
|
||||
dist/
|
||||
docs/_build
|
||||
testpkg/build/
|
||||
testpkg/dist/
|
||||
testpkg/testpkg/static/css/*.scss.css
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "libsass"]
|
||||
path = libsass
|
||||
url = git://github.com/sass/libsass.git
|
|
@ -0,0 +1,21 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
exclude: ^docs/conf.py
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.16.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
|
@ -0,0 +1,10 @@
|
|||
Sass is more than a technology; Sass is driven by the community of individuals
|
||||
that power its development and use every day. As a community, we want to embrace
|
||||
the very differences that have made our collaboration so powerful, and work
|
||||
together to provide the best environment for learning, growing, and sharing of
|
||||
ideas. It is imperative that we keep Sass a fun, welcoming, challenging, and
|
||||
fair place to play.
|
||||
|
||||
[The full community guidelines can be found on the Sass website.][link]
|
||||
|
||||
[link]: https://sass-lang.com/community-guidelines
|
|
@ -0,0 +1,69 @@
|
|||
Contributor's guide
|
||||
===================
|
||||
|
||||
Coding style
|
||||
------------
|
||||
|
||||
- Follow `PEP 8`_. flake8_ would help.
|
||||
- Order imports by lexicographical order.
|
||||
- Prefer relative imports.
|
||||
- All functions, classes, methods, attributes, and modules should have
|
||||
the docstring.
|
||||
- Functions and methods should contain ``:param:``, ``:type:``
|
||||
(``:return:``, ``:rtype`` if it returns something),
|
||||
(``:raise:`` if it may raise an error) in their docstring.
|
||||
|
||||
.. _flake8: https://gitlab.com/pycqa/flake8
|
||||
.. _PEP 8: https://www.python.org/dev/peps/pep-0008
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
- All code patches should contain one or more unit tests or regression tests.
|
||||
- All code patches have to successfully run tests on every Python version
|
||||
we aim to support. tox_ would help.
|
||||
- All commits will be tested by `Azure Pipelines`_ (Linux and Windows).
|
||||
|
||||
.. _tox: https://tox.readthedocs.io/
|
||||
.. _`Azure Pipelines`: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
|
||||
|
||||
Maintainer's guide
|
||||
==================
|
||||
|
||||
Releasing
|
||||
---------
|
||||
|
||||
Here's a brief check list for releasing a new version:
|
||||
|
||||
- Double check if the version is correctly bumped.
|
||||
You can bump the version by changing ``__version__`` in sass.py file.
|
||||
Note that it might be already bumped by other maintainers,
|
||||
so check what's the latest release version from PyPI_.
|
||||
- The changelog has to be complete, and frozen.
|
||||
"To be released" sentence has to be replaced by the actual release date.
|
||||
- If the code freeze for the release is done (including version bump),
|
||||
tag the commit using ``git tag`` command. The tag name has to simply be
|
||||
the version name e.g. ``1.2.3``. Of course, the tag also has to be pushed
|
||||
to the upstream repository.
|
||||
- Make a source distribution and upload it to PyPI
|
||||
(``python3 setup.py sdist upload``).
|
||||
If it's successful the new version must appear on PyPI_.
|
||||
- `Azure Pipelines`_ automatically makes binary wheels for Windows, but each
|
||||
CI build takes a while. These wheels are not automatically uploaded,
|
||||
but there's ``./bin/download-windows-wheels`` script that downloads built
|
||||
wheels. Then upload them with ``twine``.
|
||||
- Run ``./bin/build-manylinux-wheels`` to build linux wheels and upload them to
|
||||
PyPI (takes ~5 minutes).
|
||||
- The `docs website`__ also has to be updated.
|
||||
It's currently a static website deployed on GitHub Pages.
|
||||
Use ``python setup.py upload_doc`` command.
|
||||
Although it seems possible to be automated using Github Actions.
|
||||
- Manually create a release through https://github.com/sass/libsass-python/releases/
|
||||
|
||||
Ping Hong Minhee (hongminhee@member.fsf.org, @dahlia on GitHub) if you need
|
||||
any help!
|
||||
|
||||
.. _PyPI: https://pypi.org/pypi/libsass/
|
||||
__ https://sass.github.io/libsass-python/
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2015 Hong Minhee <https://hongminhee.org/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,9 @@
|
|||
recursive-include libsass *.c
|
||||
recursive-include libsass *.cpp
|
||||
recursive-include libsass *.h
|
||||
recursive-include libsass *.hpp
|
||||
include libsass/Makefile
|
||||
include .libsass-upstream-version
|
||||
include test/*.scss
|
||||
include README.rst
|
||||
include LICENSE
|
|
@ -0,0 +1,117 @@
|
|||
libsass-python: Sass_/SCSS for Python
|
||||
=====================================
|
||||
|
||||
.. image:: https://badge.fury.io/py/libsass.svg
|
||||
:alt: PyPI
|
||||
:target: https://pypi.org/pypi/libsass/
|
||||
|
||||
.. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=master
|
||||
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/master.svg
|
||||
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
:alt: Coverage Status
|
||||
|
||||
.. image:: https://results.pre-commit.ci/badge/github/sass/libsass-python/master.svg
|
||||
:target: https://results.pre-commit.ci/latest/github/sass/libsass-python/master
|
||||
:alt: pre-commit.ci status
|
||||
|
||||
This package provides a simple Python extension module ``sass`` which is
|
||||
binding LibSass_ (written in C/C++ by Hampton Catlin and Aaron Leung).
|
||||
It's very straightforward and there isn't any headache related Python
|
||||
distribution/deployment. That means you can add just ``libsass`` into
|
||||
your ``setup.py``'s ``install_requires`` list or ``requirements.txt`` file.
|
||||
Need no Ruby nor Node.js.
|
||||
|
||||
It currently supports CPython 2.7, 3.6--3.8, and PyPy 2.3+!
|
||||
|
||||
.. _Sass: https://sass-lang.com/
|
||||
.. _LibSass: https://github.com/sass/libsass
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- You don't need any Ruby/Node.js stack at all, for development or deployment
|
||||
either.
|
||||
- Fast. (LibSass_ is written in C++.)
|
||||
- Simple API. See the below example code for details.
|
||||
- Custom functions.
|
||||
- ``@import`` callbacks.
|
||||
- Support both tabbed (Sass) and braces (SCSS) syntax.
|
||||
- WSGI middleware for ease of development.
|
||||
It automatically compiles Sass/SCSS files for each request.
|
||||
- ``setuptools``/``distutils`` integration.
|
||||
You can build all Sass/SCSS files using
|
||||
``setup.py build_sass`` command.
|
||||
- Works also on PyPy.
|
||||
- Provides prebuilt wheel_ binaries for Linux, Windows, and Mac.
|
||||
|
||||
.. _wheel: https://www.python.org/dev/peps/pep-0427/
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
It's available on PyPI_, so you can install it using ``pip`` (or
|
||||
``easy_install``):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install libsass
|
||||
|
||||
.. note::
|
||||
|
||||
libsass requires some features introduced by the recent C++ standard.
|
||||
You need a C++ compiler that support those features.
|
||||
See also libsass project's README_ file.
|
||||
|
||||
.. _PyPI: https://pypi.org/pypi/libsass/
|
||||
.. _README: https://github.com/sass/libsass#readme
|
||||
|
||||
|
||||
.. _example:
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> import sass
|
||||
>>> print sass.compile(string='a { b { color: blue; } }')
|
||||
a b {
|
||||
color: blue; }
|
||||
|
||||
|
||||
Docs
|
||||
----
|
||||
|
||||
There's the user guide manual and the full API reference for ``libsass``:
|
||||
|
||||
https://sass.github.io/libsass-python/
|
||||
|
||||
You can build the docs by yourself:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cd docs/
|
||||
$ make html
|
||||
|
||||
The built docs will go to ``docs/_build/html/`` directory.
|
||||
|
||||
|
||||
Credit
|
||||
------
|
||||
|
||||
Hong Minhee wrote this Python binding of LibSass_.
|
||||
|
||||
Hampton Catlin and Aaron Leung wrote LibSass_, which is portable C/C++
|
||||
implementation of Sass_.
|
||||
|
||||
Hampton Catlin originally designed Sass_ language and wrote the first
|
||||
reference implementation of it in Ruby.
|
||||
|
||||
The above three are all distributed under `MIT license`_.
|
||||
|
||||
.. _MIT license: https://mit-license.org/
|
|
@ -0,0 +1,701 @@
|
|||
#include <Python.h>
|
||||
#include <sass/context.h>
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define PySass_IF_PY3(three, two) (three)
|
||||
#define PySass_Object_Bytes(o) PyUnicode_AsUTF8String(PyObject_Str(o))
|
||||
#define COLLECTIONS_ABC_MOD "collections.abc"
|
||||
#else
|
||||
#define PySass_IF_PY3(three, two) (two)
|
||||
#define PySass_Object_Bytes(o) PyObject_Str(o)
|
||||
#define COLLECTIONS_ABC_MOD "collections"
|
||||
#endif
|
||||
|
||||
static PyObject* _to_py_value(const union Sass_Value* value);
|
||||
static union Sass_Value* _to_sass_value(PyObject* value);
|
||||
|
||||
static union Sass_Value* _color_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _number_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _list_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _mapping_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _unicode_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _warning_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _error_to_sass_value(PyObject* value);
|
||||
static union Sass_Value* _unknown_type_to_sass_error(PyObject* value);
|
||||
static union Sass_Value* _exception_to_sass_error();
|
||||
|
||||
|
||||
static PyObject* _to_py_value(const union Sass_Value* value) {
|
||||
PyObject* retv = NULL;
|
||||
PyObject* types_mod = PyImport_ImportModule("sass");
|
||||
PyObject* sass_comma = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_COMMA");
|
||||
PyObject* sass_space = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_SPACE");
|
||||
|
||||
switch (sass_value_get_tag(value)) {
|
||||
case SASS_NULL:
|
||||
retv = Py_None;
|
||||
Py_INCREF(retv);
|
||||
break;
|
||||
case SASS_BOOLEAN:
|
||||
retv = PyBool_FromLong(sass_boolean_get_value(value));
|
||||
break;
|
||||
case SASS_STRING:
|
||||
retv = PyUnicode_FromString(sass_string_get_value(value));
|
||||
break;
|
||||
case SASS_NUMBER:
|
||||
retv = PyObject_CallMethod(
|
||||
types_mod,
|
||||
"SassNumber",
|
||||
PySass_IF_PY3("dy", "ds"),
|
||||
sass_number_get_value(value),
|
||||
sass_number_get_unit(value)
|
||||
);
|
||||
break;
|
||||
case SASS_COLOR:
|
||||
retv = PyObject_CallMethod(
|
||||
types_mod,
|
||||
"SassColor",
|
||||
"dddd",
|
||||
sass_color_get_r(value),
|
||||
sass_color_get_g(value),
|
||||
sass_color_get_b(value),
|
||||
sass_color_get_a(value)
|
||||
);
|
||||
break;
|
||||
case SASS_LIST: {
|
||||
size_t i = 0;
|
||||
PyObject* items = PyTuple_New(sass_list_get_length(value));
|
||||
PyObject* separator = sass_comma;
|
||||
int is_bracketed = sass_list_get_is_bracketed(value);
|
||||
PyObject* bracketed = PyBool_FromLong(is_bracketed);
|
||||
switch (sass_list_get_separator(value)) {
|
||||
case SASS_COMMA:
|
||||
separator = sass_comma;
|
||||
break;
|
||||
case SASS_SPACE:
|
||||
separator = sass_space;
|
||||
break;
|
||||
case SASS_HASH:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < sass_list_get_length(value); i += 1) {
|
||||
PyTuple_SetItem(
|
||||
items,
|
||||
i,
|
||||
_to_py_value(sass_list_get_value(value, i))
|
||||
);
|
||||
}
|
||||
retv = PyObject_CallMethod(
|
||||
types_mod, "SassList", "OOO", items, separator, bracketed
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SASS_MAP: {
|
||||
size_t i = 0;
|
||||
PyObject* items = PyTuple_New(sass_map_get_length(value));
|
||||
for (i = 0; i < sass_map_get_length(value); i += 1) {
|
||||
PyObject* kvp = PyTuple_New(2);
|
||||
PyTuple_SetItem(
|
||||
kvp, 0, _to_py_value(sass_map_get_key(value, i))
|
||||
);
|
||||
PyTuple_SetItem(
|
||||
kvp, 1, _to_py_value(sass_map_get_value(value, i))
|
||||
);
|
||||
PyTuple_SetItem(items, i, kvp);
|
||||
}
|
||||
retv = PyObject_CallMethod(types_mod, "SassMap", "(O)", items);
|
||||
Py_DECREF(items);
|
||||
break;
|
||||
}
|
||||
case SASS_ERROR:
|
||||
case SASS_WARNING:
|
||||
/* @warning and @error cannot be passed */
|
||||
break;
|
||||
}
|
||||
|
||||
if (retv == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError, "Unexpected sass type");
|
||||
}
|
||||
|
||||
Py_DECREF(types_mod);
|
||||
Py_DECREF(sass_comma);
|
||||
Py_DECREF(sass_space);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _color_to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* r_value = PyObject_GetAttrString(value, "r");
|
||||
PyObject* g_value = PyObject_GetAttrString(value, "g");
|
||||
PyObject* b_value = PyObject_GetAttrString(value, "b");
|
||||
PyObject* a_value = PyObject_GetAttrString(value, "a");
|
||||
retv = sass_make_color(
|
||||
PyFloat_AsDouble(r_value),
|
||||
PyFloat_AsDouble(g_value),
|
||||
PyFloat_AsDouble(b_value),
|
||||
PyFloat_AsDouble(a_value)
|
||||
);
|
||||
Py_DECREF(r_value);
|
||||
Py_DECREF(g_value);
|
||||
Py_DECREF(b_value);
|
||||
Py_DECREF(a_value);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _list_to_sass_value(PyObject* value) {
|
||||
PyObject* types_mod = PyImport_ImportModule("sass");
|
||||
PyObject* sass_comma = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_COMMA");
|
||||
PyObject* sass_space = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_SPACE");
|
||||
union Sass_Value* retv = NULL;
|
||||
Py_ssize_t i = 0;
|
||||
PyObject* items = PyObject_GetAttrString(value, "items");
|
||||
PyObject* separator = PyObject_GetAttrString(value, "separator");
|
||||
PyObject* bracketed = PyObject_GetAttrString(value, "bracketed");
|
||||
enum Sass_Separator sep = SASS_COMMA;
|
||||
if (separator == sass_comma) {
|
||||
sep = SASS_COMMA;
|
||||
} else if (separator == sass_space) {
|
||||
sep = SASS_SPACE;
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
int is_bracketed = bracketed == Py_True;
|
||||
retv = sass_make_list(PyTuple_Size(items), sep, is_bracketed);
|
||||
for (i = 0; i < PyTuple_Size(items); i += 1) {
|
||||
sass_list_set_value(
|
||||
retv, i, _to_sass_value(PyTuple_GetItem(items, i))
|
||||
);
|
||||
}
|
||||
Py_DECREF(types_mod);
|
||||
Py_DECREF(sass_comma);
|
||||
Py_DECREF(sass_space);
|
||||
Py_DECREF(items);
|
||||
Py_DECREF(separator);
|
||||
Py_DECREF(bracketed);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _mapping_to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
size_t i = 0;
|
||||
Py_ssize_t pos = 0;
|
||||
PyObject* d_key = NULL;
|
||||
PyObject* d_value = NULL;
|
||||
PyObject* dct = PyDict_New();
|
||||
PyDict_Update(dct, value);
|
||||
retv = sass_make_map(PyDict_Size(dct));
|
||||
while (PyDict_Next(dct, &pos, &d_key, &d_value)) {
|
||||
sass_map_set_key(retv, i, _to_sass_value(d_key));
|
||||
sass_map_set_value(retv, i, _to_sass_value(d_value));
|
||||
i += 1;
|
||||
}
|
||||
Py_DECREF(dct);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _number_to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* d_value = PyObject_GetAttrString(value, "value");
|
||||
PyObject* unit = PyObject_GetAttrString(value, "unit");
|
||||
PyObject* bytes = PyUnicode_AsEncodedString(unit, "UTF-8", "strict");
|
||||
retv = sass_make_number(
|
||||
PyFloat_AsDouble(d_value), PyBytes_AsString(bytes)
|
||||
);
|
||||
Py_DECREF(d_value);
|
||||
Py_DECREF(unit);
|
||||
Py_DECREF(bytes);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _unicode_to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* bytes = PyUnicode_AsEncodedString(value, "UTF-8", "strict");
|
||||
retv = sass_make_string(PyBytes_AsString(bytes));
|
||||
Py_DECREF(bytes);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _warning_to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* msg = PyObject_GetAttrString(value, "msg");
|
||||
PyObject* bytes = PyUnicode_AsEncodedString(msg, "UTF-8", "strict");
|
||||
retv = sass_make_warning(PyBytes_AsString(bytes));
|
||||
Py_DECREF(msg);
|
||||
Py_DECREF(bytes);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _error_to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* msg = PyObject_GetAttrString(value, "msg");
|
||||
PyObject* bytes = PyUnicode_AsEncodedString(msg, "UTF-8", "strict");
|
||||
retv = sass_make_error(PyBytes_AsString(bytes));
|
||||
Py_DECREF(msg);
|
||||
Py_DECREF(bytes);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _unknown_type_to_sass_error(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* type = PyObject_Type(value);
|
||||
PyObject* type_name = PyObject_GetAttrString(type, "__name__");
|
||||
PyObject* fmt = PyUnicode_FromString(
|
||||
"Unexpected type: `{0}`.\n"
|
||||
"Expected one of:\n"
|
||||
"- None\n"
|
||||
"- bool\n"
|
||||
"- str\n"
|
||||
"- SassNumber\n"
|
||||
"- SassColor\n"
|
||||
"- SassList\n"
|
||||
"- dict\n"
|
||||
"- SassMap\n"
|
||||
"- SassWarning\n"
|
||||
"- SassError\n"
|
||||
);
|
||||
PyObject* format_meth = PyObject_GetAttrString(fmt, "format");
|
||||
PyObject* result = PyObject_CallFunctionObjArgs(
|
||||
format_meth, type_name, NULL
|
||||
);
|
||||
PyObject* bytes = PyUnicode_AsEncodedString(result, "UTF-8", "strict");
|
||||
retv = sass_make_error(PyBytes_AsString(bytes));
|
||||
Py_DECREF(type);
|
||||
Py_DECREF(type_name);
|
||||
Py_DECREF(fmt);
|
||||
Py_DECREF(format_meth);
|
||||
Py_DECREF(result);
|
||||
Py_DECREF(bytes);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static PyObject* _exception_to_bytes() {
|
||||
PyObject* retv = NULL;
|
||||
PyObject* etype = NULL;
|
||||
PyObject* evalue = NULL;
|
||||
PyObject* etb = NULL;
|
||||
PyErr_Fetch(&etype, &evalue, &etb);
|
||||
PyErr_NormalizeException(&etype, &evalue, &etb);
|
||||
{
|
||||
PyObject* traceback_mod = PyImport_ImportModule("traceback");
|
||||
PyObject* traceback_parts = PyObject_CallMethod(
|
||||
traceback_mod, "format_exception", "OOO", etype, evalue, etb
|
||||
);
|
||||
PyList_Insert(traceback_parts, 0, PyUnicode_FromString("\n"));
|
||||
PyObject* joinstr = PyUnicode_FromString("");
|
||||
PyObject* result = PyUnicode_Join(joinstr, traceback_parts);
|
||||
retv = PyUnicode_AsEncodedString(result, "UTF-8", "strict");
|
||||
Py_DECREF(traceback_mod);
|
||||
Py_DECREF(traceback_parts);
|
||||
Py_DECREF(joinstr);
|
||||
Py_DECREF(result);
|
||||
}
|
||||
Py_DECREF(etype);
|
||||
Py_DECREF(evalue);
|
||||
Py_DECREF(etb);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _exception_to_sass_error() {
|
||||
PyObject* bytes = _exception_to_bytes();
|
||||
union Sass_Value* retv = sass_make_error(PyBytes_AsString(bytes));
|
||||
Py_DECREF(bytes);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static Sass_Import_List _exception_to_sass_import_error(const char* path) {
|
||||
PyObject* bytes = _exception_to_bytes();
|
||||
Sass_Import_List import_list = sass_make_import_list(1);
|
||||
import_list[0] = sass_make_import_entry(path, 0, 0);
|
||||
sass_import_set_error(import_list[0], PyBytes_AsString(bytes), 0, 0);
|
||||
Py_DECREF(bytes);
|
||||
return import_list;
|
||||
}
|
||||
|
||||
static union Sass_Value* _to_sass_value(PyObject* value) {
|
||||
union Sass_Value* retv = NULL;
|
||||
PyObject* types_mod = PyImport_ImportModule("sass");
|
||||
PyObject* sass_number_t = PyObject_GetAttrString(types_mod, "SassNumber");
|
||||
PyObject* sass_color_t = PyObject_GetAttrString(types_mod, "SassColor");
|
||||
PyObject* sass_list_t = PyObject_GetAttrString(types_mod, "SassList");
|
||||
PyObject* sass_warning_t = PyObject_GetAttrString(types_mod, "SassWarning");
|
||||
PyObject* sass_error_t = PyObject_GetAttrString(types_mod, "SassError");
|
||||
PyObject* collections_mod = PyImport_ImportModule(COLLECTIONS_ABC_MOD);
|
||||
PyObject* mapping_t = PyObject_GetAttrString(collections_mod, "Mapping");
|
||||
|
||||
if (value == Py_None) {
|
||||
retv = sass_make_null();
|
||||
} else if (PyBool_Check(value)) {
|
||||
retv = sass_make_boolean(value == Py_True);
|
||||
} else if (PyUnicode_Check(value)) {
|
||||
retv = _unicode_to_sass_value(value);
|
||||
} else if (PyBytes_Check(value)) {
|
||||
retv = sass_make_string(PyBytes_AsString(value));
|
||||
/* XXX: PyMapping_Check returns true for lists and tuples in python3 :( */
|
||||
/* XXX: pypy derps on dicts: https://bitbucket.org/pypy/pypy/issue/1970 */
|
||||
} else if (PyDict_Check(value) || PyObject_IsInstance(value, mapping_t)) {
|
||||
retv = _mapping_to_sass_value(value);
|
||||
} else if (PyObject_IsInstance(value, sass_number_t)) {
|
||||
retv = _number_to_sass_value(value);
|
||||
} else if (PyObject_IsInstance(value, sass_color_t)) {
|
||||
retv = _color_to_sass_value(value);
|
||||
} else if (PyObject_IsInstance(value, sass_list_t)) {
|
||||
retv = _list_to_sass_value(value);
|
||||
} else if (PyObject_IsInstance(value, sass_warning_t)) {
|
||||
retv = _warning_to_sass_value(value);
|
||||
} else if (PyObject_IsInstance(value, sass_error_t)) {
|
||||
retv = _error_to_sass_value(value);
|
||||
}
|
||||
|
||||
if (retv == NULL) {
|
||||
retv = _unknown_type_to_sass_error(value);
|
||||
}
|
||||
|
||||
Py_DECREF(types_mod);
|
||||
Py_DECREF(sass_number_t);
|
||||
Py_DECREF(sass_color_t);
|
||||
Py_DECREF(sass_list_t);
|
||||
Py_DECREF(sass_warning_t);
|
||||
Py_DECREF(sass_error_t);
|
||||
Py_DECREF(collections_mod);
|
||||
Py_DECREF(mapping_t);
|
||||
return retv;
|
||||
}
|
||||
|
||||
static union Sass_Value* _call_py_f(
|
||||
const union Sass_Value* sass_args,
|
||||
Sass_Function_Entry cb,
|
||||
struct Sass_Compiler* compiler
|
||||
) {
|
||||
size_t i;
|
||||
PyObject* pyfunc = (PyObject*)sass_function_get_cookie(cb);
|
||||
PyObject* py_args = PyTuple_New(sass_list_get_length(sass_args));
|
||||
PyObject* py_result = NULL;
|
||||
union Sass_Value* sass_result = NULL;
|
||||
|
||||
for (i = 0; i < sass_list_get_length(sass_args); i += 1) {
|
||||
const union Sass_Value* sass_arg = sass_list_get_value(sass_args, i);
|
||||
PyObject* py_arg = NULL;
|
||||
if (!(py_arg = _to_py_value(sass_arg))) goto done;
|
||||
PyTuple_SetItem(py_args, i, py_arg);
|
||||
}
|
||||
|
||||
if (!(py_result = PyObject_CallObject(pyfunc, py_args))) goto done;
|
||||
sass_result = _to_sass_value(py_result);
|
||||
|
||||
done:
|
||||
if (sass_result == NULL) {
|
||||
sass_result = _exception_to_sass_error();
|
||||
}
|
||||
Py_XDECREF(py_args);
|
||||
Py_XDECREF(py_result);
|
||||
return sass_result;
|
||||
}
|
||||
|
||||
|
||||
static void _add_custom_functions(
|
||||
struct Sass_Options* options, PyObject* custom_functions
|
||||
) {
|
||||
Py_ssize_t i;
|
||||
Sass_Function_List fn_list = sass_make_function_list(
|
||||
PyList_Size(custom_functions)
|
||||
);
|
||||
for (i = 0; i < PyList_Size(custom_functions); i += 1) {
|
||||
PyObject* sass_function = PyList_GetItem(custom_functions, i);
|
||||
PyObject* signature = PySass_Object_Bytes(sass_function);
|
||||
Sass_Function_Entry fn = sass_make_function(
|
||||
PyBytes_AsString(signature),
|
||||
_call_py_f,
|
||||
sass_function
|
||||
);
|
||||
sass_function_set_list_entry(fn_list, i, fn);
|
||||
}
|
||||
sass_option_set_c_functions(options, fn_list);
|
||||
}
|
||||
|
||||
static Sass_Import_List _call_py_importer_f(
|
||||
const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp
|
||||
) {
|
||||
PyObject* pyfunc = (PyObject*)sass_importer_get_cookie(cb);
|
||||
PyObject* py_result = NULL;
|
||||
Sass_Import_List sass_imports = NULL;
|
||||
struct Sass_Import* previous;
|
||||
const char* prev_path;
|
||||
Py_ssize_t i;
|
||||
|
||||
previous = sass_compiler_get_last_import(comp);
|
||||
prev_path = sass_import_get_abs_path(previous);
|
||||
|
||||
py_result = PyObject_CallFunction(pyfunc, PySass_IF_PY3("yy", "ss"), path, prev_path);
|
||||
|
||||
/* Handle importer throwing an exception */
|
||||
if (!py_result) goto done;
|
||||
|
||||
/* Could return None indicating it could not handle the import */
|
||||
if (py_result == Py_None) {
|
||||
Py_XDECREF(py_result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Otherwise, we know our importer is well formed (because we wrap it)
|
||||
* The return value will be a tuple of 1, 2, or 3 tuples */
|
||||
sass_imports = sass_make_import_list(PyTuple_Size(py_result));
|
||||
for (i = 0; i < PyTuple_Size(py_result); i += 1) {
|
||||
char* path_str = NULL; /* XXX: Memory leak? */
|
||||
char* source_str = NULL;
|
||||
char* sourcemap_str = NULL;
|
||||
PyObject* tup = PyTuple_GetItem(py_result, i);
|
||||
Py_ssize_t size = PyTuple_Size(tup);
|
||||
|
||||
if (size == 1) {
|
||||
PyArg_ParseTuple(tup, PySass_IF_PY3("y", "s"), &path_str);
|
||||
} else if (size == 2) {
|
||||
PyArg_ParseTuple(
|
||||
tup, PySass_IF_PY3("yy", "ss"), &path_str, &source_str
|
||||
);
|
||||
} else if (size == 3) {
|
||||
PyArg_ParseTuple(
|
||||
tup, PySass_IF_PY3("yyy", "sss"),
|
||||
&path_str, &source_str, &sourcemap_str
|
||||
);
|
||||
}
|
||||
|
||||
/* We need to give copies of these arguments; libsass handles
|
||||
* deallocation of them later, whereas path_str is left flapping
|
||||
* in the breeze -- it's treated const, so that's okay. */
|
||||
if (source_str) source_str = sass_copy_c_string(source_str);
|
||||
if (sourcemap_str) sourcemap_str = sass_copy_c_string(sourcemap_str);
|
||||
|
||||
sass_imports[i] = sass_make_import_entry(
|
||||
path_str, source_str, sourcemap_str
|
||||
);
|
||||
}
|
||||
|
||||
done:
|
||||
if (sass_imports == NULL) {
|
||||
sass_imports = _exception_to_sass_import_error(path);
|
||||
}
|
||||
|
||||
Py_XDECREF(py_result);
|
||||
|
||||
return sass_imports;
|
||||
}
|
||||
|
||||
static void _add_custom_importers(
|
||||
struct Sass_Options* options, PyObject* custom_importers
|
||||
) {
|
||||
Py_ssize_t i;
|
||||
Sass_Importer_List importer_list;
|
||||
|
||||
if (custom_importers == Py_None) {
|
||||
return;
|
||||
}
|
||||
|
||||
importer_list = sass_make_importer_list(PyTuple_Size(custom_importers));
|
||||
|
||||
for (i = 0; i < PyTuple_Size(custom_importers); i += 1) {
|
||||
PyObject* item = PyTuple_GetItem(custom_importers, i);
|
||||
int priority = 0;
|
||||
PyObject* import_function = NULL;
|
||||
|
||||
PyArg_ParseTuple(item, "iO", &priority, &import_function);
|
||||
|
||||
importer_list[i] = sass_make_importer(
|
||||
_call_py_importer_f, priority, import_function
|
||||
);
|
||||
}
|
||||
|
||||
sass_option_set_c_importers(options, importer_list);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PySass_compile_string(PyObject *self, PyObject *args) {
|
||||
struct Sass_Context *ctx;
|
||||
struct Sass_Data_Context *context;
|
||||
struct Sass_Options *options;
|
||||
char *string, *include_paths;
|
||||
const char *error_message, *output_string;
|
||||
enum Sass_Output_Style output_style;
|
||||
int source_comments, error_status, precision, indented,
|
||||
source_map_embed, source_map_contents,
|
||||
omit_source_map_url;
|
||||
PyObject *custom_functions;
|
||||
PyObject *custom_importers;
|
||||
PyObject *source_map_root;
|
||||
PyObject *result;
|
||||
|
||||
if (!PyArg_ParseTuple(args,
|
||||
PySass_IF_PY3("yiiyiOiOiiiO", "siisiOiOiiiO"),
|
||||
&string, &output_style, &source_comments,
|
||||
&include_paths, &precision,
|
||||
&custom_functions, &indented, &custom_importers,
|
||||
&source_map_contents, &source_map_embed,
|
||||
&omit_source_map_url, &source_map_root)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context = sass_make_data_context(sass_copy_c_string(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_precision(options, precision);
|
||||
sass_option_set_is_indented_syntax_src(options, indented);
|
||||
sass_option_set_source_map_contents(options, source_map_contents);
|
||||
sass_option_set_source_map_embed(options, source_map_embed);
|
||||
sass_option_set_omit_source_map_url(options, omit_source_map_url);
|
||||
|
||||
if (PyBytes_Check(source_map_root) && PyBytes_Size(source_map_root)) {
|
||||
sass_option_set_source_map_root(
|
||||
options, PyBytes_AsString(source_map_root)
|
||||
);
|
||||
}
|
||||
|
||||
_add_custom_functions(options, custom_functions);
|
||||
_add_custom_importers(options, custom_importers);
|
||||
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) !error_status,
|
||||
error_status ? error_message : output_string
|
||||
);
|
||||
sass_delete_data_context(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PySass_compile_filename(PyObject *self, PyObject *args) {
|
||||
struct Sass_Context *ctx;
|
||||
struct Sass_File_Context *context;
|
||||
struct Sass_Options *options;
|
||||
char *filename, *include_paths;
|
||||
const char *error_message, *output_string, *source_map_string;
|
||||
enum Sass_Output_Style output_style;
|
||||
int source_comments, error_status, precision, source_map_embed,
|
||||
source_map_contents, omit_source_map_url;
|
||||
PyObject *source_map_filename, *custom_functions, *custom_importers,
|
||||
*result, *output_filename_hint, *source_map_root;
|
||||
|
||||
if (!PyArg_ParseTuple(args,
|
||||
PySass_IF_PY3("yiiyiOOOOiiiO", "siisiOOOOiiiO"),
|
||||
&filename, &output_style, &source_comments,
|
||||
&include_paths, &precision,
|
||||
&source_map_filename, &custom_functions,
|
||||
&custom_importers, &output_filename_hint,
|
||||
&source_map_contents, &source_map_embed,
|
||||
&omit_source_map_url, &source_map_root)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context = sass_make_file_context(filename);
|
||||
options = sass_file_context_get_options(context);
|
||||
|
||||
if (PyBytes_Check(source_map_filename)) {
|
||||
if (PyBytes_Size(source_map_filename)) {
|
||||
sass_option_set_source_map_file(
|
||||
options, PyBytes_AsString(source_map_filename)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (PyBytes_Check(output_filename_hint)) {
|
||||
if (PyBytes_Size(output_filename_hint)) {
|
||||
sass_option_set_output_path(
|
||||
options, PyBytes_AsString(output_filename_hint)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (PyBytes_Check(source_map_root) && PyBytes_Size(source_map_root)) {
|
||||
sass_option_set_source_map_root(
|
||||
options, PyBytes_AsString(source_map_root)
|
||||
);
|
||||
}
|
||||
|
||||
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_precision(options, precision);
|
||||
sass_option_set_source_map_contents(options, source_map_contents);
|
||||
sass_option_set_source_map_embed(options, source_map_embed);
|
||||
sass_option_set_omit_source_map_url(options, omit_source_map_url);
|
||||
_add_custom_functions(options, custom_functions);
|
||||
_add_custom_importers(options, custom_importers);
|
||||
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) !error_status,
|
||||
error_status ? error_message : output_string,
|
||||
error_status || source_map_string == NULL ? "" : source_map_string
|
||||
);
|
||||
sass_delete_file_context(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyMethodDef PySass_methods[] = {
|
||||
{"compile_string", PySass_compile_string, METH_VARARGS,
|
||||
"Compile a Sass string."},
|
||||
{"compile_filename", PySass_compile_filename, METH_VARARGS,
|
||||
"Compile a Sass file."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static char PySass_doc[] = "The thin binding of libsass for Python.";
|
||||
|
||||
PyObject* PySass_make_enum_dict() {
|
||||
PyObject* dct = PyDict_New();
|
||||
PyDict_SetItemString(dct, "nested", PyLong_FromLong(SASS_STYLE_NESTED));
|
||||
PyDict_SetItemString(dct, "expanded", PyLong_FromLong(SASS_STYLE_EXPANDED));
|
||||
PyDict_SetItemString(dct, "compact", PyLong_FromLong(SASS_STYLE_COMPACT));
|
||||
PyDict_SetItemString(dct, "compressed", PyLong_FromLong(SASS_STYLE_COMPRESSED));
|
||||
return dct;
|
||||
}
|
||||
|
||||
void PySass_init_module(PyObject *module) {
|
||||
PyModule_AddObject(module, "OUTPUT_STYLES", PySass_make_enum_dict());
|
||||
PyModule_AddObject(module, "libsass_version", PyUnicode_FromString(libsass_version()));
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
static struct PyModuleDef sassmodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_sass",
|
||||
PySass_doc,
|
||||
-1,
|
||||
PySass_methods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__sass()
|
||||
{
|
||||
PyObject *module = PyModule_Create(&sassmodule);
|
||||
if (module != NULL) {
|
||||
PySass_init_module(module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
PyMODINIT_FUNC
|
||||
init_sass()
|
||||
{
|
||||
PyObject *module;
|
||||
module = Py_InitModule3("_sass", PySass_methods, PySass_doc);
|
||||
if (module != NULL) {
|
||||
PySass_init_module(module);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,42 @@
|
|||
trigger:
|
||||
branches:
|
||||
include: [master, test-me-*]
|
||||
tags:
|
||||
include: ['*']
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: self
|
||||
checkoutOptions:
|
||||
submodules: true
|
||||
- repository: asottile
|
||||
type: github
|
||||
endpoint: github
|
||||
name: asottile/azure-pipeline-templates
|
||||
ref: refs/tags/v2.1.0
|
||||
|
||||
jobs:
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py27, py36]
|
||||
os: macos
|
||||
wheel_tags: true
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py27]
|
||||
os: windows
|
||||
architectures: [x64, x86]
|
||||
name_postfix: _py27
|
||||
wheel_tags: true
|
||||
pre_test:
|
||||
- script: rm -rf libsass/test
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py36]
|
||||
os: windows
|
||||
architectures: [x64, x86]
|
||||
wheel_tags: true
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [pypy, pypy3, py27, py36, py37, py38, py39]
|
||||
os: linux
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Script for building 'manylinux' wheels for libsass.
|
||||
|
||||
Run me after putting the source distribution on pypi.
|
||||
|
||||
See: https://www.python.org/dev/peps/pep-0513/
|
||||
"""
|
||||
import os
|
||||
import pipes
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def check_call(*cmd):
|
||||
print(
|
||||
'build-manylinux-wheels>> ' +
|
||||
' '.join(pipes.quote(part) for part in cmd),
|
||||
)
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs('dist', exist_ok=True)
|
||||
for python in ('cp27-cp27mu', 'cp36-cp36m'):
|
||||
with tempfile.TemporaryDirectory() as work:
|
||||
pip = '/opt/python/{}/bin/pip'.format(python)
|
||||
check_call(
|
||||
'docker', 'run', '-ti',
|
||||
# Use this so the files are not owned by root
|
||||
'--user', '{}:{}'.format(os.getuid(), os.getgid()),
|
||||
# We'll do building in /work and copy results to /dist
|
||||
'-v', '{}:/work:rw'.format(work),
|
||||
'-v', '{}:/dist:rw'.format(os.path.abspath('dist')),
|
||||
'quay.io/pypa/manylinux1_x86_64:latest',
|
||||
'bash', '-exc',
|
||||
'{} wheel --verbose --wheel-dir /work --no-deps libsass && '
|
||||
'auditwheel repair --wheel-dir /dist /work/*.whl'.format(pip),
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
import os.path
|
||||
import re
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
DEFINITION_RE = re.compile(
|
||||
r'https://dev.azure.com/(?P<org>[^/]+)/(?P<project>[^/]+)'
|
||||
r'/_build/latest\?definitionId=(?P<definition>\d+)',
|
||||
)
|
||||
BUILDS = 'https://dev.azure.com/{org}/{project}/_apis/build/builds?api-version=5.0&definitions={definition}&$top=5' # noqa: E501
|
||||
ARTIFACTS = 'https://dev.azure.com/{org}/{project}/_apis/build/builds/{build}/artifacts?api-version=5.0' # noqa: E501
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('tag')
|
||||
parser.add_argument('--dest', default='dist')
|
||||
args = parser.parse_args()
|
||||
|
||||
with open('README.rst') as f:
|
||||
match = DEFINITION_RE.search(f.read())
|
||||
assert match
|
||||
|
||||
builds_resp = urllib.request.urlopen(BUILDS.format(**match.groupdict()))
|
||||
builds_json = json.load(builds_resp)
|
||||
|
||||
build_id = next(
|
||||
iter([
|
||||
build['id'] for build in builds_json['value']
|
||||
if build['status'] == 'completed'
|
||||
if build['result'] == 'succeeded'
|
||||
if build['sourceBranch'] == f'refs/tags/{args.tag}'
|
||||
]),
|
||||
)
|
||||
|
||||
artifacts_url = ARTIFACTS.format(build=build_id, **match.groupdict())
|
||||
artifacts_resp = urllib.request.urlopen(artifacts_url)
|
||||
artifacts_json = json.load(artifacts_resp)
|
||||
|
||||
os.makedirs(args.dest, exist_ok=True)
|
||||
for artifact in artifacts_json['value']:
|
||||
if artifact['name'].startswith('wheel_'):
|
||||
print(f'artifact: {artifact["name"]}')
|
||||
resp = urllib.request.urlopen(artifact['resource']['downloadUrl'])
|
||||
bio = io.BytesIO(resp.read())
|
||||
with zipfile.ZipFile(bio) as zipf:
|
||||
for info in zipf.infolist():
|
||||
if info.filename.endswith('.whl'):
|
||||
info.filename = os.path.basename(info.filename)
|
||||
zipf.extract(info, path=args.dest)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
|
@ -0,0 +1,153 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/libsass.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/libsass.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/libsass"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/libsass"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,841 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
Version 0.21.1
|
||||
--------------
|
||||
|
||||
Released on May 20, 2021.
|
||||
|
||||
- Fix build on OpenBSD. [:issue:`310` by Denis Fondras].
|
||||
- Produce abi3 wheels on windows. [:issue:`322` by Anthony Sottile]
|
||||
- Make the manpage build reproducible. [:issue:`319` by Chris Lamb]
|
||||
- Follow up the libsass upstream: 3.6.5 --- See the release notes of LibSass
|
||||
3.6.5__. [:issue:`344` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.6.5
|
||||
|
||||
Version 0.20.1
|
||||
--------------
|
||||
|
||||
Released on August 27, 2020.
|
||||
|
||||
- (no changes, re-releasing to test build automation)
|
||||
|
||||
|
||||
Version 0.20.0
|
||||
--------------
|
||||
|
||||
Released on May 1, 2020.
|
||||
|
||||
- Produce abi3 wheels on macos / linux [:issue:`307` by Anthony Sottile]
|
||||
- Follow up the libsass upstream: 3.6.4 --- See the release notes of LibSass
|
||||
3.6.4__. [:issue:`313` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.6.4
|
||||
|
||||
|
||||
Version 0.19.4
|
||||
--------------
|
||||
|
||||
Released on November 3, 2019.
|
||||
|
||||
- Follow up the libsass upstream: 3.6.3 --- See the release notes of LibSass
|
||||
3.6.3__. [:issue:`304` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.6.3
|
||||
|
||||
|
||||
Version 0.19.3
|
||||
--------------
|
||||
|
||||
Released on October 5, 2019.
|
||||
|
||||
- Follow up the libsass upstream: 3.6.2 --- See the release notes of LibSass
|
||||
3.6.2__. [:issue:`302` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.6.2
|
||||
|
||||
|
||||
Version 0.19.2
|
||||
--------------
|
||||
|
||||
Released on June 16, 2019.
|
||||
|
||||
- Follow up the libsass upstream: 3.6.1 --- See the release notes of LibSass
|
||||
3.6.1__. [:issue:`298` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.6.1
|
||||
|
||||
|
||||
Version 0.19.1
|
||||
--------------
|
||||
|
||||
Released on May 18, 2019.
|
||||
|
||||
- Re-release of 0.19.0 with windows python2.7 wheels [:issue:`297` by Anthony
|
||||
Sottile]
|
||||
|
||||
|
||||
Version 0.19.0
|
||||
--------------
|
||||
|
||||
Released on May 18, 2019.
|
||||
|
||||
- Follow up the libsass upstream: 3.6.0 --- See the release notes of LibSass
|
||||
3.6.0__. [:issue:`295` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.6.0
|
||||
|
||||
|
||||
Version 0.18.0
|
||||
--------------
|
||||
|
||||
Release on March 13, 2019
|
||||
|
||||
- Add support for previous import path to importer callbacks [:issue:`287`
|
||||
:issue:`291` by Frankie Dintino]
|
||||
|
||||
Version 0.17.0
|
||||
--------------
|
||||
|
||||
Release on January 03, 2019
|
||||
|
||||
- Add several new cli options [:issue:`279` :issue:`268` by Frankie Dintino]
|
||||
- ``--sourcemap-file``: output file for source map
|
||||
- ``--sourcemap-contents``: embed ``sourcesContent`` in source map
|
||||
- ``--sourcemap-embed``: embed ``sourceMappingURL`` as data uri
|
||||
- ``--omit-sourcemap-url``: omit source map url comment from output
|
||||
- ``--sourcemap-root``: base path, emitted as ``sourceRoot`` in source map
|
||||
- Fix ``.sass`` in ``WsgiMiddleware`` (again) [:issue:`280` by Anthony Sottile]
|
||||
|
||||
Version 0.16.1
|
||||
--------------
|
||||
|
||||
Released on November 25, 2018.
|
||||
|
||||
- Fix compilation on macos mojave [:issue:`276` :issue:`277` by Anthony
|
||||
Sottile]
|
||||
- Fix ``.sass`` in ``WsgiMiddleware`` for ``strip_extension=True``
|
||||
[:issue:`278` by Anthony Sottile]
|
||||
|
||||
|
||||
Version 0.16.0
|
||||
--------------
|
||||
|
||||
Released on November 13, 2018.
|
||||
|
||||
- Use ``-lc++`` link flag when compiling with ``clang`` [:issue:`270` by
|
||||
Christian Thieme :issue:`271` by Anthony Sottile]
|
||||
- Honor ``strip_extension`` in ``SassMiddleware`` [:issue:`274` by Anthony
|
||||
Sottile]
|
||||
- Follow up the libsass upstream: 3.5.5 --- See the release notes of LibSass
|
||||
3.5.5__. [:issue:`275` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.5.5
|
||||
|
||||
|
||||
Version 0.15.1
|
||||
--------------
|
||||
|
||||
Released on September 24, 2018.
|
||||
|
||||
- Fix ``setup.py sdist`` (regressed in 0.15.0) [:issue:`267` by
|
||||
Anthony Sottile]
|
||||
|
||||
|
||||
Version 0.15.0
|
||||
--------------
|
||||
|
||||
Released on September 16, 2018.
|
||||
|
||||
- Fix invalid escape sequences [:issue:`249` by Anthony Sottile]
|
||||
- Add code of conduct [:issue:`251` by Nick Schonning]
|
||||
- Add support for python3.7 and remove testing for python3.4 [:issue:`254`
|
||||
by Anthony Sottile]
|
||||
- Add ``strip_extension`` option for wsgi / distutils builder [:issue:`55`
|
||||
:issue:`258` by Anthony Sottile :issue:`260` by Morten Brekkevold]
|
||||
- Deprecate ``sassc`` (replaced by ``pysassc``). [:issue:`262` by
|
||||
Anthony Sottile]
|
||||
- Import abc classes from ``collections.abc`` to remove ``DeprecationWarning``
|
||||
[:issue:`264` by Gary van der Merwe :issue:`265` by Anthony Sottile]
|
||||
|
||||
|
||||
Version 0.14.5
|
||||
--------------
|
||||
|
||||
Released on April 25, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.5.4 --- See the release notes of LibSass
|
||||
3.5.4__. [:issue:`247` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.5.4
|
||||
|
||||
|
||||
Version 0.14.4
|
||||
--------------
|
||||
|
||||
Released on April 24, 2018.
|
||||
|
||||
- Add ability to specify imports for custom extensions. This provides a
|
||||
way to enable imports of ``.css`` files (which was removed in 3.5.3).
|
||||
Specify ``--import-extensions .css`` to restore the previous behavior.
|
||||
[:issue:`246` by Samuel Colvin]
|
||||
|
||||
|
||||
Version 0.14.3
|
||||
--------------
|
||||
|
||||
Released on April 23, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.5.3 --- See the release notes of LibSass
|
||||
3.5.3__. [:issue:`244` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.5.3
|
||||
|
||||
|
||||
Version 0.14.2
|
||||
--------------
|
||||
|
||||
Released on March 16, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.5.2 --- See the release notes of LibSass
|
||||
3.5.2__. [:issue:`243` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.5.2
|
||||
|
||||
|
||||
Version 0.14.1
|
||||
--------------
|
||||
|
||||
Released on March 12, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.5.1 --- See the release notes of LibSass
|
||||
3.5.1__. [:issue:`242` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.5.1
|
||||
|
||||
|
||||
Version 0.14.0
|
||||
--------------
|
||||
|
||||
Released on March 6, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.5.0 --- See the release notes of LibSass
|
||||
3.5.0__. [:issue:`241` by Anthony Sottile]
|
||||
- ``SassList`` type gained an additional option ``bracketed=False`` to match
|
||||
the upstream changes to the ``sass_list`` type. [:issue:`184` by Anthony
|
||||
Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.5.0
|
||||
|
||||
|
||||
Version 0.13.7
|
||||
--------------
|
||||
|
||||
Released on February 5, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.9 --- See the release notes of LibSass
|
||||
3.4.9__. [:issue:`232` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.9
|
||||
|
||||
|
||||
Version 0.13.6
|
||||
--------------
|
||||
|
||||
Released on January 19, 2018.
|
||||
|
||||
- libsass-python has moved to the sass organization!
|
||||
|
||||
|
||||
Version 0.13.5
|
||||
--------------
|
||||
|
||||
Released on January 11, 2018.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.8 --- See the release notes of LibSass
|
||||
3.4.8__. [:issue:`228` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.8
|
||||
|
||||
|
||||
Version 0.13.4
|
||||
--------------
|
||||
|
||||
Released on November 14, 2017.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.7 --- See the release notes of LibSass
|
||||
3.4.7__. [:issue:`226` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.7
|
||||
|
||||
|
||||
Version 0.13.3
|
||||
--------------
|
||||
|
||||
Released on October 11, 2017.
|
||||
|
||||
- Sort input files for determinism [:issue:`212` by Bernhard M. Wiedemann]
|
||||
- Include LICENSE file in distributions [:issue:`216` by Dougal J. Sutherland]
|
||||
- Add a ``pysassc`` entry to replace ``sassc`` [:issue:`218` by
|
||||
Anthony Sottile]
|
||||
- Enable building with dynamic linking [:issue:`219` by Marcel Plch]
|
||||
- Follow up the libsass upstream: 3.4.6 --- See the release notes of LibSass
|
||||
3.4.6__. [:issue:`221` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.6
|
||||
|
||||
|
||||
Version 0.13.2
|
||||
--------------
|
||||
|
||||
Released on June 14, 2017.
|
||||
|
||||
- Always add cwd to import paths [:issue:`208` by Anthony Sottile]
|
||||
|
||||
|
||||
Version 0.13.1
|
||||
--------------
|
||||
|
||||
Released on June 8, 2017.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.5 --- See the release notes of LibSass
|
||||
3.4.5__. [:issue:`207` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.5
|
||||
|
||||
|
||||
Version 0.13.0
|
||||
--------------
|
||||
|
||||
Released on June 7, 2017.
|
||||
|
||||
- Use ``getfullargspec`` when available in python 3. [:issue:`188` by
|
||||
Thom Wiggers]
|
||||
- Use ``sass_copy_c_string`` instead of ``strdup`` for portability
|
||||
[:issue:`196` by Anthony Sottile]
|
||||
- Use ``-std=gnu++0x`` to fix installation under cygwin [:issue:`195`
|
||||
:issue:`197` by Anthony Sottile]
|
||||
- Correct source map url [:issue:`201` :issue:`202` by Anthony Sottile]
|
||||
- Remove ``--watch`` [:issue:`203` by Anthony Sottile]
|
||||
- Follow up the libsass upstream: 3.4.4 --- See the release notes of LibSass
|
||||
3.4.4__. [:issue:`205` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.4
|
||||
|
||||
|
||||
Version 0.12.3
|
||||
--------------
|
||||
|
||||
Released on January 7, 2017.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.3 --- See the release notes of LibSass
|
||||
3.4.3__. [:issue:`178` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.3
|
||||
|
||||
|
||||
Version 0.12.2
|
||||
--------------
|
||||
|
||||
Released on January 5, 2017.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.2 --- See the release notes of LibSass
|
||||
3.4.2__. [:issue:`176` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.2
|
||||
|
||||
|
||||
Version 0.12.1
|
||||
--------------
|
||||
|
||||
Released on December 20, 2016.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.1 --- See the release notes of LibSass
|
||||
3.4.1__. [:issue:`175` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.1
|
||||
|
||||
|
||||
Version 0.12.0
|
||||
--------------
|
||||
|
||||
Released on December 10, 2016.
|
||||
|
||||
- Follow up the libsass upstream: 3.4.0 --- See the release notes of LibSass
|
||||
3.4.0__. [:issue:`173` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.4.0
|
||||
|
||||
|
||||
Version 0.11.2
|
||||
--------------
|
||||
|
||||
Released on October 24, 2016.
|
||||
|
||||
- Drop support for python2.6 [:issue:`158` by Anthony Sottile]
|
||||
- Deprecate ``--watch`` [:issue:`156` by Anthony Sottile]
|
||||
- Preserve line endings [:issue:`160` by Anthony Sottile]
|
||||
- Follow up the libsass upstream: 3.3.6 --- See the release notes of LibSass
|
||||
3.3.6__. [:issue:`167` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.6
|
||||
|
||||
|
||||
|
||||
Version 0.11.1
|
||||
--------------
|
||||
|
||||
Released on April 22, 2016.
|
||||
|
||||
- Follow up the libsass upstream: 3.3.5 --- See the release notes of LibSass
|
||||
3.3.5__. [:issue:`148` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.5
|
||||
|
||||
Version 0.11.0
|
||||
--------------
|
||||
|
||||
Released on March 23, 2016.
|
||||
|
||||
- Follow up the libsass upstream: 3.3.4 --- See the release notes of LibSass
|
||||
3.3.4__. [:issue:`144` by Anthony Sottile]
|
||||
- Expose libsass version in ``sassc --version`` and ``sass.libsass_version``
|
||||
[:issue:`142` :issue:`141` :issue:`140` by Anthony Sottile]
|
||||
- Fix warning about unused enum on switch [:issue:`127` :issue:`131` by
|
||||
Anthony Sottile]
|
||||
- Sourcemaps no longer imply source comments [:issue:`124` :issue:`130` by
|
||||
Tim Tisdall]
|
||||
- Add ``--source-comments`` option to ``sassc`` [:issue:`124` :issue:`130` by
|
||||
Anthony Sottile]
|
||||
- Improve formatting of ``CompileError`` under python3 [:issue:`123` by Anthony
|
||||
Sottile]
|
||||
- Raise when compiling a directory which does not exist [:issue:`116`
|
||||
:issue:`119` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.4
|
||||
|
||||
Version 0.10.1
|
||||
--------------
|
||||
|
||||
Released on January 29, 2016.
|
||||
|
||||
- Follow up the libsass upstream: 3.3.3 --- See the release notes of LibSass
|
||||
3.3.3__. [by Anthony Sottile]
|
||||
- Allow -t for style like sassc [:issue:`98` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.3
|
||||
|
||||
Version 0.10.0
|
||||
--------------
|
||||
|
||||
Released on December 15, 2015.
|
||||
|
||||
- Support custom import callbacks [:issue:`81` by Alice Zoë Bevan–McGregor,
|
||||
Anthony Sottile]
|
||||
- Disallow arbitrary kwargs in compile() [:issue:`109` by Anthony Sottile]
|
||||
|
||||
Version 0.9.3
|
||||
-------------
|
||||
|
||||
Released on December 03, 2015.
|
||||
|
||||
- Support "indented" Sass compilation [:issue:`41` by Alice Zoë Bevan–McGregor]
|
||||
- Fix wheels on windows [:issue:`28` :issue:`49` by Anthony Sottile]
|
||||
|
||||
Version 0.9.2
|
||||
-------------
|
||||
|
||||
Released on November 12, 2015.
|
||||
|
||||
- Follow up the libsass upstream: 3.3.2 --- See the release notes of LibSass
|
||||
3.3.2__. [by Anthony Sottile]
|
||||
- Require VS 2015 to build on windows [:issue:`99` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.2
|
||||
|
||||
Version 0.9.1
|
||||
-------------
|
||||
|
||||
Released on October 29, 2015.
|
||||
|
||||
- Follow up the libsass upstream: 3.3.1 --- See the release notes of LibSass
|
||||
3.3.1__. [by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.1
|
||||
|
||||
|
||||
Version 0.9.0
|
||||
-------------
|
||||
|
||||
Released on October 28, 2015.
|
||||
|
||||
- Fix a bug with writing UTF-8 to a file [:issue:`72` by Caleb Ely]
|
||||
- Fix a segmentation fault on ^C [:issue:`87` by Anthony Sottile]
|
||||
- Follow up the libsass upstream: 3.3.0 --- See the release notes of LibSass
|
||||
3.3.0__. [:issue:`96` by Anthony Sottile]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.3.0
|
||||
|
||||
|
||||
Version 0.8.3
|
||||
-------------
|
||||
|
||||
Released on August 2, 2015.
|
||||
|
||||
- Follow up the libsass upstream: 3.2.5 --- See the release notes of LibSass
|
||||
3.2.5__. [:issue:`79`, :issue:`80` by Anthony Sottile]
|
||||
- Fixed a bug that :file:`*.sass` files were ignored.
|
||||
[:issue:`78` by Guilhem MAS-PAITRAULT]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.2.5
|
||||
|
||||
|
||||
Version 0.8.2
|
||||
-------------
|
||||
|
||||
Released on May 19, 2015.
|
||||
|
||||
- Follow up the libsass upstream: 3.2.4 --- See the release notes of LibSass
|
||||
3.2.3__, and 3.2.4__. [:issue:`69` by Anthony Sottile]
|
||||
- The default value of :class:`~sassutils.wsgi.SassMiddleware`'s
|
||||
``error_status`` parameter was changed from ``'500 Internal Server Error'``
|
||||
to ``'200 OK'`` so that Mozilla Firefox can render the error message well.
|
||||
[:issue:`67`, :issue:`68`, :issue:`70` by zxv]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.2.3
|
||||
__ https://github.com/sass/libsass/releases/tag/3.2.4
|
||||
|
||||
|
||||
Version 0.8.1
|
||||
-------------
|
||||
|
||||
Released on May 14, 2015.
|
||||
|
||||
- Fixed a bug that there was no ``'expanded'`` in :const:`sass.OUTPUT_STYLES`
|
||||
but ``'expected'`` instead which is a typo. [:issue:`66` by Triangle717]
|
||||
- Fixed broken FreeBSD build. [:issue:`65` by Toshiharu Moriyama]
|
||||
|
||||
|
||||
Version 0.8.0
|
||||
-------------
|
||||
|
||||
Released on May 3, 2015.
|
||||
|
||||
- Follow up the libsass upstream: 3.2.2 --- See the release notes of LibSass
|
||||
3.2.0__, 3.2.1__, and 3.2.2__.
|
||||
[:issue:`61`, :issue:`52`, :issue:`56`, :issue:`58`, :issue:`62`, :issue:`64`
|
||||
by Anthony Sottile]
|
||||
|
||||
- Compact and expanded output styles [:issue:`37`]
|
||||
- Strings and interpolation closer to Ruby Sass
|
||||
- The correctness of the generated sourcemap files
|
||||
- Directive buddling
|
||||
- Full support for the ``@at-root`` directive
|
||||
- Full support for ``!global`` variable scoping
|
||||
|
||||
- Now underscored files are ignored when compiling a directory.
|
||||
[:issue:`57` by Anthony Sottile]
|
||||
- Fixed broken FreeBSD build. [:issue:`34`, :issue:`60` by Ilya Baryshev]
|
||||
- :class:`~sassutils.wsgi.SassMiddleware` became to log syntax errors
|
||||
if exist during compilation to ``sassutils.wsgi.SassMiddleware`` logger
|
||||
with level ``ERROR``. [:issue:`42`]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.2.0
|
||||
__ https://github.com/sass/libsass/releases/tag/3.2.1
|
||||
__ https://github.com/sass/libsass/releases/tag/3.2.2
|
||||
|
||||
|
||||
Version 0.7.0
|
||||
-------------
|
||||
|
||||
Released on March 6, 2015.
|
||||
|
||||
Anthony Sottile contributed to the most of this release. Huge thanks to him!
|
||||
|
||||
- Follow up the libsass upstream: 3.1.0 --- See the `release note`__ of LibSass.
|
||||
[:issue:`38`, :issue:`43` by Anthony Sottile]
|
||||
|
||||
- Custom functions and imports
|
||||
- Decrementing in ``@for`` loops
|
||||
- ``@debug`` and ``@error``
|
||||
- ``not`` operator
|
||||
- ``nth()`` for maps
|
||||
- ``inspect()``
|
||||
- ``feature-exists()``
|
||||
- ``unique-id()``
|
||||
- ``random()``
|
||||
|
||||
- Added custom functions support. [:issue:`13`, :issue:`44` by Anthony Sottile]
|
||||
|
||||
- Added :class:`sass.SassFunction` class.
|
||||
- Added ``custom_functions`` parameter to :func:`sass.compile()` function.
|
||||
- Added data types for custom functions:
|
||||
|
||||
- :class:`sass.SassNumber`
|
||||
- :class:`sass.SassColor`
|
||||
- :class:`sass.SassList`
|
||||
- :class:`sass.SassMap`
|
||||
- :class:`sass.SassError`
|
||||
- :class:`sass.SassWarning`
|
||||
|
||||
- Added ``precision`` parameter to :func:`sass.compile()` function.
|
||||
[:issue:`39` by Andrea Stagi]
|
||||
- :program:`sassc` has a new :option:`-p <sassc -p>`/:option:`--precision
|
||||
<sassc --precision>` option. [:issue:`39` by Andrea Stagi]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.1.0
|
||||
|
||||
|
||||
Version 0.6.2
|
||||
-------------
|
||||
|
||||
Released on November 25, 2014.
|
||||
|
||||
Although 0.6.0--0.6.1 have needed GCC (G++) 4.8+, LLVM Clang 3.3+,
|
||||
now it became back to only need GCC (G++) 4.6+, LLVM Clang 2.9+,
|
||||
or Visual Studio 2013 Update 4+.
|
||||
|
||||
- Follow up the libsass upstream: 3.0.2 --- See the `release note`__ of libsass.
|
||||
[:issue:`33` by Rodolphe Pelloux-Prayer]
|
||||
- Fixed a bug that :program:`sassc --watch` crashed when a file is not
|
||||
compilable on the first try. [:issue:`32` by Alan Justino da Silva]
|
||||
- Fixed broken build on Windows.
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.0.2
|
||||
|
||||
|
||||
Version 0.6.1
|
||||
-------------
|
||||
|
||||
Released on November 6, 2014.
|
||||
|
||||
- Follow up the libsass upstream: 3.0.1 --- See the `release note`__ of LibSass.
|
||||
- Fixed a bug that :class:`~sassutils.wsgi.SassMiddleware` never closes
|
||||
the socket on some WSGI servers e.g. ``eventlet.wsgi``.
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.0.1
|
||||
|
||||
|
||||
Version 0.6.0
|
||||
-------------
|
||||
|
||||
Released on October 27, 2014.
|
||||
|
||||
Note that since libsass-python 0.6.0 (and libsass 3.0) it requires C++11
|
||||
to compile. Although 0.6.2 became back to only need GCC (G++) 4.6+,
|
||||
LLVM Clang 2.9+, from 0.6.0 to 0.6.1 you need GCC (G++) 4.8+, LLVM Clang 3.3+,
|
||||
or Visual Studio 2013 Update 4+.
|
||||
|
||||
- Follow up the libsass upstream: 3.0 --- See the `release note`__ of LibSass.
|
||||
|
||||
- Decent extends support
|
||||
- Basic Sass Maps Support
|
||||
- Better UTF-8 Support
|
||||
- ``call()`` function
|
||||
- Better Windows Support
|
||||
- Spec Enhancements
|
||||
|
||||
- Added missing `partial import`_ support. [:issue:`27` by item4]
|
||||
- :const:`~sass.SOURCE_COMMENTS` became deprecated.
|
||||
- :func:`sass.compile()`'s parameter ``source_comments`` now can take only
|
||||
:const:`bool` instead of :const:`str`. String values like ``'none'``,
|
||||
``'line_numbers'``, and ``'map'`` become deprecated, and will be obsolete
|
||||
soon.
|
||||
- :func:`~sassutils.builder.build_directory()` function has a new optional
|
||||
parameter ``output_style``.
|
||||
- :meth:`~sassutils.builder.Build.build()` method has a new optional
|
||||
parameter ``output_style``.
|
||||
- Added ``--output-style``/``-s`` option to
|
||||
:class:`~sassutils.distutils.build_sass` command. [:issue:`25`]
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/3.0
|
||||
.. _partial import: https://sass-lang.com/documentation/file.SASS_REFERENCE.html#partials
|
||||
|
||||
|
||||
Version 0.5.1
|
||||
-------------
|
||||
|
||||
Released on September 23, 2014.
|
||||
|
||||
- Fixed a bug that :class:`~sassutils.wsgi.SassMiddleware` yielded
|
||||
:class:`str` instead of :class:`bytes` on Python 3.
|
||||
- Fixed several Unicode-related bugs on Windows.
|
||||
- Fixed a bug that :func:`~sassutils.builder.build_directory()`,
|
||||
:class:`~sassutils.wsgi.SassMiddleware`, and
|
||||
:class:`~sassutils.distutils.build_sass` don't recursively build
|
||||
subdirectories.
|
||||
|
||||
|
||||
Version 0.5.0
|
||||
-------------
|
||||
|
||||
Released on June 6, 2014.
|
||||
|
||||
- Follow up the libsass upstream: 2.0 --- See the `release note`__ of LibSass.
|
||||
|
||||
- Added indented syntax support (:file:`*.sass` files).
|
||||
- Added expanded selector support (BEM).
|
||||
- Added string functions.
|
||||
- Fixed UTF-8 support.
|
||||
- Backward incompatibility: broken extends.
|
||||
|
||||
__ https://github.com/sass/libsass/releases/tag/v2.0
|
||||
|
||||
|
||||
Unstable version 0.4.2.20140529.cd3ee1cbe3
|
||||
------------------------------------------
|
||||
|
||||
Released on May 29, 2014.
|
||||
|
||||
- Version scheme changed to use periods (``.``) instead of hyphens (``-``)
|
||||
due to setuptools seems to treat hyphens special.
|
||||
- Fixed malformed packaging that doesn't correctly preserve the package name
|
||||
and version.
|
||||
|
||||
|
||||
Unstable Version 0.4.2-20140528-cd3ee1cbe3
|
||||
------------------------------------------
|
||||
|
||||
Released on May 28, 2014.
|
||||
|
||||
- Follow up the libsass upstream:
|
||||
:upcommit:`cd3ee1cbe34d5316eb762a43127a3de9575454ee`.
|
||||
|
||||
|
||||
Version 0.4.2
|
||||
-------------
|
||||
|
||||
Released on May 22, 2014.
|
||||
|
||||
- Fixed build failing on Mac OS X 10.8 or earlier. [:issue:`19`]
|
||||
- Fixed :exc:`UnicodeEncodeError` that :meth:`Manifest.build_one()
|
||||
<sassutils.builder.Manifest.build_one>` method rises when the input source
|
||||
contains any non-ASCII Unicode characters.
|
||||
|
||||
|
||||
Version 0.4.1
|
||||
-------------
|
||||
|
||||
Released on May 20, 2014.
|
||||
|
||||
- Fixed :exc:`UnicodeEncodeError` that rise when the input source contains
|
||||
any non-ASCII Unicode characters.
|
||||
|
||||
|
||||
Version 0.4.0
|
||||
-------------
|
||||
|
||||
Released on May 6, 2014.
|
||||
|
||||
- :program:`sassc` has a new :option:`-w <sassc -w>`/:option:`--watch
|
||||
<sassc --watch>` option.
|
||||
- Expose source maps support:
|
||||
|
||||
- :program:`sassc` has a new :option:`-m <sassc -m>`/:option:`-g
|
||||
<sassc -g>`/:option:`--sourcemap <sassc --sourcemap>` option.
|
||||
- :class:`~sassutils.wsgi.SassMiddleware` now also creates source map files
|
||||
with filenames followed by :file:`.map` suffix.
|
||||
- :meth:`Manifest.build_one() <sassutils.builder.Manifest.build_one>` method
|
||||
has a new ``source_map`` option. This option builds also a source map
|
||||
file with the filename followed by :file:`.map` suffix.
|
||||
- :func:`sass.compile()` has a new optional parameter ``source_comments``.
|
||||
It can be one of :const:`sass.SOURCE_COMMENTS` keys. It also has
|
||||
a new parameter ``source_map_filename`` which is required only when
|
||||
``source_comments='map'``.
|
||||
|
||||
- Fixed Python 3 incompatibility of :program:`sassc` program.
|
||||
- Fixed a bug that multiple ``include_paths`` doesn't work on Windows.
|
||||
|
||||
|
||||
Version 0.3.0
|
||||
-------------
|
||||
|
||||
Released on February 21, 2014.
|
||||
|
||||
- Added support for Python 3.3. [:issue:`7`]
|
||||
- Dropped support for Python 2.5.
|
||||
- Fixed build failing on Mac OS X.
|
||||
[:issue:`4`, :issue:`5`, :issue:`6` by Hyungoo Kang]
|
||||
- Now the builder creates target subdirectories recursively even if they don't
|
||||
exist yet, rather than silently failing.
|
||||
[:issue:`8`, :issue:`9` by Philipp Volguine]
|
||||
- Merged recent changes from libsass 1.0.1: `57a2f62--v1.0.1`_.
|
||||
|
||||
- Supports `variable arguments`_.
|
||||
- Supports sourcemaps.
|
||||
|
||||
.. _57a2f62--v1.0.1: https://github.com/sass/libsass/compare/57a2f627b4d2fbd3cf1913b241f1d5aa31e35580...v1.0.1
|
||||
.. _variable arguments: https://sass-lang.com/docs/yardoc/file.SASS_CHANGELOG.html#variable_arguments
|
||||
|
||||
|
||||
Version 0.2.4
|
||||
-------------
|
||||
|
||||
Released on December 4, 2012.
|
||||
|
||||
- Added :mod:`sassc` CLI executable script.
|
||||
- Added :const:`sass.OUTPUT_STYLES` constant map.
|
||||
- Merged recent changes from libsass upstream:
|
||||
`e997102--a84b181`__.
|
||||
|
||||
__ https://github.com/sass/libsass/compare/e9971023785dabd41aa44f431f603f62b15e6017...a84b181a6e59463c0ac9796ca7fdaf4864f0ad84
|
||||
|
||||
|
||||
Version 0.2.3
|
||||
-------------
|
||||
|
||||
Released on October 24, 2012.
|
||||
|
||||
- :mod:`sassutils.distutils`: Prevent double monkey patch of ``sdist``.
|
||||
- Merged upstream changes of libsass.
|
||||
|
||||
|
||||
Version 0.2.2
|
||||
-------------
|
||||
|
||||
Released on September 28, 2012.
|
||||
|
||||
- Fixed a link error on PyPy and Linux.
|
||||
- Fixed build errors on Windows.
|
||||
|
||||
|
||||
Version 0.2.1
|
||||
-------------
|
||||
|
||||
Released on September 12, 2012.
|
||||
|
||||
- Support Windows.
|
||||
|
||||
|
||||
Version 0.2.0
|
||||
-------------
|
||||
|
||||
Released on August 24, 2012.
|
||||
|
||||
- Added new :mod:`sassutils` package.
|
||||
|
||||
- Added :mod:`sassutils.builder` module to build the whole directory
|
||||
at a time.
|
||||
- Added :mod:`sassutils.distutils` module for :mod:`distutils` and
|
||||
:mod:`setuptools` integration.
|
||||
- Added :mod:`sassutils.wsgi` module which provides a development-purpose
|
||||
WSGI middleware.
|
||||
|
||||
- Added :class:`~sassutils.distutils.build_sass` command for
|
||||
:mod:`distutils`/:mod:`setuptools`.
|
||||
|
||||
|
||||
Version 0.1.1
|
||||
-------------
|
||||
|
||||
Released on August 18, 2012.
|
||||
|
||||
- Fixed segmentation fault for reading ``filename`` which does not exist.
|
||||
Now it raises a proper ``exceptions.IOError`` exception.
|
||||
|
||||
|
||||
Version 0.1.0
|
||||
-------------
|
||||
|
||||
Released on August 17, 2012. Initial version.
|
|
@ -0,0 +1,277 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# libsass documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Aug 19 22:45:57 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import sass
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
needs_sphinx = '1.3'
|
||||
|
||||
suppress_warnings = ['image.nonlocal_uri']
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.extlinks',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'libsass'
|
||||
copyright = u'2012, Hong Minhee'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version. (Parse setup.py script.)
|
||||
version = sass.__version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'libsassdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
(
|
||||
'index', 'libsass.tex', u'libsass Documentation',
|
||||
u'Hong Minhee', 'manual',
|
||||
),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(
|
||||
'index', 'libsass', u'libsass Documentation',
|
||||
[u'Hong Minhee'], 1,
|
||||
),
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(
|
||||
'index', 'libsass', u'libsass Documentation',
|
||||
u'Hong Minhee', 'libsass', 'One line description of project.',
|
||||
'Miscellaneous',
|
||||
),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'setuptools': ('https://setuptools.readthedocs.io/en/latest/', None),
|
||||
'flask': ('http://flask.pocoo.org/docs/', None),
|
||||
}
|
||||
|
||||
|
||||
extlinks = {
|
||||
'issue': ('https://github.com/sass/libsass-python/issues/%s', '#'),
|
||||
'branch': (
|
||||
'https://github.com/sass/libsass-python/compare/master...%s',
|
||||
'',
|
||||
),
|
||||
'commit': ('https://github.com/sass/libsass-python/commit/%s', ''),
|
||||
'upcommit': ('https://github.com/sass/libsass/commit/%s', ''),
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
Using with Flask
|
||||
================
|
||||
|
||||
This guide explains how to use libsass with the Flask_ web framework.
|
||||
:mod:`sassutils` package provides several tools that can be integrated
|
||||
into web applications written in Flask.
|
||||
|
||||
.. _Flask: http://flask.pocoo.org/
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
Directory layout
|
||||
----------------
|
||||
|
||||
Imagine the project contained in such directory layout:
|
||||
|
||||
- :file:`setup.py`
|
||||
- :file:`myapp/`
|
||||
|
||||
- :file:`__init__.py`
|
||||
- :file:`static/`
|
||||
|
||||
- :file:`sass/`
|
||||
- :file:`css/`
|
||||
- :file:`templates/`
|
||||
|
||||
Sass/SCSS files will go inside :file:`myapp/static/sass/` directory.
|
||||
Compiled CSS files will go inside :file:`myapp/static/css/` directory.
|
||||
CSS files can be regenerated, so add :file:`myapp/static/css/` into your
|
||||
ignore list like :file:`.gitignore` or :file:`.hgignore`.
|
||||
|
||||
|
||||
Defining manifest
|
||||
-----------------
|
||||
|
||||
The :mod:`sassutils` defines a concept named :dfn:`manifest`.
|
||||
Manifest is the build settings of Sass/SCSS. It specifies some paths
|
||||
related to building Sass/SCSS:
|
||||
|
||||
- The path of the directory which contains Sass/SCSS source files.
|
||||
- The path of the directory which the compiled CSS files will go.
|
||||
- The path, exposed to HTTP (through WSGI), of the directory that
|
||||
will contain the compiled CSS files.
|
||||
|
||||
Every package may have its own manifest. Paths have to be relative
|
||||
to the path of the package.
|
||||
|
||||
For example, in the above project, the package name is :mod:`myapp`.
|
||||
The path of the package is :file:`myapp/`. The path of the Sass/SCSS
|
||||
directory is :file:`static/sass/` (relative to the package directory).
|
||||
The path of the CSS directory is :file:`static/css/`.
|
||||
The exposed path is :file:`/static/css`.
|
||||
|
||||
These settings can be represented as the following manifests::
|
||||
|
||||
{
|
||||
'myapp': ('static/sass', 'static/css', '/static/css')
|
||||
}
|
||||
|
||||
As you can see the above, the set of manifests are represented in dictionary,
|
||||
in which the keys are packages names and the values are tuples of paths.
|
||||
|
||||
|
||||
Building Sass/SCSS for each request
|
||||
-----------------------------------
|
||||
|
||||
.. seealso::
|
||||
|
||||
Flask --- `Hooking in WSGI Middlewares`__
|
||||
The section which explains how to integrate WSGI middlewares to
|
||||
Flask.
|
||||
|
||||
Flask --- :ref:`flask:app-dispatch`
|
||||
The documentation which explains how Flask dispatches each
|
||||
request internally.
|
||||
|
||||
__ http://flask.pocoo.org/docs/quickstart/#hooking-in-wsgi-middlewares
|
||||
|
||||
In development, manually building Sass/SCSS files for each change is
|
||||
a tedious task. :class:`~sassutils.wsgi.SassMiddleware` makes the web
|
||||
application build Sass/SCSS files for each request automatically.
|
||||
It's a WSGI middleware, so it can be plugged into the web app written in
|
||||
Flask.
|
||||
|
||||
:class:`~sassutils.wsgi.SassMiddleware` takes two required parameters:
|
||||
|
||||
- The WSGI-compliant callable object.
|
||||
- The set of manifests represented as a dictionary.
|
||||
|
||||
So::
|
||||
|
||||
from flask import Flask
|
||||
from sassutils.wsgi import SassMiddleware
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.wsgi_app = SassMiddleware(app.wsgi_app, {
|
||||
'myapp': ('static/sass', 'static/css', '/static/css')
|
||||
})
|
||||
|
||||
And then, if you want to link a compiled CSS file, use the
|
||||
:func:`~flask.url_for()` function:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<link href="{{ url_for('static', filename='css/style.scss.css') }}"
|
||||
rel="stylesheet" type="text/css">
|
||||
|
||||
.. note::
|
||||
|
||||
The linked filename is :file:`style.scss.css`, not just :file:`style.scss`.
|
||||
All compiled filenames have trailing ``.css`` suffix.
|
||||
|
||||
|
||||
Building Sass/SCSS for each deployment
|
||||
--------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section assumes that you use setuptools_ for deployment.
|
||||
|
||||
.. seealso::
|
||||
|
||||
Flask --- :ref:`flask:distribute-deployment`
|
||||
How to deploy Flask application using setuptools_.
|
||||
|
||||
If libsass is installed in the :file:`site-packages` (for example,
|
||||
your virtualenv), the :file:`setup.py` script also gets a new command
|
||||
provided by libsass: :class:`~sassutils.distutils.build_sass`.
|
||||
The command is aware of the ``sass_manifests`` option of :file:`setup.py` and
|
||||
builds all Sass/SCSS sources according to the manifests.
|
||||
|
||||
Add these arguments to :file:`setup.py` script::
|
||||
|
||||
setup(
|
||||
# ...,
|
||||
setup_requires=['libsass >= 0.6.0'],
|
||||
sass_manifests={
|
||||
'myapp': ('static/sass', 'static/css', '/static/css')
|
||||
}
|
||||
)
|
||||
|
||||
The ``setup_requires`` option makes sure that libsass is installed
|
||||
in :file:`site-packages` (for example, your virtualenv) before
|
||||
the :file:`setup.py` script. That means if you run the :file:`setup.py`
|
||||
script and libsass isn't installed in advance, it will automatically
|
||||
install libsass first.
|
||||
|
||||
The ``sass_manifests`` specifies the manifests for libsass.
|
||||
|
||||
Now :program:`setup.py build_sass` will compile all Sass/SCSS files
|
||||
in the specified path and generates compiled CSS files inside the specified
|
||||
path (according to the manifests).
|
||||
|
||||
If you use it with ``sdist`` or ``bdist`` commands, the packed archive will
|
||||
also contain the compiled CSS files!
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
$ python setup.py build_sass sdist
|
||||
|
||||
You can add aliases to make these commands always run the ``build_sass``
|
||||
command first. Make :file:`setup.cfg` config:
|
||||
|
||||
.. sourcecode:: ini
|
||||
|
||||
[aliases]
|
||||
sdist = build_sass sdist
|
||||
bdist = build_sass bdist
|
||||
|
||||
Now it automatically builds Sass/SCSS sources and include the compiled CSS files
|
||||
to the package archive when you run :program:`setup.py sdist`.
|
||||
|
||||
.. _setuptools: https://pypi.org/pypi/setuptools/
|
|
@ -0,0 +1,165 @@
|
|||
libsass-python: Sass_/SCSS for Python
|
||||
=====================================
|
||||
|
||||
This package provides a simple Python extension module :mod:`sass` which is
|
||||
binding LibSass_ (written in C/C++ by Hampton Catlin and Aaron Leung).
|
||||
It's very straightforward and there isn't any headache related Python
|
||||
distribution/deployment. That means you can add just ``libsass`` into
|
||||
your :file:`setup.py`'s ``install_requires`` list or :file:`requirements.txt`
|
||||
file.
|
||||
|
||||
It currently supports CPython 2.6, 2.7, 3.5--3.7, and PyPy 2.3+!
|
||||
|
||||
.. _Sass: https://sass-lang.com/
|
||||
.. _LibSass: https://github.com/sass/libsass
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- You don't need any Ruby/Node.js stack at all, for development or deployment
|
||||
either.
|
||||
- Fast. (LibSass_ is written in C++.)
|
||||
- Simple API. See :ref:`example code <example>` for details.
|
||||
- Custom functions.
|
||||
- ``@import`` callbacks.
|
||||
- Support both tabbed (Sass) and braces (SCSS) syntax.
|
||||
- WSGI middleware for ease of development.
|
||||
It automatically compiles Sass/SCSS files for each request.
|
||||
See also :mod:`sassutils.wsgi` for details.
|
||||
- :mod:`setuptools`/:mod:`distutils` integration.
|
||||
You can build all Sass/SCSS files using
|
||||
:program:`setup.py build_sass` command.
|
||||
See also :mod:`sassutils.distutils` for details.
|
||||
- Works also on PyPy.
|
||||
- Provides prebuilt wheel (:pep:`427`) binaries for Windows and Mac.
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
It's available on PyPI_, so you can install it using :program:`pip`:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install libsass
|
||||
|
||||
.. note::
|
||||
|
||||
libsass requires some features introduced by the recent C++ standard.
|
||||
You need a C++ compiler that support those features.
|
||||
See also libsass project's README_ file.
|
||||
|
||||
.. _PyPI: https://pypi.org/pypi/libsass/
|
||||
.. _README: https://github.com/sass/libsass#readme
|
||||
|
||||
|
||||
.. _example:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Compile a String of Sass to CSS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
>>> import sass
|
||||
>>> sass.compile(string='a { b { color: blue; } }')
|
||||
'a b {\n color: blue; }\n'
|
||||
|
||||
Compile a Directory of Sass Files to CSS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
>>> import sass
|
||||
>>> import os
|
||||
>>> os.mkdir('css')
|
||||
>>> os.mkdir('sass')
|
||||
>>> scss = """\
|
||||
... $theme_color: #cc0000;
|
||||
... body {
|
||||
... background-color: $theme_color;
|
||||
... }
|
||||
... """
|
||||
>>> with open('sass/example.scss', 'w') as example_scss:
|
||||
... example_scss.write(scss)
|
||||
...
|
||||
>>> sass.compile(dirname=('sass', 'css'), output_style='compressed')
|
||||
>>> with open('css/example.css') as example_css:
|
||||
... print(example_css.read())
|
||||
...
|
||||
body{background-color:#c00}
|
||||
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
frameworks/flask
|
||||
changes
|
||||
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
pysassc
|
||||
sass
|
||||
sassutils
|
||||
|
||||
|
||||
Credit
|
||||
------
|
||||
|
||||
Hong Minhee wrote this Python binding of LibSass_.
|
||||
|
||||
Hampton Catlin and Aaron Leung wrote LibSass_, which is portable C/C++
|
||||
implementation of Sass_.
|
||||
|
||||
Hampton Catlin originally designed Sass_ language and wrote the first
|
||||
reference implementation of it in Ruby.
|
||||
|
||||
The above three are all distributed under `MIT license`_.
|
||||
|
||||
.. _MIT license: https://mit-license.org/
|
||||
|
||||
|
||||
Open source
|
||||
-----------
|
||||
|
||||
GitHub (Git repository + issues)
|
||||
https://github.com/sass/libsass-python
|
||||
|
||||
Azure Pipelines CI (linux + windows)
|
||||
https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
|
||||
.. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=master
|
||||
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
:alt: Build Status
|
||||
|
||||
Azure Pipelines Coverage (Test coverage)
|
||||
https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
|
||||
.. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/master.svg
|
||||
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master
|
||||
:alt: Coverage Status
|
||||
|
||||
PyPI
|
||||
https://pypi.org/pypi/libsass/
|
||||
|
||||
.. image:: https://badge.fury.io/py/libsass.svg
|
||||
:alt: PyPI
|
||||
:target: https://pypi.org/pypi/libsass/
|
||||
|
||||
Changelog
|
||||
:doc:`changes`
|
||||
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
- :ref:`genindex`
|
||||
- :ref:`modindex`
|
||||
- :ref:`search`
|
|
@ -0,0 +1,190 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\libsass.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\libsass.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
.. program:: pysassc
|
||||
|
||||
.. automodule:: pysassc
|
||||
:members:
|
|
@ -0,0 +1,2 @@
|
|||
.. automodule:: sass
|
||||
:members:
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
.. automodule:: sassutils
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
sassutils/builder
|
||||
sassutils/distutils
|
||||
sassutils/wsgi
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
.. automodule:: sassutils.builder
|
||||
:members:
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
.. automodule:: sassutils.distutils
|
||||
:members:
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
.. automodule:: sassutils.wsgi
|
||||
:members:
|
|
@ -0,0 +1,243 @@
|
|||
#!/usr/bin/env python
|
||||
r""":mod:`pysassc` --- SassC compliant command line interface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This provides SassC_ compliant CLI executable named :program:`pysassc`:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
$ pysassc
|
||||
Usage: pysassc [options] SCSS_FILE [CSS_FILE]
|
||||
|
||||
There are options as well:
|
||||
|
||||
.. option:: -t <style>, --style <style>
|
||||
|
||||
Coding style of the compiled result. The same as :func:`sass.compile()`
|
||||
function's ``output_style`` keyword argument. Default is ``nested``.
|
||||
|
||||
.. option:: -s <style>, --output-style <style>
|
||||
|
||||
Alias for -t / --style.
|
||||
|
||||
.. deprecated:: 0.11.0
|
||||
|
||||
.. option:: -I <dir>, --include-path <dir>
|
||||
|
||||
Optional directory path to find ``@import``\ ed (S)CSS files.
|
||||
Can be multiply used.
|
||||
|
||||
.. option:: -m, -g, --sourcemap
|
||||
|
||||
Emit source map. Requires the second argument (output CSS filename).
|
||||
The filename of source map will be the output CSS filename followed by
|
||||
:file:`.map`.
|
||||
|
||||
.. versionadded:: 0.4.0
|
||||
|
||||
.. option:: -p, --precision
|
||||
|
||||
Set the precision for numbers. Default is 5.
|
||||
|
||||
.. versionadded:: 0.7.0
|
||||
|
||||
.. option:: --source-comments
|
||||
|
||||
Include debug info in output.
|
||||
|
||||
.. versionadded:: 0.11.0
|
||||
|
||||
.. option:: --sourcemap-file
|
||||
|
||||
Output file for source map
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
.. option:: --sourcemap-contents
|
||||
|
||||
Embed sourcesContent in source map.
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
.. option:: --sourcemap-embed
|
||||
|
||||
Embed sourceMappingUrl as data URI
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
.. option:: --omit-sourcemap-url
|
||||
|
||||
Omit source map URL comment from output
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
.. option:: --sourcemap-root
|
||||
|
||||
Base path, will be emitted to sourceRoot in source-map as is
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
|
||||
.. option:: -v, --version
|
||||
|
||||
Prints the program version.
|
||||
|
||||
.. option:: -h, --help
|
||||
|
||||
Prints the help message.
|
||||
|
||||
.. _SassC: https://github.com/sass/sassc
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import io
|
||||
import optparse
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import sass
|
||||
|
||||
|
||||
def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr):
|
||||
parser = optparse.OptionParser(
|
||||
usage='%prog [options] SCSS_FILE [OUT_CSS_FILE]',
|
||||
version='%prog {} (sass/libsass {})'.format(
|
||||
sass.__version__, sass.libsass_version,
|
||||
),
|
||||
)
|
||||
output_styles = list(sass.OUTPUT_STYLES)
|
||||
output_styles = ', '.join(output_styles[:-1]) + ', or ' + output_styles[-1]
|
||||
parser.add_option(
|
||||
'-t', '--style', '-s', '--output-style', metavar='STYLE',
|
||||
type='choice', choices=list(sass.OUTPUT_STYLES), default='nested',
|
||||
help=(
|
||||
'Coding style of the compiled result. Choose one of ' +
|
||||
output_styles + '. [default: %default]'
|
||||
),
|
||||
)
|
||||
parser.add_option(
|
||||
'-m', '-g', '--sourcemap', dest='source_map',
|
||||
action='store_true', default=False,
|
||||
help='Emit source map. Requires the second argument '
|
||||
'(output css filename).',
|
||||
)
|
||||
parser.add_option(
|
||||
'--sourcemap-file', dest='source_map_file', metavar='FILE',
|
||||
action='store',
|
||||
help='Output file for source map. If omitted, source map is based on '
|
||||
'the output css filename',
|
||||
)
|
||||
parser.add_option(
|
||||
'--sourcemap-contents', dest='source_map_contents',
|
||||
action='store_true', default=False,
|
||||
help='Embed sourcesContent in source map',
|
||||
)
|
||||
parser.add_option(
|
||||
'--sourcemap-embed', dest='source_map_embed',
|
||||
action='store_true', default=False,
|
||||
help='Embed sourceMappingUrl as data URI',
|
||||
)
|
||||
parser.add_option(
|
||||
'--omit-sourcemap-url', dest='omit_source_map_url',
|
||||
action='store_true', default=False,
|
||||
help='Omit source map URL comment from output',
|
||||
)
|
||||
parser.add_option(
|
||||
'--sourcemap-root', metavar='DIR',
|
||||
dest='source_map_root', action='store',
|
||||
help='Base path, will be emitted to sourceRoot in source-map as is',
|
||||
)
|
||||
parser.add_option(
|
||||
'-I', '--include-path', metavar='DIR',
|
||||
dest='include_paths', action='append',
|
||||
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,
|
||||
help='Set the precision for numbers. [default: %default]',
|
||||
)
|
||||
parser.add_option(
|
||||
'--source-comments', action='store_true', default=False,
|
||||
help='Include debug info in output',
|
||||
)
|
||||
parser.add_option('--import-extensions', help=optparse.SUPPRESS_HELP)
|
||||
options, args = parser.parse_args(argv[1:])
|
||||
error = functools.partial(
|
||||
print,
|
||||
parser.get_prog_name() + ': error:',
|
||||
file=stderr,
|
||||
)
|
||||
if not args:
|
||||
parser.print_usage(stderr)
|
||||
error('too few arguments')
|
||||
return 2
|
||||
elif len(args) > 2:
|
||||
parser.print_usage(stderr)
|
||||
error('too many arguments')
|
||||
return 2
|
||||
filename = args[0]
|
||||
if options.source_map and len(args) < 2:
|
||||
parser.print_usage(stderr)
|
||||
error(
|
||||
'-m/-g/--sourcemap requires the second argument, the output '
|
||||
'css filename.',
|
||||
)
|
||||
return 2
|
||||
|
||||
if options.import_extensions:
|
||||
warnings.warn(
|
||||
'`--import-extensions` has no effect and will be removed in '
|
||||
'a future version.',
|
||||
FutureWarning,
|
||||
)
|
||||
|
||||
try:
|
||||
if options.source_map:
|
||||
source_map_filename = options.source_map_file or args[1] + '.map'
|
||||
css, source_map = sass.compile(
|
||||
filename=filename,
|
||||
output_style=options.style,
|
||||
source_comments=options.source_comments,
|
||||
source_map_filename=source_map_filename,
|
||||
source_map_contents=options.source_map_contents,
|
||||
source_map_embed=options.source_map_embed,
|
||||
omit_source_map_url=options.omit_source_map_url,
|
||||
source_map_root=options.source_map_root,
|
||||
output_filename_hint=args[1],
|
||||
include_paths=options.include_paths,
|
||||
precision=options.precision,
|
||||
)
|
||||
else:
|
||||
source_map_filename = None
|
||||
source_map = None
|
||||
css = sass.compile(
|
||||
filename=filename,
|
||||
output_style=options.style,
|
||||
source_comments=options.source_comments,
|
||||
include_paths=options.include_paths,
|
||||
precision=options.precision,
|
||||
)
|
||||
except (IOError, OSError) as e:
|
||||
error(e)
|
||||
return 3
|
||||
except sass.CompileError as e:
|
||||
error(e)
|
||||
return 1
|
||||
else:
|
||||
if len(args) < 2:
|
||||
print(css, file=stdout)
|
||||
else:
|
||||
with io.open(args[1], 'w', encoding='utf-8', newline='') as f:
|
||||
f.write(css)
|
||||
if source_map_filename:
|
||||
with io.open(
|
||||
source_map_filename, 'w', encoding='utf-8', newline='',
|
||||
) as f:
|
||||
f.write(source_map)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -0,0 +1,5 @@
|
|||
coverage
|
||||
coverage-enable-subprocess
|
||||
pre-commit
|
||||
pytest
|
||||
werkzeug>=0.9
|
|
@ -0,0 +1,869 @@
|
|||
""":mod:`sass` --- Binding of ``libsass``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This simple C extension module provides a very simple binding of ``libsass``,
|
||||
which is written in C/C++. It contains only one function and one exception
|
||||
type.
|
||||
|
||||
>>> import sass
|
||||
>>> sass.compile(string='a { b { color: blue; } }')
|
||||
'a b {\n color: blue; }\n'
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import inspect
|
||||
import io
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from six import string_types, text_type, PY2
|
||||
|
||||
import _sass
|
||||
from sassutils._compat import collections_abc
|
||||
|
||||
__all__ = (
|
||||
'MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError', 'SassColor',
|
||||
'SassError', 'SassFunction', 'SassList', 'SassMap', 'SassNumber',
|
||||
'SassWarning', 'and_join', 'compile', 'libsass_version',
|
||||
)
|
||||
__version__ = '0.21.0'
|
||||
libsass_version = _sass.libsass_version
|
||||
|
||||
|
||||
#: (:class:`collections.abc.Mapping`) The dictionary of output styles.
|
||||
#: Keys are output name strings, and values are flag integers.
|
||||
OUTPUT_STYLES = _sass.OUTPUT_STYLES
|
||||
|
||||
#: (:class:`collections.abc.Mapping`) The dictionary of source comments styles.
|
||||
#: Keys are mode names, and values are corresponding flag integers.
|
||||
#:
|
||||
#: .. versionadded:: 0.4.0
|
||||
#:
|
||||
#: .. deprecated:: 0.6.0
|
||||
SOURCE_COMMENTS = {'none': 0, 'line_numbers': 1, 'default': 1, 'map': 2}
|
||||
|
||||
#: (:class:`frozenset`) The set of keywords :func:`compile()` can take.
|
||||
MODES = frozenset(('string', 'filename', 'dirname'))
|
||||
|
||||
|
||||
def to_native_s(s):
|
||||
if isinstance(s, bytes) and not PY2: # pragma: no cover (py3)
|
||||
s = s.decode('UTF-8')
|
||||
elif isinstance(s, text_type) and PY2: # pragma: no cover (py2)
|
||||
s = s.encode('UTF-8')
|
||||
return s
|
||||
|
||||
|
||||
class CompileError(ValueError):
|
||||
"""The exception type that is raised by :func:`compile()`.
|
||||
It is a subtype of :exc:`exceptions.ValueError`.
|
||||
"""
|
||||
def __init__(self, msg):
|
||||
super(CompileError, self).__init__(to_native_s(msg))
|
||||
|
||||
|
||||
def mkdirp(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError:
|
||||
if os.path.isdir(path):
|
||||
return
|
||||
raise
|
||||
|
||||
|
||||
class SassFunction(object):
|
||||
"""Custom function for Sass. It can be instantiated using
|
||||
:meth:`from_lambda()` and :meth:`from_named_function()` as well.
|
||||
|
||||
:param name: the function name
|
||||
:type name: :class:`str`
|
||||
:param arguments: the argument names
|
||||
:type arguments: :class:`collections.abc.Sequence`
|
||||
:param callable_: the actual function to be called
|
||||
:type callable_: :class:`collections.abc.Callable`
|
||||
|
||||
.. versionadded:: 0.7.0
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = 'name', 'arguments', 'callable_'
|
||||
|
||||
@classmethod
|
||||
def from_lambda(cls, name, lambda_):
|
||||
"""Make a :class:`SassFunction` object from the given ``lambda_``
|
||||
function. Since lambda functions don't have their name, it need
|
||||
its ``name`` as well. Arguments are automatically inspected.
|
||||
|
||||
:param name: the function name
|
||||
:type name: :class:`str`
|
||||
:param lambda_: the actual lambda function to be called
|
||||
:type lambda_: :class:`types.LambdaType`
|
||||
:returns: a custom function wrapper of the ``lambda_`` function
|
||||
:rtype: :class:`SassFunction`
|
||||
|
||||
"""
|
||||
if PY2: # pragma: no cover
|
||||
a = inspect.getargspec(lambda_)
|
||||
varargs, varkw, defaults, kwonlyargs = (
|
||||
a.varargs, a.keywords, a.defaults, None,
|
||||
)
|
||||
else: # pragma: no cover
|
||||
a = inspect.getfullargspec(lambda_)
|
||||
varargs, varkw, defaults, kwonlyargs = (
|
||||
a.varargs, a.varkw, a.defaults, a.kwonlyargs,
|
||||
)
|
||||
|
||||
if varargs or varkw or defaults or kwonlyargs:
|
||||
raise TypeError(
|
||||
'functions cannot have starargs or defaults: {} {}'.format(
|
||||
name, lambda_,
|
||||
),
|
||||
)
|
||||
return cls(name, a.args, lambda_)
|
||||
|
||||
@classmethod
|
||||
def from_named_function(cls, function):
|
||||
"""Make a :class:`SassFunction` object from the named ``function``.
|
||||
Function name and arguments are automatically inspected.
|
||||
|
||||
:param function: the named function to be called
|
||||
:type function: :class:`types.FunctionType`
|
||||
:returns: a custom function wrapper of the ``function``
|
||||
:rtype: :class:`SassFunction`
|
||||
|
||||
"""
|
||||
if not getattr(function, '__name__', ''):
|
||||
raise TypeError('function must be named')
|
||||
return cls.from_lambda(function.__name__, function)
|
||||
|
||||
def __init__(self, name, arguments, callable_):
|
||||
if not isinstance(name, string_types):
|
||||
raise TypeError('name must be a string, not ' + repr(name))
|
||||
elif not isinstance(arguments, collections_abc.Sequence):
|
||||
raise TypeError(
|
||||
'arguments must be a sequence, not ' +
|
||||
repr(arguments),
|
||||
)
|
||||
elif not callable(callable_):
|
||||
raise TypeError(repr(callable_) + ' is not callable')
|
||||
self.name = name
|
||||
self.arguments = tuple(
|
||||
arg if arg.startswith('$') else '$' + arg
|
||||
for arg in arguments
|
||||
)
|
||||
self.callable_ = callable_
|
||||
|
||||
@property
|
||||
def signature(self):
|
||||
"""Signature string of the function."""
|
||||
return '{}({})'.format(self.name, ', '.join(self.arguments))
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.callable_(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return self.signature
|
||||
|
||||
|
||||
def _normalize_importer_return_value(result):
|
||||
# An importer must return an iterable of iterables of 1-3 stringlike
|
||||
# objects
|
||||
if result is None:
|
||||
return result
|
||||
|
||||
def _to_importer_result(single_result):
|
||||
single_result = tuple(single_result)
|
||||
if len(single_result) not in (1, 2, 3):
|
||||
raise ValueError(
|
||||
'Expected importer result to be a tuple of length (1, 2, 3) '
|
||||
'but got {}: {!r}'.format(len(single_result), single_result),
|
||||
)
|
||||
|
||||
def _to_bytes(obj):
|
||||
if not isinstance(obj, bytes):
|
||||
return obj.encode('UTF-8')
|
||||
else:
|
||||
return obj
|
||||
|
||||
return tuple(_to_bytes(s) for s in single_result)
|
||||
|
||||
return tuple(_to_importer_result(x) for x in result)
|
||||
|
||||
|
||||
def _importer_callback_wrapper(func):
|
||||
def inner(path, prev):
|
||||
path, prev = path.decode('UTF-8'), prev.decode('UTF-8')
|
||||
num_args = getattr(inner, '_num_args', None)
|
||||
if num_args is None:
|
||||
try:
|
||||
ret = func(path, prev)
|
||||
except TypeError:
|
||||
inner._num_args = 1
|
||||
ret = func(path)
|
||||
else:
|
||||
inner._num_args = 2
|
||||
elif num_args == 2:
|
||||
ret = func(path, prev)
|
||||
else:
|
||||
ret = func(path)
|
||||
return _normalize_importer_return_value(ret)
|
||||
return inner
|
||||
|
||||
|
||||
def _validate_importers(importers):
|
||||
"""Validates the importers and decorates the callables with our output
|
||||
formatter.
|
||||
"""
|
||||
# They could have no importers, that's chill
|
||||
if importers is None:
|
||||
return None
|
||||
|
||||
def _to_importer(priority, func):
|
||||
assert isinstance(priority, int), priority
|
||||
assert callable(func), func
|
||||
return (priority, _importer_callback_wrapper(func))
|
||||
|
||||
# Our code assumes tuple of tuples
|
||||
return tuple(_to_importer(priority, func) for priority, func in importers)
|
||||
|
||||
|
||||
def _raise(e):
|
||||
raise e
|
||||
|
||||
|
||||
def compile_dirname(
|
||||
search_path, output_path, output_style, source_comments, include_paths,
|
||||
precision, custom_functions, importers, source_map_contents,
|
||||
source_map_embed, omit_source_map_url, source_map_root,
|
||||
):
|
||||
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
for dirpath, _, filenames in os.walk(search_path, onerror=_raise):
|
||||
filenames = [
|
||||
filename for filename in filenames
|
||||
if filename.endswith(('.scss', '.sass')) and
|
||||
not filename.startswith('_')
|
||||
]
|
||||
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('.s[ac]ss$', '.css', output_filename)
|
||||
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, None,
|
||||
source_map_contents, source_map_embed, omit_source_map_url,
|
||||
source_map_root,
|
||||
)
|
||||
if s:
|
||||
v = v.decode('UTF-8')
|
||||
mkdirp(os.path.dirname(output_filename))
|
||||
with io.open(
|
||||
output_filename, 'w', encoding='UTF-8', newline='',
|
||||
) as output_file:
|
||||
output_file.write(v)
|
||||
else:
|
||||
return False, v
|
||||
return True, None
|
||||
|
||||
|
||||
def _check_no_remaining_kwargs(func, kwargs):
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
'{}() got unexpected keyword argument(s) {}'.format(
|
||||
func.__name__,
|
||||
', '.join("'{}'".format(arg) for arg in sorted(kwargs)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def compile(**kwargs):
|
||||
r"""There are three modes of parameters :func:`compile()` can take:
|
||||
``string``, ``filename``, and ``dirname``.
|
||||
|
||||
The ``string`` parameter is the most basic way to compile Sass.
|
||||
It simply takes a string of Sass code, and then returns a compiled
|
||||
CSS string.
|
||||
|
||||
:param string: Sass source code to compile. it's exclusive to
|
||||
``filename`` and ``dirname`` parameters
|
||||
:type string: :class:`str`
|
||||
:param output_style: an optional coding style of the compiled result.
|
||||
choose one of: ``'nested'`` (default), ``'expanded'``,
|
||||
``'compact'``, ``'compressed'``
|
||||
:type output_style: :class:`str`
|
||||
:param source_comments: whether to add comments about source lines.
|
||||
:const:`False` by default
|
||||
:type source_comments: :class:`bool`
|
||||
:param source_map_contents: embed include contents in map
|
||||
:type source_map_contents: :class:`bool`
|
||||
:param source_map_embed: embed sourceMappingUrl as data URI
|
||||
:type source_map_embed: :class:`bool`
|
||||
:param omit_source_map_url: omit source map URL comment from output
|
||||
:type omit_source_map_url: :class:`bool`
|
||||
:param source_map_root: base path, will be emitted in source map as is
|
||||
:type source_map_root: :class:`str`
|
||||
:param include_paths: an optional list of paths to find ``@import``\ ed
|
||||
Sass/CSS source files
|
||||
:type include_paths: :class:`collections.abc.Sequence`
|
||||
:param precision: optional precision for numbers. :const:`5` by default.
|
||||
:type precision: :class:`int`
|
||||
:param custom_functions: optional mapping of custom functions.
|
||||
see also below `custom functions
|
||||
<custom-functions_>`_ description
|
||||
:type custom_functions: :class:`set`,
|
||||
:class:`collections.abc.Sequence`,
|
||||
:class:`collections.abc.Mapping`
|
||||
:param custom_import_extensions: (ignored, for backward compatibility)
|
||||
:param indented: optional declaration that the string is Sass, not SCSS
|
||||
formatted. :const:`False` by default
|
||||
:type indented: :class:`bool`
|
||||
:returns: the compiled CSS string
|
||||
:param importers: optional callback functions.
|
||||
see also below `importer callbacks
|
||||
<importer-callbacks_>`_ description
|
||||
:type importers: :class:`collections.abc.Callable`
|
||||
:rtype: :class:`str`
|
||||
:raises sass.CompileError: when it fails for any reason
|
||||
(for example the given Sass has broken syntax)
|
||||
|
||||
The ``filename`` is the most commonly used way. It takes a string of
|
||||
Sass filename, and then returns a compiled CSS string.
|
||||
|
||||
:param filename: the filename of Sass source code to compile.
|
||||
it's exclusive to ``string`` and ``dirname`` parameters
|
||||
:type filename: :class:`str`
|
||||
:param output_style: an optional coding style of the compiled result.
|
||||
choose one of: ``'nested'`` (default), ``'expanded'``,
|
||||
``'compact'``, ``'compressed'``
|
||||
:type output_style: :class:`str`
|
||||
:param source_comments: whether to add comments about source lines.
|
||||
:const:`False` by default
|
||||
:type source_comments: :class:`bool`
|
||||
:param source_map_filename: use source maps and indicate the source map
|
||||
output filename. :const:`None` means not
|
||||
using source maps. :const:`None` by default.
|
||||
:type source_map_filename: :class:`str`
|
||||
:param source_map_contents: embed include contents in map
|
||||
:type source_map_contents: :class:`bool`
|
||||
:param source_map_embed: embed sourceMappingUrl as data URI
|
||||
:type source_map_embed: :class:`bool`
|
||||
:param omit_source_map_url: omit source map URL comment from output
|
||||
:type omit_source_map_url: :class:`bool`
|
||||
:param source_map_root: base path, will be emitted in source map as is
|
||||
:type source_map_root: :class:`str`
|
||||
:param include_paths: an optional list of paths to find ``@import``\ ed
|
||||
Sass/CSS source files
|
||||
:type include_paths: :class:`collections.abc.Sequence`
|
||||
:param precision: optional precision for numbers. :const:`5` by default.
|
||||
:type precision: :class:`int`
|
||||
:param custom_functions: optional mapping of custom functions.
|
||||
see also below `custom functions
|
||||
<custom-functions_>`_ description
|
||||
:type custom_functions: :class:`set`,
|
||||
:class:`collections.abc.Sequence`,
|
||||
:class:`collections.abc.Mapping`
|
||||
:param custom_import_extensions: (ignored, for backward compatibility)
|
||||
:param importers: optional callback functions.
|
||||
see also below `importer callbacks
|
||||
<importer-callbacks_>`_ description
|
||||
:type importers: :class:`collections.abc.Callable`
|
||||
:returns: the compiled CSS string, or a pair of the compiled CSS string
|
||||
and the source map string if ``source_map_filename`` is set
|
||||
:rtype: :class:`str`, :class:`tuple`
|
||||
:raises sass.CompileError: when it fails for any reason
|
||||
(for example the given Sass has broken syntax)
|
||||
:raises exceptions.IOError: when the ``filename`` doesn't exist or
|
||||
cannot be read
|
||||
|
||||
The ``dirname`` is useful for automation. It takes a pair of paths.
|
||||
The first of the ``dirname`` pair refers the source directory, contains
|
||||
several Sass source files to compiled. Sass source files can be nested
|
||||
in directories. The second of the pair refers the output directory
|
||||
that compiled CSS files would be saved. Directory tree structure of
|
||||
the source directory will be maintained in the output directory as well.
|
||||
If ``dirname`` parameter is used the function returns :const:`None`.
|
||||
|
||||
:param dirname: a pair of ``(source_dir, output_dir)``.
|
||||
it's exclusive to ``string`` and ``filename``
|
||||
parameters
|
||||
:type dirname: :class:`tuple`
|
||||
:param output_style: an optional coding style of the compiled result.
|
||||
choose one of: ``'nested'`` (default), ``'expanded'``,
|
||||
``'compact'``, ``'compressed'``
|
||||
:type output_style: :class:`str`
|
||||
:param source_comments: whether to add comments about source lines.
|
||||
:const:`False` by default
|
||||
:type source_comments: :class:`bool`
|
||||
:param source_map_contents: embed include contents in map
|
||||
:type source_map_contents: :class:`bool`
|
||||
:param source_map_embed: embed sourceMappingUrl as data URI
|
||||
:type source_map_embed: :class:`bool`
|
||||
:param omit_source_map_url: omit source map URL comment from output
|
||||
:type omit_source_map_url: :class:`bool`
|
||||
:param source_map_root: base path, will be emitted in source map as is
|
||||
:type source_map_root: :class:`str`
|
||||
:param include_paths: an optional list of paths to find ``@import``\ ed
|
||||
Sass/CSS source files
|
||||
:type include_paths: :class:`collections.abc.Sequence`
|
||||
:param precision: optional precision for numbers. :const:`5` by default.
|
||||
:type precision: :class:`int`
|
||||
:param custom_functions: optional mapping of custom functions.
|
||||
see also below `custom functions
|
||||
<custom-functions_>`_ description
|
||||
:type custom_functions: :class:`set`,
|
||||
:class:`collections.abc.Sequence`,
|
||||
:class:`collections.abc.Mapping`
|
||||
:param custom_import_extensions: (ignored, for backward compatibility)
|
||||
:raises sass.CompileError: when it fails for any reason
|
||||
(for example the given Sass has broken syntax)
|
||||
|
||||
.. _custom-functions:
|
||||
|
||||
The ``custom_functions`` parameter can take three types of forms:
|
||||
|
||||
:class:`~set`/:class:`~collections.abc.Sequence` of \
|
||||
:class:`SassFunction`\ s
|
||||
It is the most general form. Although pretty verbose, it can take
|
||||
any kind of callables like type objects, unnamed functions,
|
||||
and user-defined callables.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
sass.compile(
|
||||
...,
|
||||
custom_functions={
|
||||
sass.SassFunction('func-name', ('$a', '$b'), some_callable),
|
||||
...
|
||||
}
|
||||
)
|
||||
|
||||
:class:`~collections.abc.Mapping` of names to functions
|
||||
Less general, but easier-to-use form. Although it's not it can take
|
||||
any kind of callables, it can take any kind of *functions* defined
|
||||
using :keyword:`def`/:keyword:`lambda` syntax.
|
||||
It cannot take callables other than them since inspecting arguments
|
||||
is not always available for every kind of callables.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
sass.compile(
|
||||
...,
|
||||
custom_functions={
|
||||
'func-name': lambda a, b: ...,
|
||||
...
|
||||
}
|
||||
)
|
||||
|
||||
:class:`~set`/:class:`~collections.abc.Sequence` of \
|
||||
named functions
|
||||
Not general, but the easiest-to-use form for *named* functions.
|
||||
It can take only named functions, defined using :keyword:`def`.
|
||||
It cannot take lambdas sinc names are unavailable for them.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def func_name(a, b):
|
||||
return ...
|
||||
|
||||
sass.compile(
|
||||
...,
|
||||
custom_functions={func_name}
|
||||
)
|
||||
|
||||
.. _importer-callbacks:
|
||||
|
||||
Newer versions of ``libsass`` allow developers to define callbacks to be
|
||||
called and given a chance to process ``@import`` directives. You can
|
||||
define yours by passing in a list of callables via the ``importers``
|
||||
parameter. The callables must be passed as 2-tuples in the form:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
(priority_int, callback_fn)
|
||||
|
||||
A priority of zero is acceptable; priority determines the order callbacks
|
||||
are attempted.
|
||||
|
||||
These callbacks can accept one or two string arguments. The first argument
|
||||
is the path that was passed to the ``@import`` directive; the second
|
||||
(optional) argument is the previous resolved path, where the ``@import``
|
||||
directive was found. The callbacks must either return ``None`` to
|
||||
indicate the path wasn't handled by that callback (to continue with others
|
||||
or fall back on internal ``libsass`` filesystem behaviour) or a list of
|
||||
one or more tuples, each in one of three forms:
|
||||
|
||||
* A 1-tuple representing an alternate path to handle internally; or,
|
||||
* A 2-tuple representing an alternate path and the content that path
|
||||
represents; or,
|
||||
* A 3-tuple representing the same as the 2-tuple with the addition of a
|
||||
"sourcemap".
|
||||
|
||||
All tuple return values must be strings. As a not overly realistic
|
||||
example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def my_importer(path, prev):
|
||||
return [(path, '#' + path + ' { color: red; }')]
|
||||
|
||||
sass.compile(
|
||||
...,
|
||||
importers=[(0, my_importer)]
|
||||
)
|
||||
|
||||
Now, within the style source, attempting to ``@import 'button';`` will
|
||||
instead attach ``color: red`` as a property of an element with the
|
||||
imported name.
|
||||
|
||||
.. versionadded:: 0.4.0
|
||||
Added ``source_comments`` and ``source_map_filename`` parameters.
|
||||
|
||||
.. versionchanged:: 0.6.0
|
||||
The ``source_comments`` parameter becomes to take only :class:`bool`
|
||||
instead of :class:`str`.
|
||||
|
||||
.. deprecated:: 0.6.0
|
||||
Values like ``'none'``, ``'line_numbers'``, and ``'map'`` for
|
||||
the ``source_comments`` parameter are deprecated.
|
||||
|
||||
.. versionadded:: 0.7.0
|
||||
Added ``precision`` parameter.
|
||||
|
||||
.. versionadded:: 0.7.0
|
||||
Added ``custom_functions`` parameter.
|
||||
|
||||
.. versionadded:: 0.11.0
|
||||
``source_map_filename`` no longer implies ``source_comments``.
|
||||
|
||||
.. versionadded:: 0.17.0
|
||||
Added ``source_map_contents``, ``source_map_embed``,
|
||||
``omit_source_map_url``, and ``source_map_root`` parameters.
|
||||
|
||||
.. versionadded:: 0.18.0
|
||||
The importer callbacks can now take a second argument, the previously-
|
||||
resolved path, so that importers can do relative path resolution.
|
||||
|
||||
"""
|
||||
modes = set()
|
||||
for mode_name in MODES:
|
||||
if mode_name in kwargs:
|
||||
modes.add(mode_name)
|
||||
if not modes:
|
||||
raise TypeError('choose one at least in ' + and_join(MODES))
|
||||
elif len(modes) > 1:
|
||||
raise TypeError(
|
||||
and_join(modes) + ' are exclusive each other; '
|
||||
'cannot be used at a time',
|
||||
)
|
||||
precision = kwargs.pop('precision', 5)
|
||||
output_style = kwargs.pop('output_style', 'nested')
|
||||
if not isinstance(output_style, string_types):
|
||||
raise TypeError(
|
||||
'output_style must be a string, not ' +
|
||||
repr(output_style),
|
||||
)
|
||||
try:
|
||||
output_style = OUTPUT_STYLES[output_style]
|
||||
except KeyError:
|
||||
raise CompileError(
|
||||
'{} is unsupported output_style; choose one of {}'
|
||||
''.format(output_style, and_join(OUTPUT_STYLES)),
|
||||
)
|
||||
source_comments = kwargs.pop('source_comments', False)
|
||||
if source_comments in SOURCE_COMMENTS:
|
||||
if source_comments == 'none':
|
||||
deprecation_message = (
|
||||
'you can simply pass False to '
|
||||
"source_comments instead of 'none'"
|
||||
)
|
||||
source_comments = False
|
||||
elif source_comments in ('line_numbers', 'default'):
|
||||
deprecation_message = (
|
||||
'you can simply pass True to '
|
||||
"source_comments instead of " +
|
||||
repr(source_comments)
|
||||
)
|
||||
source_comments = True
|
||||
else:
|
||||
deprecation_message = (
|
||||
"you don't have to pass 'map' to "
|
||||
'source_comments but just need to '
|
||||
'specify source_map_filename'
|
||||
)
|
||||
source_comments = False
|
||||
warnings.warn(
|
||||
"values like 'none', 'line_numbers', and 'map' for "
|
||||
'the source_comments parameter are deprecated; ' +
|
||||
deprecation_message,
|
||||
FutureWarning,
|
||||
)
|
||||
if not isinstance(source_comments, bool):
|
||||
raise TypeError(
|
||||
'source_comments must be bool, not ' +
|
||||
repr(source_comments),
|
||||
)
|
||||
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
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')
|
||||
|
||||
source_map_contents = kwargs.pop('source_map_contents', False)
|
||||
source_map_embed = kwargs.pop('source_map_embed', False)
|
||||
omit_source_map_url = kwargs.pop('omit_source_map_url', False)
|
||||
source_map_root = kwargs.pop('source_map_root', None)
|
||||
|
||||
if isinstance(source_map_root, text_type):
|
||||
source_map_root = source_map_root.encode('utf-8')
|
||||
|
||||
# #208: cwd is always included in include paths
|
||||
include_paths = (os.getcwd(),)
|
||||
include_paths += tuple(kwargs.pop('include_paths', ()) or ())
|
||||
include_paths = os.pathsep.join(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_abc.Mapping):
|
||||
custom_functions = [
|
||||
SassFunction.from_lambda(name, lambda_)
|
||||
for name, lambda_ in custom_functions.items()
|
||||
]
|
||||
elif isinstance(
|
||||
custom_functions,
|
||||
(collections_abc.Set, collections_abc.Sequence),
|
||||
):
|
||||
custom_functions = [
|
||||
func if isinstance(func, SassFunction)
|
||||
else SassFunction.from_named_function(func)
|
||||
for func in custom_functions
|
||||
]
|
||||
else:
|
||||
raise TypeError(
|
||||
'custom_functions must be one of:\n'
|
||||
'- a set/sequence of {0.__module__}.{0.__name__} objects,\n'
|
||||
'- a mapping of function name strings to lambda functions,\n'
|
||||
'- a set/sequence of named functions,\n'
|
||||
'not {1!r}'.format(SassFunction, custom_functions),
|
||||
)
|
||||
|
||||
if kwargs.pop('custom_import_extensions', None) is not None:
|
||||
warnings.warn(
|
||||
'`custom_import_extensions` has no effect and will be removed in '
|
||||
'a future version.',
|
||||
FutureWarning,
|
||||
)
|
||||
|
||||
importers = _validate_importers(kwargs.pop('importers', None))
|
||||
|
||||
if 'string' in modes:
|
||||
string = kwargs.pop('string')
|
||||
if isinstance(string, text_type):
|
||||
string = string.encode('utf-8')
|
||||
indented = kwargs.pop('indented', False)
|
||||
if not isinstance(indented, bool):
|
||||
raise TypeError(
|
||||
'indented must be bool, not ' +
|
||||
repr(source_comments),
|
||||
)
|
||||
_check_no_remaining_kwargs(compile, kwargs)
|
||||
s, v = _sass.compile_string(
|
||||
string, output_style, source_comments, include_paths, precision,
|
||||
custom_functions, indented, importers,
|
||||
source_map_contents, source_map_embed, omit_source_map_url,
|
||||
source_map_root,
|
||||
)
|
||||
if s:
|
||||
return v.decode('utf-8')
|
||||
elif 'filename' in modes:
|
||||
filename = kwargs.pop('filename')
|
||||
if not isinstance(filename, string_types):
|
||||
raise TypeError('filename must be a string, not ' + repr(filename))
|
||||
elif not os.path.isfile(filename):
|
||||
raise IOError('{!r} seems not a file'.format(filename))
|
||||
elif isinstance(filename, text_type):
|
||||
filename = filename.encode(fs_encoding)
|
||||
_check_no_remaining_kwargs(compile, kwargs)
|
||||
s, v, source_map = _sass.compile_filename(
|
||||
filename, output_style, source_comments, include_paths, precision,
|
||||
source_map_filename, custom_functions, importers,
|
||||
output_filename_hint,
|
||||
source_map_contents, source_map_embed, omit_source_map_url,
|
||||
source_map_root,
|
||||
)
|
||||
if s:
|
||||
v = v.decode('utf-8')
|
||||
if source_map_filename:
|
||||
source_map = source_map.decode('utf-8')
|
||||
v = v, source_map
|
||||
return v
|
||||
elif 'dirname' in modes:
|
||||
try:
|
||||
search_path, output_path = kwargs.pop('dirname')
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'dirname must be a pair of (source_dir, '
|
||||
'output_dir)',
|
||||
)
|
||||
_check_no_remaining_kwargs(compile, kwargs)
|
||||
s, v = compile_dirname(
|
||||
search_path, output_path, output_style, source_comments,
|
||||
include_paths, precision, custom_functions, importers,
|
||||
source_map_contents, source_map_embed, omit_source_map_url,
|
||||
source_map_root,
|
||||
)
|
||||
if s:
|
||||
return
|
||||
else:
|
||||
raise TypeError('something went wrong')
|
||||
assert not s
|
||||
raise CompileError(v)
|
||||
|
||||
|
||||
def and_join(strings):
|
||||
"""Join the given ``strings`` by commas with last `' and '` conjunction.
|
||||
|
||||
>>> and_join(['Korea', 'Japan', 'China', 'Taiwan'])
|
||||
'Korea, Japan, China, and Taiwan'
|
||||
|
||||
:param strings: a list of words to join
|
||||
:type string: :class:`collections.abc.Sequence`
|
||||
:returns: a joined string
|
||||
:rtype: :class:`str`, :class:`basestring`
|
||||
|
||||
"""
|
||||
last = len(strings) - 1
|
||||
if last == 0:
|
||||
return strings[0]
|
||||
elif last < 0:
|
||||
return ''
|
||||
iterator = enumerate(strings)
|
||||
return ', '.join('and ' + s if i == last else s for i, s in iterator)
|
||||
|
||||
|
||||
"""
|
||||
This module provides datatypes to be used in custom sass functions.
|
||||
|
||||
The following mappings from sass types to python types are used:
|
||||
|
||||
SASS_NULL: ``None``
|
||||
SASS_BOOLEAN: ``True`` or ``False``
|
||||
SASS_STRING: class:`str`
|
||||
SASS_NUMBER: class:`SassNumber`
|
||||
SASS_COLOR: class:`SassColor`
|
||||
SASS_LIST: class:`SassList`
|
||||
SASS_MAP: class:`dict` or class:`SassMap`
|
||||
SASS_ERROR: class:`SassError`
|
||||
SASS_WARNING: class:`SassWarning`
|
||||
"""
|
||||
|
||||
|
||||
class SassNumber(collections.namedtuple('SassNumber', ('value', 'unit'))):
|
||||
|
||||
def __new__(cls, value, unit):
|
||||
value = float(value)
|
||||
if not isinstance(unit, text_type):
|
||||
unit = unit.decode('UTF-8')
|
||||
return super(SassNumber, cls).__new__(cls, value, unit)
|
||||
|
||||
|
||||
class SassColor(collections.namedtuple('SassColor', ('r', 'g', 'b', 'a'))):
|
||||
|
||||
def __new__(cls, r, g, b, a):
|
||||
r = float(r)
|
||||
g = float(g)
|
||||
b = float(b)
|
||||
a = float(a)
|
||||
return super(SassColor, cls).__new__(cls, r, g, b, a)
|
||||
|
||||
|
||||
SASS_SEPARATOR_COMMA = collections.namedtuple('SASS_SEPARATOR_COMMA', ())()
|
||||
SASS_SEPARATOR_SPACE = collections.namedtuple('SASS_SEPARATOR_SPACE', ())()
|
||||
SEPARATORS = frozenset((SASS_SEPARATOR_COMMA, SASS_SEPARATOR_SPACE))
|
||||
|
||||
|
||||
class SassList(
|
||||
collections.namedtuple(
|
||||
'SassList', ('items', 'separator', 'bracketed'),
|
||||
),
|
||||
):
|
||||
|
||||
def __new__(cls, items, separator, bracketed=False):
|
||||
items = tuple(items)
|
||||
assert separator in SEPARATORS, separator
|
||||
assert isinstance(bracketed, bool), bracketed
|
||||
return super(SassList, cls).__new__(cls, items, separator, bracketed)
|
||||
|
||||
|
||||
class SassError(collections.namedtuple('SassError', ('msg',))):
|
||||
|
||||
def __new__(cls, msg):
|
||||
if not isinstance(msg, text_type):
|
||||
msg = msg.decode('UTF-8')
|
||||
return super(SassError, cls).__new__(cls, msg)
|
||||
|
||||
|
||||
class SassWarning(collections.namedtuple('SassWarning', ('msg',))):
|
||||
|
||||
def __new__(cls, msg):
|
||||
if not isinstance(msg, text_type):
|
||||
msg = msg.decode('UTF-8')
|
||||
return super(SassWarning, cls).__new__(cls, msg)
|
||||
|
||||
|
||||
class SassMap(collections_abc.Mapping):
|
||||
"""Because sass maps can have mapping types as keys, we need an immutable
|
||||
hashable mapping type.
|
||||
|
||||
.. versionadded:: 0.7.0
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = '_dict', '_hash'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._dict = dict(*args, **kwargs)
|
||||
# An assertion that all things are hashable
|
||||
self._hash = hash(frozenset(self._dict.items()))
|
||||
|
||||
# Mapping interface
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._dict[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._dict)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dict)
|
||||
|
||||
# Our interface
|
||||
|
||||
def __repr__(self):
|
||||
return '{}({})'.format(type(self).__name__, frozenset(self.items()))
|
||||
|
||||
def __hash__(self):
|
||||
return self._hash
|
||||
|
||||
def _immutable(self, *_):
|
||||
raise TypeError('SassMaps are immutable.')
|
||||
|
||||
__setitem__ = __delitem__ = _immutable
|
|
@ -0,0 +1,15 @@
|
|||
import warnings
|
||||
|
||||
import pysassc
|
||||
|
||||
|
||||
def main(*args, **kwargs):
|
||||
warnings.warn(
|
||||
'The `sassc` entrypoint is deprecated, please use `pysassc`',
|
||||
FutureWarning,
|
||||
),
|
||||
return pysassc.main(*args, **kwargs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
|||
""":mod:`sassutils` --- Additional utilities related to Sass
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This package provides several additional utilities related to Sass
|
||||
which depends on libsass core (:mod:`sass` module).
|
||||
|
||||
"""
|
|
@ -0,0 +1,7 @@
|
|||
from six import PY2
|
||||
|
||||
|
||||
if PY2: # pragma: no cover (PY2)
|
||||
import collections as collections_abc # noqa: F401
|
||||
else: # pragma: no cover (PY3)
|
||||
import collections.abc as collections_abc # noqa: F401
|
|
@ -0,0 +1,303 @@
|
|||
""":mod:`sassutils.builder` --- Build the whole directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from six import string_types
|
||||
|
||||
from sass import compile
|
||||
from sassutils._compat import collections_abc
|
||||
|
||||
__all__ = 'SUFFIXES', 'SUFFIX_PATTERN', 'Manifest', 'build_directory'
|
||||
|
||||
|
||||
#: (:class:`frozenset`) The set of supported filename suffixes.
|
||||
SUFFIXES = frozenset(('sass', 'scss'))
|
||||
|
||||
#: (:class:`re.RegexObject`) The regular expression pattern which matches to
|
||||
#: filenames of supported :const:`SUFFIXES`.
|
||||
SUFFIX_PATTERN = re.compile(
|
||||
'[.](' + '|'.join(map(re.escape, sorted(SUFFIXES))) + ')$',
|
||||
)
|
||||
|
||||
|
||||
def build_directory(
|
||||
sass_path, css_path, output_style='nested',
|
||||
_root_sass=None, _root_css=None, strip_extension=False,
|
||||
):
|
||||
"""Compiles all Sass/SCSS files in ``path`` to CSS.
|
||||
|
||||
:param sass_path: the path of the directory which contains source files
|
||||
to compile
|
||||
:type sass_path: :class:`str`, :class:`basestring`
|
||||
:param css_path: the path of the directory compiled CSS files will go
|
||||
:type css_path: :class:`str`, :class:`basestring`
|
||||
:param output_style: an optional coding style of the compiled result.
|
||||
choose one of: ``'nested'`` (default), ``'expanded'``,
|
||||
``'compact'``, ``'compressed'``
|
||||
:type output_style: :class:`str`
|
||||
:returns: a dictionary of source filenames to compiled CSS filenames
|
||||
:rtype: :class:`collections.abc.Mapping`
|
||||
|
||||
.. versionadded:: 0.6.0
|
||||
The ``output_style`` parameter.
|
||||
|
||||
"""
|
||||
if _root_sass is None or _root_css is None:
|
||||
_root_sass = sass_path
|
||||
_root_css = css_path
|
||||
result = {}
|
||||
if not os.path.isdir(css_path):
|
||||
os.mkdir(css_path)
|
||||
for name in os.listdir(sass_path):
|
||||
sass_fullname = os.path.join(sass_path, name)
|
||||
if SUFFIX_PATTERN.search(name) and os.path.isfile(sass_fullname):
|
||||
if name[0] == '_':
|
||||
# Do not compile if it's partial
|
||||
continue
|
||||
if strip_extension:
|
||||
name, _ = os.path.splitext(name)
|
||||
css_fullname = os.path.join(css_path, name) + '.css'
|
||||
css = compile(
|
||||
filename=sass_fullname,
|
||||
output_style=output_style,
|
||||
include_paths=[_root_sass],
|
||||
)
|
||||
with io.open(
|
||||
css_fullname, 'w', encoding='utf-8', newline='',
|
||||
) as css_file:
|
||||
css_file.write(css)
|
||||
result[os.path.relpath(sass_fullname, _root_sass)] = \
|
||||
os.path.relpath(css_fullname, _root_css)
|
||||
elif os.path.isdir(sass_fullname):
|
||||
css_fullname = os.path.join(css_path, name)
|
||||
subresult = build_directory(
|
||||
sass_fullname, css_fullname,
|
||||
output_style=output_style,
|
||||
_root_sass=_root_sass,
|
||||
_root_css=_root_css,
|
||||
strip_extension=strip_extension,
|
||||
)
|
||||
result.update(subresult)
|
||||
return result
|
||||
|
||||
|
||||
class Manifest(object):
|
||||
"""Building manifest of Sass/SCSS.
|
||||
|
||||
:param sass_path: the path of the directory that contains Sass/SCSS
|
||||
source files
|
||||
:type sass_path: :class:`str`, :class:`basestring`
|
||||
:param css_path: the path of the directory to store compiled CSS
|
||||
files
|
||||
:type css_path: :class:`str`, :class:`basestring`
|
||||
:param strip_extension: whether to remove the original file extension
|
||||
:type strip_extension: :class:`bool`
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def normalize_manifests(cls, manifests):
|
||||
if manifests is None:
|
||||
manifests = {}
|
||||
elif isinstance(manifests, collections_abc.Mapping):
|
||||
manifests = dict(manifests)
|
||||
else:
|
||||
raise TypeError(
|
||||
'manifests must be a mapping object, not ' +
|
||||
repr(manifests),
|
||||
)
|
||||
for package_name, manifest in manifests.items():
|
||||
if not isinstance(package_name, string_types):
|
||||
raise TypeError(
|
||||
'manifest keys must be a string of package '
|
||||
'name, not ' + repr(package_name),
|
||||
)
|
||||
if isinstance(manifest, Manifest):
|
||||
continue
|
||||
elif isinstance(manifest, tuple):
|
||||
manifest = Manifest(*manifest)
|
||||
elif isinstance(manifest, collections_abc.Mapping):
|
||||
manifest = Manifest(**manifest)
|
||||
elif isinstance(manifest, string_types):
|
||||
manifest = Manifest(manifest)
|
||||
else:
|
||||
raise TypeError(
|
||||
'manifest values must be a sassutils.builder.Manifest, '
|
||||
'a pair of (sass_path, css_path), or a string of '
|
||||
'sass_path, not ' + repr(manifest),
|
||||
)
|
||||
manifests[package_name] = manifest
|
||||
return manifests
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sass_path,
|
||||
css_path=None,
|
||||
wsgi_path=None,
|
||||
strip_extension=None,
|
||||
):
|
||||
if not isinstance(sass_path, string_types):
|
||||
raise TypeError(
|
||||
'sass_path must be a string, not ' +
|
||||
repr(sass_path),
|
||||
)
|
||||
if css_path is None:
|
||||
css_path = sass_path
|
||||
elif not isinstance(css_path, string_types):
|
||||
raise TypeError(
|
||||
'css_path must be a string, not ' +
|
||||
repr(css_path),
|
||||
)
|
||||
if wsgi_path is None:
|
||||
wsgi_path = css_path
|
||||
elif not isinstance(wsgi_path, string_types):
|
||||
raise TypeError(
|
||||
'wsgi_path must be a string, not ' +
|
||||
repr(wsgi_path),
|
||||
)
|
||||
if strip_extension is None:
|
||||
warnings.warn(
|
||||
'`strip_extension` was not specified, defaulting to `False`.\n'
|
||||
'In the future, `strip_extension` will default to `True`.',
|
||||
FutureWarning,
|
||||
)
|
||||
strip_extension = False
|
||||
elif not isinstance(strip_extension, bool):
|
||||
raise TypeError(
|
||||
'strip_extension must be bool not {!r}'.format(
|
||||
strip_extension,
|
||||
),
|
||||
)
|
||||
self.sass_path = sass_path
|
||||
self.css_path = css_path
|
||||
self.wsgi_path = wsgi_path
|
||||
self.strip_extension = strip_extension
|
||||
|
||||
def resolve_filename(self, package_dir, filename):
|
||||
"""Gets a proper full relative path of Sass source and
|
||||
CSS source that will be generated, according to ``package_dir``
|
||||
and ``filename``.
|
||||
|
||||
:param package_dir: the path of package directory
|
||||
:type package_dir: :class:`str`, :class:`basestring`
|
||||
:param filename: the filename of Sass/SCSS source to compile
|
||||
:type filename: :class:`str`, :class:`basestring`
|
||||
:returns: a pair of (sass, css) path
|
||||
:rtype: :class:`tuple`
|
||||
|
||||
"""
|
||||
sass_path = os.path.join(package_dir, self.sass_path, filename)
|
||||
if self.strip_extension:
|
||||
filename, _ = os.path.splitext(filename)
|
||||
css_filename = filename + '.css'
|
||||
css_path = os.path.join(package_dir, self.css_path, css_filename)
|
||||
return sass_path, css_path
|
||||
|
||||
def unresolve_filename(self, package_dir, filename):
|
||||
"""Retrieves the probable source path from the output filename. Pass
|
||||
in a .css path to get out a .scss path.
|
||||
|
||||
:param package_dir: the path of the package directory
|
||||
:type package_dir: :class:`str`
|
||||
:param filename: the css filename
|
||||
:type filename: :class:`str`
|
||||
:returns: the scss filename
|
||||
:rtype: :class:`str`
|
||||
"""
|
||||
filename, _ = os.path.splitext(filename)
|
||||
if self.strip_extension:
|
||||
for ext in ('.scss', '.sass'):
|
||||
test_path = os.path.join(
|
||||
package_dir, self.sass_path, filename + ext,
|
||||
)
|
||||
if os.path.exists(test_path):
|
||||
return filename + ext
|
||||
else: # file not found, let it error with `.scss` extension
|
||||
return filename + '.scss'
|
||||
else:
|
||||
return filename
|
||||
|
||||
def build(self, package_dir, output_style='nested'):
|
||||
"""Builds the Sass/SCSS files in the specified :attr:`sass_path`.
|
||||
It finds :attr:`sass_path` and locates :attr:`css_path`
|
||||
as relative to the given ``package_dir``.
|
||||
|
||||
:param package_dir: the path of package directory
|
||||
:type package_dir: :class:`str`, :class:`basestring`
|
||||
:param output_style: an optional coding style of the compiled result.
|
||||
choose one of: ``'nested'`` (default),
|
||||
``'expanded'``, ``'compact'``, ``'compressed'``
|
||||
:type output_style: :class:`str`
|
||||
:returns: the set of compiled CSS filenames
|
||||
:rtype: :class:`frozenset`
|
||||
|
||||
.. versionadded:: 0.6.0
|
||||
The ``output_style`` parameter.
|
||||
|
||||
"""
|
||||
sass_path = os.path.join(package_dir, self.sass_path)
|
||||
css_path = os.path.join(package_dir, self.css_path)
|
||||
css_files = build_directory(
|
||||
sass_path, css_path,
|
||||
output_style=output_style,
|
||||
strip_extension=self.strip_extension,
|
||||
).values()
|
||||
return frozenset(
|
||||
os.path.join(self.css_path, filename)
|
||||
for filename in css_files
|
||||
)
|
||||
|
||||
def build_one(self, package_dir, filename, source_map=False):
|
||||
"""Builds one Sass/SCSS file.
|
||||
|
||||
:param package_dir: the path of package directory
|
||||
:type package_dir: :class:`str`, :class:`basestring`
|
||||
:param filename: the filename of Sass/SCSS source to compile
|
||||
:type filename: :class:`str`, :class:`basestring`
|
||||
:param source_map: whether to use source maps. if :const:`True`
|
||||
it also write a source map to a ``filename``
|
||||
followed by :file:`.map` suffix.
|
||||
default is :const:`False`
|
||||
:type source_map: :class:`bool`
|
||||
:returns: the filename of compiled CSS
|
||||
:rtype: :class:`str`, :class:`basestring`
|
||||
|
||||
.. versionadded:: 0.4.0
|
||||
Added optional ``source_map`` parameter.
|
||||
|
||||
"""
|
||||
sass_filename, css_filename = self.resolve_filename(
|
||||
package_dir, filename,
|
||||
)
|
||||
root_path = os.path.join(package_dir, self.sass_path)
|
||||
css_path = os.path.join(package_dir, self.css_path, css_filename)
|
||||
if source_map:
|
||||
source_map_path = css_filename + '.map'
|
||||
css, source_map = compile(
|
||||
filename=sass_filename,
|
||||
include_paths=[root_path],
|
||||
source_map_filename=source_map_path, # FIXME
|
||||
output_filename_hint=css_path,
|
||||
)
|
||||
else:
|
||||
css = compile(filename=sass_filename, include_paths=[root_path])
|
||||
source_map_path = None
|
||||
source_map = None
|
||||
css_folder = os.path.dirname(css_path)
|
||||
if not os.path.exists(css_folder):
|
||||
os.makedirs(css_folder)
|
||||
with io.open(css_path, 'w', encoding='utf-8', newline='') as f:
|
||||
f.write(css)
|
||||
if source_map:
|
||||
# Source maps are JSON, and JSON has to be UTF-8 encoded
|
||||
with io.open(
|
||||
source_map_path, 'w', encoding='utf-8', newline='',
|
||||
) as f:
|
||||
f.write(source_map)
|
||||
return css_filename
|
|
@ -0,0 +1,200 @@
|
|||
""":mod:`sassutils.distutils` --- :mod:`setuptools`/:mod:`distutils` integrat\
|
||||
ion
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|
||||
~~~
|
||||
|
||||
This module provides extensions (and some magical monkey-patches, sorry)
|
||||
of the standard :mod:`distutils` and :mod:`setuptools` (now it's named
|
||||
Distribute) for libsass.
|
||||
|
||||
To use this, add ``libsass`` into ``setup_requires`` (not ``install_requires``)
|
||||
option of the :file:`setup.py` script::
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
# ...,
|
||||
setup_requires=['libsass >= 0.6.0']
|
||||
)
|
||||
|
||||
It will adds :class:`build_sass` command to the :file:`setup.py` script:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
$ python setup.py build_sass
|
||||
|
||||
This commands builds Sass/SCSS files to compiled CSS files of the project
|
||||
and makes the package archive (made by :class:`~distutils.command.sdist.sdist`,
|
||||
:class:`~distutils.command.bdist.bdist`, and so on) to include these compiled
|
||||
CSS files.
|
||||
|
||||
To set the directory of Sass/SCSS source files and the directory to
|
||||
store compiled CSS files, specify ``sass_manifests`` option::
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='YourPackage',
|
||||
packages=find_packages(),
|
||||
sass_manifests={
|
||||
'your.webapp': ('static/sass', 'static/css')
|
||||
},
|
||||
setup_requires=['libsass >= 0.6.0']
|
||||
)
|
||||
|
||||
The option should be a mapping of package names to pairs of paths, e.g.::
|
||||
|
||||
{
|
||||
'package': ('static/sass', 'static/css'),
|
||||
'package.name': ('static/scss', 'static')
|
||||
}
|
||||
|
||||
The option can also be a mapping of package names to manifest dictionaries::
|
||||
|
||||
{
|
||||
'package': {
|
||||
'sass_path': 'static/sass',
|
||||
'css_path': 'static/css',
|
||||
'strip_extension': True,
|
||||
},
|
||||
}
|
||||
|
||||
.. versionadded:: 0.15.0
|
||||
Added ``strip_extension`` so ``a.scss`` is compiled to ``a.css`` instead
|
||||
of ``a.scss.css``. This option will default to ``True`` in the future.
|
||||
|
||||
.. versionadded:: 0.6.0
|
||||
Added ``--output-style``/``-s`` option to :class:`build_sass` command.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import distutils.errors
|
||||
import distutils.log
|
||||
import distutils.util
|
||||
import functools
|
||||
import os.path
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools.command.sdist import sdist
|
||||
|
||||
from sass import OUTPUT_STYLES
|
||||
from .builder import Manifest
|
||||
|
||||
__all__ = 'build_sass', 'validate_manifests'
|
||||
|
||||
|
||||
def validate_manifests(dist, attr, value):
|
||||
"""Verifies that ``value`` is an expected mapping of package to
|
||||
:class:`sassutils.builder.Manifest`.
|
||||
|
||||
"""
|
||||
try:
|
||||
Manifest.normalize_manifests(value)
|
||||
except TypeError:
|
||||
raise distutils.errors.DistutilsSetupError(
|
||||
attr + "must be a mapping object like: {'package.name': "
|
||||
"sassutils.distutils.Manifest('sass/path')}, or as shorten form: "
|
||||
"{'package.name': ('sass/path', 'css/path'}), not " +
|
||||
repr(value),
|
||||
)
|
||||
|
||||
|
||||
class build_sass(Command):
|
||||
"""Builds Sass/SCSS files to CSS files."""
|
||||
|
||||
description = __doc__
|
||||
user_options = [
|
||||
(
|
||||
'output-style=', 's',
|
||||
'Coding style of the compiled result. Choose one of ' +
|
||||
', '.join(OUTPUT_STYLES),
|
||||
),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.package_dir = None
|
||||
self.output_style = 'nested'
|
||||
|
||||
def finalize_options(self):
|
||||
self.package_dir = {}
|
||||
if self.distribution.package_dir:
|
||||
self.package_dir = {}
|
||||
for name, path in self.distribution.package_dir.items():
|
||||
self.package_dir[name] = distutils.util.convert_path(path)
|
||||
|
||||
def run(self):
|
||||
manifests = self.distribution.sass_manifests
|
||||
manifests = Manifest.normalize_manifests(manifests)
|
||||
self.distribution.sass_manifests = manifests
|
||||
package_data = self.distribution.package_data
|
||||
data_files = self.distribution.data_files or []
|
||||
for package_name, manifest in manifests.items():
|
||||
package_dir = self.get_package_dir(package_name)
|
||||
distutils.log.info("building '%s' sass", package_name)
|
||||
css_files = manifest.build(
|
||||
package_dir,
|
||||
output_style=self.output_style,
|
||||
)
|
||||
map(distutils.log.info, css_files)
|
||||
package_data.setdefault(package_name, []).extend(css_files)
|
||||
data_files.append(
|
||||
(
|
||||
package_dir,
|
||||
[os.path.join(package_dir, f) for f in css_files],
|
||||
),
|
||||
)
|
||||
self.distribution.package_data = package_data
|
||||
self.distribution.data_files = data_files
|
||||
self.distribution.has_data_files = lambda: True
|
||||
# See the below monkey patch (end of this source code).
|
||||
self.distribution.compiled_sass_files = data_files
|
||||
|
||||
def get_package_dir(self, package):
|
||||
"""Returns the directory, relative to the top of the source
|
||||
distribution, where package ``package`` should be found
|
||||
(at least according to the :attr:`package_dir` option, if any).
|
||||
|
||||
Copied from :meth:`distutils.command.build_py.get_package_dir()`
|
||||
method.
|
||||
|
||||
"""
|
||||
path = package.split('.')
|
||||
if not self.package_dir:
|
||||
if path:
|
||||
return os.path.join(*path)
|
||||
return ''
|
||||
tail = []
|
||||
while path:
|
||||
try:
|
||||
pdir = self.package_dir['.'.join(path)]
|
||||
except KeyError:
|
||||
tail.insert(0, path[-1])
|
||||
del path[-1]
|
||||
else:
|
||||
tail.insert(0, pdir)
|
||||
return os.path.join(*tail)
|
||||
else:
|
||||
pdir = self.package_dir.get('')
|
||||
if pdir is not None:
|
||||
tail.insert(0, pdir)
|
||||
if tail:
|
||||
return os.path.join(*tail)
|
||||
return ''
|
||||
|
||||
|
||||
# Does monkey-patching the setuptools.command.sdist.sdist.check_readme()
|
||||
# method to include compiled Sass files as data files.
|
||||
if not hasattr(sdist, '_wrapped_check_readme'):
|
||||
@functools.wraps(sdist.check_readme)
|
||||
def check_readme(self):
|
||||
try:
|
||||
files = self.distribution.compiled_sass_files
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for _, css_files in files:
|
||||
self.filelist.extend(css_files)
|
||||
return self._wrapped_check_readme()
|
||||
sdist._wrapped_check_readme = sdist.check_readme
|
||||
sdist.check_readme = check_readme
|
|
@ -0,0 +1,175 @@
|
|||
""":mod:`sassutils.wsgi` --- WSGI middleware for development purpose
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from pkg_resources import resource_filename
|
||||
|
||||
from sass import CompileError
|
||||
from sassutils._compat import collections_abc
|
||||
from .builder import Manifest
|
||||
|
||||
__all__ = 'SassMiddleware',
|
||||
|
||||
|
||||
class SassMiddleware(object):
|
||||
r"""WSGI middleware for development purpose. Every time a CSS file has
|
||||
requested it finds a matched Sass/SCSS source file and then compiled
|
||||
it into CSS.
|
||||
|
||||
It shows syntax errors in three ways:
|
||||
|
||||
Heading comment
|
||||
The result CSS includes detailed error message in the heading
|
||||
CSS comment e.g.:
|
||||
|
||||
.. code-block:: css
|
||||
|
||||
/*
|
||||
Error: invalid property name
|
||||
*/
|
||||
|
||||
Red text in ``body:before``
|
||||
The result CSS draws detailed error message in ``:before``
|
||||
pseudo-class of ``body`` element e.g.:
|
||||
|
||||
.. code-block:: css
|
||||
|
||||
body:before {
|
||||
content: 'Error: invalid property name';
|
||||
color: maroon;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
In most cases you could be aware of syntax error by refreshing your
|
||||
working document because it will removes all other styles and leaves
|
||||
only a red text.
|
||||
|
||||
:mod:`logging`
|
||||
It logs syntax errors if exist during compilation to
|
||||
``sassutils.wsgi.SassMiddleware`` logger with level ``ERROR``.
|
||||
|
||||
To enable this::
|
||||
|
||||
from logging import Formatter, StreamHandler, getLogger
|
||||
logger = getLogger('sassutils.wsgi.SassMiddleware')
|
||||
handler = StreamHandler(level=logging.ERROR)
|
||||
formatter = Formatter(fmt='*' * 80 + '\n%(message)s\n' + '*' * 80)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
Or simply::
|
||||
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
|
||||
:param app: the WSGI application to wrap
|
||||
:type app: :class:`collections.abc.Callable`
|
||||
:param manifests: build settings. the same format to
|
||||
:file:`setup.py` script's ``sass_manifests``
|
||||
option
|
||||
:type manifests: :class:`collections.abc.Mapping`
|
||||
:param package_dir: optional mapping of package names to directories.
|
||||
the same format to :file:`setup.py` script's
|
||||
``package_dir`` option
|
||||
:type package_dir: :class:`collections.abc.Mapping`
|
||||
|
||||
.. versionchanged:: 0.4.0
|
||||
It creates also source map files with filenames followed by
|
||||
:file:`.map` suffix.
|
||||
|
||||
.. versionadded:: 0.8.0
|
||||
It logs syntax errors if exist during compilation to
|
||||
``sassutils.wsgi.SassMiddleware`` logger with level ``ERROR``.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, app, manifests, package_dir={},
|
||||
error_status='200 OK',
|
||||
):
|
||||
if not callable(app):
|
||||
raise TypeError(
|
||||
'app must be a WSGI-compliant callable object, '
|
||||
'not ' + repr(app),
|
||||
)
|
||||
self.app = app
|
||||
self.manifests = Manifest.normalize_manifests(manifests)
|
||||
if not isinstance(package_dir, collections_abc.Mapping):
|
||||
raise TypeError(
|
||||
'package_dir must be a mapping object, not ' +
|
||||
repr(package_dir),
|
||||
)
|
||||
self.error_status = error_status
|
||||
self.package_dir = dict(package_dir)
|
||||
for package_name in self.manifests:
|
||||
if package_name in self.package_dir:
|
||||
continue
|
||||
path = resource_filename(package_name, '')
|
||||
self.package_dir[package_name] = path
|
||||
self.paths = []
|
||||
for package_name, manifest in self.manifests.items():
|
||||
wsgi_path = manifest.wsgi_path
|
||||
if not wsgi_path.startswith('/'):
|
||||
wsgi_path = '/' + wsgi_path
|
||||
if not wsgi_path.endswith('/'):
|
||||
wsgi_path += '/'
|
||||
package_dir = self.package_dir[package_name]
|
||||
self.paths.append((wsgi_path, package_dir, manifest))
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
path = environ.get('PATH_INFO', '/')
|
||||
if path.endswith('.css'):
|
||||
for prefix, package_dir, manifest in self.paths:
|
||||
if not path.startswith(prefix):
|
||||
continue
|
||||
css_filename = path[len(prefix):]
|
||||
sass_filename = manifest.unresolve_filename(
|
||||
package_dir, css_filename,
|
||||
)
|
||||
try:
|
||||
result = manifest.build_one(
|
||||
package_dir,
|
||||
sass_filename,
|
||||
source_map=True,
|
||||
)
|
||||
except (IOError, OSError):
|
||||
break
|
||||
except CompileError as e:
|
||||
logger = logging.getLogger(__name__ + '.SassMiddleware')
|
||||
logger.error(str(e))
|
||||
start_response(
|
||||
self.error_status,
|
||||
[('Content-Type', 'text/css; charset=utf-8')],
|
||||
)
|
||||
return [
|
||||
b'/*\n', str(e).encode('utf-8'), b'\n*/\n\n',
|
||||
b'body:before { content: ',
|
||||
self.quote_css_string(str(e)).encode('utf-8'),
|
||||
b'; color: maroon; background-color: white',
|
||||
b'; white-space: pre-wrap; display: block',
|
||||
b'; font-family: "Courier New", monospace'
|
||||
b'; user-select: text; }',
|
||||
]
|
||||
|
||||
def read_file(path):
|
||||
with open(path, 'rb') as in_:
|
||||
while 1:
|
||||
chunk = in_.read(4096)
|
||||
if chunk:
|
||||
yield chunk
|
||||
else:
|
||||
break
|
||||
start_response('200 OK', [('Content-Type', 'text/css')])
|
||||
return read_file(os.path.join(package_dir, result))
|
||||
return self.app(environ, start_response)
|
||||
|
||||
@staticmethod
|
||||
def quote_css_string(s):
|
||||
"""Quotes a string as CSS string literal."""
|
||||
return "'" + ''.join('\\%06x' % ord(c) for c in s) + "'"
|
|
@ -0,0 +1,9 @@
|
|||
[aliases]
|
||||
upload_doc = build_sphinx upload_doc
|
||||
release = sdist upload build_sphinx upload_doc
|
||||
|
||||
[flake8]
|
||||
exclude = .tox,build,dist,docs,ez_setup.py
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
|
@ -0,0 +1,298 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
import atexit
|
||||
import distutils.cmd
|
||||
import distutils.log
|
||||
import distutils.sysconfig
|
||||
import os.path
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from setuptools import Extension, setup
|
||||
|
||||
MACOS_FLAG = ['-mmacosx-version-min=10.7']
|
||||
FLAGS_POSIX = [
|
||||
'-fPIC', '-std=gnu++0x', '-Wall', '-Wno-parentheses', '-Werror=switch',
|
||||
]
|
||||
FLAGS_CLANG = ['-c', '-O3'] + FLAGS_POSIX + ['-stdlib=libc++']
|
||||
LFLAGS_POSIX = ['-fPIC', '-lstdc++']
|
||||
LFLAGS_CLANG = ['-fPIC', '-stdlib=libc++']
|
||||
|
||||
sources = ['_sass.c']
|
||||
headers = []
|
||||
|
||||
if sys.platform == 'win32':
|
||||
extra_compile_args = ['/Od', '/EHsc', '/MT']
|
||||
extra_link_args = []
|
||||
elif platform.system() == 'Darwin':
|
||||
extra_compile_args = FLAGS_CLANG + MACOS_FLAG
|
||||
extra_link_args = LFLAGS_CLANG + MACOS_FLAG
|
||||
elif platform.system() in {'FreeBSD', 'OpenBSD'}:
|
||||
extra_compile_args = FLAGS_CLANG
|
||||
extra_link_args = LFLAGS_CLANG
|
||||
else:
|
||||
extra_compile_args = FLAGS_POSIX
|
||||
extra_link_args = LFLAGS_POSIX
|
||||
|
||||
if platform.system() in {'Darwin', 'FreeBSD', 'OpenBSD'}:
|
||||
os.environ.setdefault('CC', 'clang')
|
||||
os.environ.setdefault('CXX', 'clang++')
|
||||
orig_customize_compiler = distutils.sysconfig.customize_compiler
|
||||
|
||||
def customize_compiler(compiler):
|
||||
orig_customize_compiler(compiler)
|
||||
compiler.compiler[0] = os.environ['CC']
|
||||
compiler.compiler_so[0] = os.environ['CXX']
|
||||
compiler.compiler_cxx[0] = os.environ['CXX']
|
||||
compiler.linker_so[0] = os.environ['CXX']
|
||||
return compiler
|
||||
distutils.sysconfig.customize_compiler = customize_compiler
|
||||
|
||||
if os.environ.get('SYSTEM_SASS', False):
|
||||
libraries = ['sass']
|
||||
include_dirs = []
|
||||
else:
|
||||
LIBSASS_SOURCE_DIR = os.path.join('libsass', 'src')
|
||||
|
||||
if (
|
||||
not os.path.isfile(os.path.join('libsass', 'Makefile')) and
|
||||
os.path.isdir('.git')
|
||||
):
|
||||
print(file=sys.stderr)
|
||||
print('Missing the libsass sumbodule. Try:', file=sys.stderr)
|
||||
print(' git submodule update --init', file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
# Determine the libsass version from the git checkout
|
||||
if os.path.exists(os.path.join('libsass', '.git')):
|
||||
out = subprocess.check_output((
|
||||
'git', '-C', 'libsass', 'describe',
|
||||
'--abbrev=4', '--dirty', '--always', '--tags',
|
||||
))
|
||||
with open('.libsass-upstream-version', 'wb') as libsass_version_file:
|
||||
libsass_version_file.write(out)
|
||||
|
||||
# The version file should always exist at this point
|
||||
with open('.libsass-upstream-version', 'rb') as libsass_version_file:
|
||||
libsass_version = libsass_version_file.read().decode('UTF-8').strip()
|
||||
if sys.platform == 'win32':
|
||||
# This looks wrong, but is required for some reason :(
|
||||
define = r'/DLIBSASS_VERSION="\"{}\""'.format(libsass_version)
|
||||
else:
|
||||
define = '-DLIBSASS_VERSION="{}"'.format(libsass_version)
|
||||
|
||||
for directory in (
|
||||
os.path.join('libsass', 'src'),
|
||||
os.path.join('libsass', 'include'),
|
||||
):
|
||||
for pth, _, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
filename = os.path.join(pth, filename)
|
||||
if filename.endswith(('.c', '.cpp')):
|
||||
sources.append(filename)
|
||||
elif filename.endswith('.h'):
|
||||
headers.append(filename)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from distutils.msvc9compiler import get_build_version
|
||||
vscomntools_env = 'VS{}{}COMNTOOLS'.format(
|
||||
int(get_build_version()),
|
||||
int(get_build_version() * 10) % 10,
|
||||
)
|
||||
try:
|
||||
os.environ[vscomntools_env] = os.environ['VS140COMNTOOLS']
|
||||
except KeyError:
|
||||
distutils.log.warn(
|
||||
'You probably need Visual Studio 2015 (14.0) '
|
||||
'or higher',
|
||||
)
|
||||
from distutils import msvccompiler, msvc9compiler
|
||||
if msvccompiler.get_build_version() < 14.0:
|
||||
msvccompiler.get_build_version = lambda: 14.0
|
||||
if get_build_version() < 14.0:
|
||||
msvc9compiler.get_build_version = lambda: 14.0
|
||||
msvc9compiler.VERSION = 14.0
|
||||
elif platform.system() in {'Darwin', 'FreeBSD', 'OpenBSD'}:
|
||||
# Dirty workaround to avoid link error...
|
||||
# Python distutils doesn't provide any way
|
||||
# to configure different flags for each cc and c++.
|
||||
cencode_path = os.path.join(LIBSASS_SOURCE_DIR, 'cencode.c')
|
||||
cencode_body = ''
|
||||
with open(cencode_path) as f:
|
||||
cencode_body = f.read()
|
||||
with open(cencode_path, 'w') as f:
|
||||
f.write(
|
||||
'#ifdef __cplusplus\n'
|
||||
'extern "C" {\n'
|
||||
'#endif\n',
|
||||
)
|
||||
f.write(cencode_body)
|
||||
f.write(
|
||||
'#ifdef __cplusplus\n'
|
||||
'}\n'
|
||||
'#endif\n',
|
||||
)
|
||||
|
||||
@atexit.register
|
||||
def restore_cencode():
|
||||
if os.path.isfile(cencode_path):
|
||||
with open(cencode_path, 'w') as f:
|
||||
f.write(cencode_body)
|
||||
|
||||
libraries = []
|
||||
include_dirs = [os.path.join('.', 'libsass', 'include')]
|
||||
extra_compile_args.append(define)
|
||||
|
||||
# Py_LIMITED_API does not work for pypy
|
||||
# https://foss.heptapod.net/pypy/pypy/issues/3173
|
||||
if not hasattr(sys, 'pypy_version_info'):
|
||||
py_limited_api = True
|
||||
define_macros = [('Py_LIMITED_API', None)]
|
||||
else:
|
||||
py_limited_api = False
|
||||
define_macros = []
|
||||
|
||||
sass_extension = Extension(
|
||||
'_sass',
|
||||
sorted(sources),
|
||||
include_dirs=include_dirs,
|
||||
depends=headers,
|
||||
extra_compile_args=extra_compile_args,
|
||||
extra_link_args=extra_link_args,
|
||||
libraries=libraries,
|
||||
py_limited_api=py_limited_api,
|
||||
define_macros=define_macros,
|
||||
)
|
||||
|
||||
|
||||
def version(sass_filename='sass.py'):
|
||||
with open(sass_filename) as f:
|
||||
tree = ast.parse(f.read(), sass_filename)
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign) and len(node.targets) == 1:
|
||||
target, = node.targets
|
||||
if isinstance(target, ast.Name) and target.id == '__version__':
|
||||
return node.value.s
|
||||
|
||||
|
||||
def readme():
|
||||
try:
|
||||
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f:
|
||||
return f.read()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
class upload_doc(distutils.cmd.Command):
|
||||
"""Uploads the documentation to GitHub pages."""
|
||||
|
||||
description = __doc__
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
if sys.version_info < (3,):
|
||||
raise SystemExit('upload_doc must be run with python 3')
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
path = tempfile.mkdtemp()
|
||||
build = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
'build', 'sphinx', 'html',
|
||||
)
|
||||
os.chdir(path)
|
||||
os.system(
|
||||
'git clone -b gh-pages --depth 5 '
|
||||
'git@github.com:sass/libsass-python.git .',
|
||||
)
|
||||
os.system('git rm -r .')
|
||||
os.system('touch .nojekyll')
|
||||
os.system('cp -r ' + build + '/* .')
|
||||
os.system('git stage .')
|
||||
os.system('git commit -a -m "Documentation updated."')
|
||||
os.system('git push origin gh-pages')
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
cmdclass = {'upload_doc': upload_doc}
|
||||
|
||||
if sys.version_info >= (3,) and platform.python_implementation() == 'CPython':
|
||||
try:
|
||||
import wheel.bdist_wheel
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
|
||||
def finalize_options(self):
|
||||
self.py_limited_api = 'cp3{}'.format(sys.version_info[1])
|
||||
super().finalize_options()
|
||||
|
||||
cmdclass['bdist_wheel'] = bdist_wheel
|
||||
|
||||
|
||||
setup(
|
||||
name='libsass',
|
||||
description='Sass for Python: '
|
||||
'A straightforward binding of libsass for Python.',
|
||||
long_description=readme(),
|
||||
version=version(),
|
||||
ext_modules=[sass_extension],
|
||||
packages=['sassutils'],
|
||||
py_modules=['pysassc', 'sass', 'sassc', 'sasstests'],
|
||||
package_data={
|
||||
'': [
|
||||
'README.rst',
|
||||
'test/*.sass',
|
||||
],
|
||||
},
|
||||
license='MIT License',
|
||||
author='Hong Minhee',
|
||||
author_email='minhee' '@' 'dahlia.kr',
|
||||
url='https://sass.github.io/libsass-python/',
|
||||
download_url='https://github.com/sass/libsass-python/releases',
|
||||
entry_points={
|
||||
'distutils.commands': [
|
||||
'build_sass = sassutils.distutils:build_sass',
|
||||
],
|
||||
'distutils.setup_keywords': [
|
||||
'sass_manifests = sassutils.distutils:validate_manifests',
|
||||
],
|
||||
'console_scripts': [
|
||||
['pysassc = pysassc:main'],
|
||||
# TODO: remove `sassc` entry (#134)
|
||||
['sassc = sassc:main'],
|
||||
],
|
||||
},
|
||||
install_requires=['six'],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: C',
|
||||
'Programming Language :: C++',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Programming Language :: Python :: Implementation :: Stackless',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Code Generators',
|
||||
'Topic :: Software Development :: Compilers',
|
||||
],
|
||||
cmdclass=cmdclass,
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
$test-variable : true !default;
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
@mixin mx {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
body {
|
||||
@include mx;
|
||||
a {
|
||||
color: blue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
b {
|
||||
i {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
@import 'a.scss';
|
||||
|
||||
h1 {
|
||||
a {
|
||||
color: green;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
@mixin mx {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
body {
|
||||
@include mx;
|
||||
a {
|
||||
font: '나눔고딕', sans-serif;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
@import "f";
|
||||
@import "subdir/sub";
|
|
@ -0,0 +1,9 @@
|
|||
$font-stack: Helvetica, sans-serif;
|
||||
$primary-color: #333;
|
||||
$variabile: 5 / 3 * 6 / 7;
|
||||
|
||||
body {
|
||||
font: 100% $font-stack;
|
||||
color: $primary-color;
|
||||
height: $variabile;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
a
|
||||
b
|
||||
color: blue
|
|
@ -0,0 +1,5 @@
|
|||
@if $test-variable == true {
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
body {
|
||||
p { color: blue; }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
from setuptools import setup
|
||||
|
||||
|
||||
setup(
|
||||
name='testpkg',
|
||||
packages=['testpkg'],
|
||||
sass_manifests={
|
||||
'testpkg': ('static/scss', 'static/css'),
|
||||
},
|
||||
setup_requires=['libsass'],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
NOTE: Do not delete this file; it's for preserving the directory in Git.
|
|
@ -0,0 +1,8 @@
|
|||
p {
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
b {
|
||||
color: blue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
[tox]
|
||||
envlist = pypy,pypy3,py27,py36,py37,py38,py39,pre-commit
|
||||
|
||||
[testenv]
|
||||
usedevelop = true
|
||||
deps = -rrequirements-dev.txt
|
||||
setenv = PWD={toxinidir}
|
||||
commands =
|
||||
coverage erase
|
||||
coverage run -m pytest sasstests.py
|
||||
coverage combine
|
||||
coverage report
|
||||
|
||||
[testenv:pre-commit]
|
||||
skip_install = true
|
||||
deps = pre-commit
|
||||
commands = pre-commit run --all-files --show-diff-on-failure
|
Loading…
Reference in New Issue