├── testpkg
├── testpkg
│ ├── __init__.py
│ └── static
│ │ ├── css
│ │ └── README
│ │ └── scss
│ │ └── a.scss
└── setup.py
├── test
├── _f.scss
├── h.sass
├── e.scss
├── b.scss
├── subdir
│ ├── recur.scss
│ └── _sub.scss
├── c.scss
├── a.scss
├── d.scss
└── g.scss
├── docs
├── sass.rst
├── sassutils
│ ├── wsgi.rst
│ ├── builder.rst
│ └── distutils.rst
├── pysassc.rst
├── sassutils.rst
├── index.rst
├── make.bat
├── frameworks
│ └── flask.rst
├── Makefile
├── conf.py
└── changes.rst
├── .ackrc
├── .gitmodules
├── requirements-dev.txt
├── .gitignore
├── MANIFEST.in
├── sassutils
├── __init__.py
├── wsgi.py
├── distutils.py
└── builder.py
├── tox.ini
├── CODE_OF_CONDUCT.md
├── .coveragerc
├── .github
└── workflows
│ └── main.yml
├── LICENSE
├── .pre-commit-config.yaml
├── bin
└── build-manylinux-wheels
├── setup.cfg
├── CONTRIBUTING.rst
├── README.rst
├── setup.py
├── pysassc.py
├── _sass.c
└── sass.py
/testpkg/testpkg/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/_f.scss:
--------------------------------------------------------------------------------
1 | $test-variable : true !default;
2 |
--------------------------------------------------------------------------------
/test/h.sass:
--------------------------------------------------------------------------------
1 | a
2 | b
3 | color: blue
4 |
--------------------------------------------------------------------------------
/docs/sass.rst:
--------------------------------------------------------------------------------
1 | .. automodule:: sass
2 | :members:
3 |
--------------------------------------------------------------------------------
/test/e.scss:
--------------------------------------------------------------------------------
1 | @import "f";
2 | @import "subdir/sub";
3 |
--------------------------------------------------------------------------------
/test/b.scss:
--------------------------------------------------------------------------------
1 | b {
2 | i {
3 | font-size: 20px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/subdir/recur.scss:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | p { color: blue; }
4 | }
5 |
--------------------------------------------------------------------------------
/docs/sassutils/wsgi.rst:
--------------------------------------------------------------------------------
1 |
2 | .. automodule:: sassutils.wsgi
3 | :members:
4 |
--------------------------------------------------------------------------------
/.ackrc:
--------------------------------------------------------------------------------
1 | --ignore-dir=.tox
2 | --ignore-dir=build
3 | --ignore-dir=libsass.egg-info
4 |
--------------------------------------------------------------------------------
/docs/sassutils/builder.rst:
--------------------------------------------------------------------------------
1 |
2 | .. automodule:: sassutils.builder
3 | :members:
4 |
--------------------------------------------------------------------------------
/docs/sassutils/distutils.rst:
--------------------------------------------------------------------------------
1 |
2 | .. automodule:: sassutils.distutils
3 | :members:
4 |
--------------------------------------------------------------------------------
/test/c.scss:
--------------------------------------------------------------------------------
1 | @import 'a.scss';
2 |
3 | h1 {
4 | a {
5 | color: green;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libsass"]
2 | path = libsass
3 | url = https://github.com/sass/libsass
4 |
--------------------------------------------------------------------------------
/docs/pysassc.rst:
--------------------------------------------------------------------------------
1 |
2 | .. program:: pysassc
3 |
4 | .. automodule:: pysassc
5 | :members:
6 |
--------------------------------------------------------------------------------
/test/subdir/_sub.scss:
--------------------------------------------------------------------------------
1 | @if $test-variable == true {
2 | a {
3 | color: red;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/testpkg/testpkg/static/css/README:
--------------------------------------------------------------------------------
1 | NOTE: Do not delete this file; it's for preserving the directory in Git.
2 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | coverage
2 | coverage-enable-subprocess
3 | pre-commit
4 | pytest
5 | setuptools
6 | werkzeug>=0.9
7 |
--------------------------------------------------------------------------------
/testpkg/testpkg/static/scss/a.scss:
--------------------------------------------------------------------------------
1 | p {
2 | a {
3 | color: red;
4 | }
5 | b {
6 | color: blue;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/a.scss:
--------------------------------------------------------------------------------
1 |
2 | @mixin mx {
3 | background-color: green;
4 | }
5 |
6 | body {
7 | @include mx;
8 | a {
9 | color: blue;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/d.scss:
--------------------------------------------------------------------------------
1 |
2 | @mixin mx {
3 | background-color: green;
4 | }
5 |
6 | body {
7 | @include mx;
8 | a {
9 | font: '나눔고딕', sans-serif;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/docs/sassutils.rst:
--------------------------------------------------------------------------------
1 |
2 | .. automodule:: sassutils
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | sassutils/builder
8 | sassutils/distutils
9 | sassutils/wsgi
10 |
--------------------------------------------------------------------------------
/test/g.scss:
--------------------------------------------------------------------------------
1 | $font-stack: Helvetica, sans-serif;
2 | $primary-color: #333;
3 | $variabile: 5 / 3 * 6 / 7;
4 |
5 | body {
6 | font: 100% $font-stack;
7 | color: $primary-color;
8 | height: $variabile;
9 | }
10 |
--------------------------------------------------------------------------------
/testpkg/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 |
4 | setup(
5 | name='testpkg',
6 | packages=['testpkg'],
7 | sass_manifests={
8 | 'testpkg': ('static/scss', 'static/css'),
9 | },
10 | setup_requires=['libsass'],
11 | )
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg
2 | *.egg-info
3 | *.pyc
4 | *.pyd
5 | *.so
6 | .*.swp
7 | .DS_Store
8 | ._.DS_Store
9 | .coverage
10 | .tox
11 | /.libsass-upstream-version
12 | build/
13 | dist/
14 | docs/_build
15 | testpkg/build/
16 | testpkg/dist/
17 | testpkg/testpkg/static/css/*.scss.css
18 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include libsass *.c
2 | recursive-include libsass *.cpp
3 | recursive-include libsass *.h
4 | recursive-include libsass *.hpp
5 | include libsass/Makefile
6 | include .libsass-upstream-version
7 | include test/*.scss
8 | include README.rst
9 | include LICENSE
10 |
--------------------------------------------------------------------------------
/sassutils/__init__.py:
--------------------------------------------------------------------------------
1 | """:mod:`sassutils` --- Additional utilities related to Sass
2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 |
4 | This package provides several additional utilities related to Sass
5 | which depends on libsass core (:mod:`sass` module).
6 |
7 | """
8 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py,pypy3,pre-commit
3 |
4 | [testenv]
5 | usedevelop = true
6 | deps = -rrequirements-dev.txt
7 | setenv = PWD={toxinidir}
8 | commands =
9 | coverage erase
10 | coverage run -m pytest sasstests.py
11 | coverage combine
12 | coverage report
13 |
14 | [testenv:pre-commit]
15 | skip_install = true
16 | deps = pre-commit
17 | commands = pre-commit run --all-files --show-diff-on-failure
18 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | Sass is more than a technology; Sass is driven by the community of individuals
2 | that power its development and use every day. As a community, we want to embrace
3 | the very differences that have made our collaboration so powerful, and work
4 | together to provide the best environment for learning, growing, and sharing of
5 | ideas. It is imperative that we keep Sass a fun, welcoming, challenging, and
6 | fair place to play.
7 |
8 | [The full community guidelines can be found on the Sass website.][link]
9 |
10 | [link]: https://sass-lang.com/community-guidelines
11 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | parallel = True
3 | branch = True
4 | source = $PWD
5 | data_file = $PWD/.coverage
6 | omit =
7 | */.tox/*
8 | /usr/*
9 | */setup.py
10 |
11 | [report]
12 | show_missing = True
13 | exclude_lines =
14 | # Have to re-enable the standard pragma
15 | \#\s*pragma: no cover
16 |
17 | # Don't complain if tests don't hit defensive assertion code:
18 | ^\s*raise AssertionError\b
19 | ^\s*raise NotImplementedError\b
20 | ^\s*return NotImplemented\b
21 | ^\s*raise TypeError\b
22 | ^\s*raise$
23 |
24 | # Don't complain if non-runnable code isn't run:
25 | ^if __name__ == ['"]__main__['"]:$
26 |
27 | [html]
28 | directory = coverage-html
29 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 |
3 | on:
4 | push:
5 | branches: [main, test-me-*]
6 | tags: '*'
7 | pull_request:
8 |
9 | jobs:
10 | main-windows:
11 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
12 | with:
13 | env: '["py310"]'
14 | os: windows-latest
15 | arch: '["x64", "x86"]'
16 | wheel-tags: true
17 | submodules: true
18 | main-macos:
19 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
20 | with:
21 | env: '["py310"]'
22 | os: macos-latest
23 | wheel-tags: true
24 | submodules: true
25 | main-macos-intel:
26 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
27 | with:
28 | env: '["py310"]'
29 | os: macos-13
30 | wheel-tags: true
31 | submodules: true
32 | main-linux:
33 | uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
34 | with:
35 | env: '["py310", "py311", "py312"]'
36 | os: ubuntu-latest
37 | submodules: true
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Hong Minhee
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v6.0.0
4 | hooks:
5 | - id: trailing-whitespace
6 | - id: end-of-file-fixer
7 | - id: check-yaml
8 | - id: debug-statements
9 | - id: double-quote-string-fixer
10 | - id: name-tests-test
11 | - id: requirements-txt-fixer
12 | - repo: https://github.com/asottile/setup-cfg-fmt
13 | rev: v3.1.0
14 | hooks:
15 | - id: setup-cfg-fmt
16 | - repo: https://github.com/asottile/reorder-python-imports
17 | rev: v3.16.0
18 | hooks:
19 | - id: reorder-python-imports
20 | args: [--py310-plus]
21 | - repo: https://github.com/asottile/add-trailing-comma
22 | rev: v4.0.0
23 | hooks:
24 | - id: add-trailing-comma
25 | - repo: https://github.com/asottile/pyupgrade
26 | rev: v3.21.0
27 | hooks:
28 | - id: pyupgrade
29 | args: [--py310-plus]
30 | - repo: https://github.com/hhatto/autopep8
31 | rev: v2.3.2
32 | hooks:
33 | - id: autopep8
34 | - repo: https://github.com/PyCQA/flake8
35 | rev: 7.3.0
36 | hooks:
37 | - id: flake8
38 | exclude: ^docs/conf.py
39 |
--------------------------------------------------------------------------------
/bin/build-manylinux-wheels:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Script for building 'manylinux' wheels for libsass.
3 |
4 | Run me after putting the source distribution on pypi.
5 |
6 | See: https://www.python.org/dev/peps/pep-0513/
7 | """
8 | import os
9 | import pipes
10 | import subprocess
11 | import tempfile
12 |
13 |
14 | def check_call(*cmd):
15 | print(
16 | 'build-manylinux-wheels>> ' +
17 | ' '.join(pipes.quote(part) for part in cmd),
18 | )
19 | subprocess.check_call(cmd)
20 |
21 |
22 | def main():
23 | os.makedirs('dist', exist_ok=True)
24 | with tempfile.TemporaryDirectory() as work:
25 | pip = '/opt/python/cp39-cp39/bin/pip'
26 | check_call(
27 | 'docker', 'run', '-ti',
28 | # Use this so the files are not owned by root
29 | '--user', f'{os.getuid()}:{os.getgid()}',
30 | # We'll do building in /work and copy results to /dist
31 | '-v', f'{work}:/work:rw',
32 | '-v', '{}:/dist:rw'.format(os.path.abspath('dist')),
33 | 'quay.io/pypa/manylinux1_x86_64:latest',
34 | 'bash', '-exc',
35 | '{} wheel --verbose --wheel-dir /work --no-deps libsass && '
36 | 'auditwheel repair --wheel-dir /dist /work/*.whl'.format(pip),
37 | )
38 | return 0
39 |
40 |
41 | if __name__ == '__main__':
42 | raise SystemExit(main())
43 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = libsass
3 | version = attr: sass.__version__
4 | description = Sass for Python: A straightforward binding of libsass for Python.
5 | long_description = file: README.rst
6 | long_description_content_type = text/x-rst
7 | url = https://sass.github.io/libsass-python/
8 | author = Hong Minhee
9 | author_email = minhee@dahlia.kr
10 | license = MIT
11 | license_files = LICENSE
12 | classifiers =
13 | Development Status :: 5 - Production/Stable
14 | Environment :: Web Environment
15 | Intended Audience :: Developers
16 | Operating System :: OS Independent
17 | Programming Language :: C
18 | Programming Language :: C++
19 | Programming Language :: Python :: 3
20 | Programming Language :: Python :: 3 :: Only
21 | Programming Language :: Python :: Implementation :: CPython
22 | Programming Language :: Python :: Implementation :: PyPy
23 | Programming Language :: Python :: Implementation :: Stackless
24 | Topic :: Internet :: WWW/HTTP
25 | Topic :: Internet :: WWW/HTTP :: Dynamic Content
26 | Topic :: Software Development :: Code Generators
27 | Topic :: Software Development :: Compilers
28 |
29 | [options]
30 | packages = sassutils
31 | py_modules =
32 | pysassc
33 | sass
34 | sasstests
35 | python_requires = >=3.10
36 |
37 | [options.entry_points]
38 | console_scripts =
39 | pysassc = pysassc:main
40 | distutils.commands =
41 | build_sass = sassutils.distutils:build_sass
42 | distutils.setup_keywords =
43 | sass_manifests = sassutils.distutils:validate_manifests
44 |
45 | [aliases]
46 | upload_doc = build_sphinx upload_doc
47 | release = sdist upload build_sphinx upload_doc
48 |
49 | [flake8]
50 | exclude = .tox,build,dist,docs,ez_setup.py
51 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | Contributor's guide
2 | ===================
3 |
4 | Coding style
5 | ------------
6 |
7 | - Follow `PEP 8`_. flake8_ would help.
8 | - Order imports by lexicographical order.
9 | - Prefer relative imports.
10 | - All functions, classes, methods, attributes, and modules should have
11 | the docstring.
12 | - Functions and methods should contain ``:param:``, ``:type:``
13 | (``:return:``, ``:rtype`` if it returns something),
14 | (``:raise:`` if it may raise an error) in their docstring.
15 |
16 | .. _flake8: https://gitlab.com/pycqa/flake8
17 | .. _PEP 8: https://www.python.org/dev/peps/pep-0008
18 |
19 |
20 | Tests
21 | -----
22 |
23 | - All code patches should contain one or more unit tests or regression tests.
24 | - All code patches have to successfully run tests on every Python version
25 | we aim to support. tox_ would help.
26 | - All commits will be tested by `GitHub Actions`_ (Linux and Windows).
27 |
28 | .. _tox: https://tox.readthedocs.io/
29 | .. _`GitHub Actions`: https://github.com/sass/libsass-python/actions
30 |
31 |
32 | Maintainer's guide
33 | ==================
34 |
35 | Releasing
36 | ---------
37 |
38 | Here's a brief check list for releasing a new version:
39 |
40 | - Double check if the version is correctly bumped.
41 | You can bump the version by changing ``__version__`` in sass.py file.
42 | Note that it might be already bumped by other maintainers,
43 | so check what's the latest release version from PyPI_.
44 | - The changelog has to be complete, and frozen.
45 | "To be released" sentence has to be replaced by the actual release date.
46 | - If the code freeze for the release is done (including version bump),
47 | tag the commit using ``git tag`` command. The tag name has to simply be
48 | the version name e.g. ``1.2.3``. Of course, the tag also has to be pushed
49 | to the upstream repository.
50 | - Make a source distribution and upload it to PyPI
51 | (``python3 setup.py sdist upload``).
52 | If it's successful the new version must appear on PyPI_.
53 | - `GitHub Actions`_ automatically makes binary wheels for Windows, but each
54 | CI build takes a while. These wheels are not automatically uploaded,
55 | you can retreive them from the build's artifacts.
56 | - Run ``./bin/build-manylinux-wheels`` to build linux wheels and upload them to
57 | PyPI (takes ~5 minutes).
58 | - The `docs website`__ also has to be updated.
59 | It's currently a static website deployed on GitHub Pages.
60 | Use ``python setup.py upload_doc`` command.
61 | Although it seems possible to be automated using Github Actions.
62 | - Manually create a release through https://github.com/sass/libsass-python/releases/
63 |
64 | Ping Hong Minhee (hongminhee@member.fsf.org, @dahlia on GitHub) if you need
65 | any help!
66 |
67 | .. _PyPI: https://pypi.org/pypi/libsass/
68 | __ https://sass.github.io/libsass-python/
69 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | libsass-python: Sass_/SCSS for Python
2 | =====================================
3 |
4 | LibSass has reached its end of life. It will no longer receive updates of any kind,
5 | nor will its wrapper libraries. Users should migrate to Dart Sass at their earliest
6 | convenience. See also https://github.com/attakei/sass-embedded-python.
7 |
8 | .. image:: https://badge.fury.io/py/libsass.svg
9 | :alt: PyPI
10 | :target: https://pypi.org/pypi/libsass/
11 |
12 | .. image:: https://github.com/sass/libsass-python/actions/workflows/main.yml/badge.svg
13 | :target: https://github.com/sass/libsass-python/actions/workflows/main.yml
14 | :alt: Build Status
15 |
16 | .. image:: https://results.pre-commit.ci/badge/github/sass/libsass-python/main.svg
17 | :target: https://results.pre-commit.ci/latest/github/sass/libsass-python/main
18 | :alt: pre-commit.ci status
19 |
20 | This package provides a simple Python extension module ``sass`` which is
21 | binding LibSass_ (written in C/C++ by Hampton Catlin and Aaron Leung).
22 | It's very straightforward and there isn't any headache related to Python
23 | distribution/deployment. That means you can add just ``libsass`` into
24 | your ``setup.py``'s ``install_requires`` list or ``requirements.txt`` file.
25 | No need for Ruby nor Node.js.
26 |
27 | .. _Sass: https://sass-lang.com/
28 | .. _LibSass: https://github.com/sass/libsass
29 |
30 |
31 | Features
32 | --------
33 |
34 | - You don't need any Ruby/Node.js stack at all, for development or deployment
35 | either.
36 | - Fast. (LibSass_ is written in C++.)
37 | - Simple API. See the below example code for details.
38 | - Custom functions.
39 | - ``@import`` callbacks.
40 | - Support both tabbed (Sass) and braces (SCSS) syntax.
41 | - WSGI middleware for ease of development.
42 | It automatically compiles Sass/SCSS files for each request.
43 | - ``setuptools``/``distutils`` integration.
44 | You can build all Sass/SCSS files using
45 | ``setup.py build_sass`` command.
46 | - Works also on PyPy.
47 | - Provides prebuilt wheel_ binaries for Linux, Windows, and Mac.
48 |
49 | .. _wheel: https://www.python.org/dev/peps/pep-0427/
50 |
51 |
52 | Install
53 | -------
54 |
55 | It's available on PyPI_, so you can install it using ``pip`` (or
56 | ``easy_install``):
57 |
58 | .. code-block:: console
59 |
60 | $ pip install libsass
61 |
62 | .. note::
63 |
64 | libsass requires some features introduced by the recent C++ standard.
65 | You need a C++ compiler that support those features.
66 | See also libsass project's README_ file.
67 |
68 | .. _PyPI: https://pypi.org/pypi/libsass/
69 | .. _README: https://github.com/sass/libsass#readme
70 |
71 |
72 | .. _example:
73 |
74 | Example
75 | -------
76 |
77 | .. code-block:: pycon
78 |
79 | >>> import sass
80 | >>> print sass.compile(string='a { b { color: blue; } }')
81 | a b {
82 | color: blue; }
83 |
84 |
85 | Docs
86 | ----
87 |
88 | There's the user guide manual and the full API reference for ``libsass``:
89 |
90 | https://sass.github.io/libsass-python/
91 |
92 | You can build the docs by yourself:
93 |
94 | .. code-block:: console
95 |
96 | $ cd docs/
97 | $ make html
98 |
99 | The built docs will go to ``docs/_build/html/`` directory.
100 |
101 |
102 | Credit
103 | ------
104 |
105 | Hong Minhee wrote this Python binding of LibSass_.
106 |
107 | Hampton Catlin and Aaron Leung wrote LibSass_, which is portable C/C++
108 | implementation of Sass_.
109 |
110 | Hampton Catlin originally designed Sass_ language and wrote the first
111 | reference implementation of it in Ruby.
112 |
113 | The above three are all distributed under `MIT license`_.
114 |
115 | .. _MIT license: https://mit-license.org/
116 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | libsass-python: Sass_/SCSS for Python
2 | =====================================
3 |
4 | This package provides a simple Python extension module :mod:`sass` which is
5 | binding LibSass_ (written in C/C++ by Hampton Catlin and Aaron Leung).
6 | It's very straightforward and there isn't any headache related to Python
7 | distribution/deployment. That means you can add just ``libsass`` into
8 | your :file:`setup.py`'s ``install_requires`` list or :file:`requirements.txt`
9 | file.
10 |
11 | .. _Sass: https://sass-lang.com/
12 | .. _LibSass: https://github.com/sass/libsass
13 |
14 |
15 | Features
16 | --------
17 |
18 | - You don't need any Ruby/Node.js stack at all, for development or deployment
19 | either.
20 | - Fast. (LibSass_ is written in C++.)
21 | - Simple API. See :ref:`example code ` for details.
22 | - Custom functions.
23 | - ``@import`` callbacks.
24 | - Support both tabbed (Sass) and braces (SCSS) syntax.
25 | - WSGI middleware for ease of development.
26 | It automatically compiles Sass/SCSS files for each request.
27 | See also :mod:`sassutils.wsgi` for details.
28 | - :mod:`setuptools`/:mod:`distutils` integration.
29 | You can build all Sass/SCSS files using
30 | :program:`setup.py build_sass` command.
31 | See also :mod:`sassutils.distutils` for details.
32 | - Works also on PyPy.
33 | - Provides prebuilt wheel (:pep:`427`) binaries for Windows and Mac.
34 |
35 |
36 | Install
37 | -------
38 |
39 | It's available on PyPI_, so you can install it using :program:`pip`:
40 |
41 | .. code-block:: console
42 |
43 | $ pip install libsass
44 |
45 | .. note::
46 |
47 | libsass requires some features introduced by the recent C++ standard.
48 | You need a C++ compiler that support those features.
49 | See also libsass project's README_ file.
50 |
51 | .. _PyPI: https://pypi.org/pypi/libsass/
52 | .. _README: https://github.com/sass/libsass#readme
53 |
54 |
55 | .. _example:
56 |
57 | Examples
58 | --------
59 |
60 | Compile a String of Sass to CSS
61 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62 |
63 | >>> import sass
64 | >>> sass.compile(string='a { b { color: blue; } }')
65 | 'a b {\n color: blue; }\n'
66 |
67 | Compile a Directory of Sass Files to CSS
68 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 |
70 | >>> import sass
71 | >>> import os
72 | >>> os.mkdir('css')
73 | >>> os.mkdir('sass')
74 | >>> scss = """\
75 | ... $theme_color: #cc0000;
76 | ... body {
77 | ... background-color: $theme_color;
78 | ... }
79 | ... """
80 | >>> with open('sass/example.scss', 'w') as example_scss:
81 | ... example_scss.write(scss)
82 | ...
83 | >>> sass.compile(dirname=('sass', 'css'), output_style='compressed')
84 | >>> with open('css/example.css') as example_css:
85 | ... print(example_css.read())
86 | ...
87 | body{background-color:#c00}
88 |
89 |
90 | User's Guide
91 | ------------
92 |
93 | .. toctree::
94 | :maxdepth: 2
95 |
96 | frameworks/flask
97 | changes
98 |
99 |
100 | References
101 | ----------
102 |
103 | .. toctree::
104 | :maxdepth: 2
105 |
106 | pysassc
107 | sass
108 | sassutils
109 |
110 |
111 | Credit
112 | ------
113 |
114 | Hong Minhee wrote this Python binding of LibSass_.
115 |
116 | Hampton Catlin and Aaron Leung wrote LibSass_, which is portable C/C++
117 | implementation of Sass_.
118 |
119 | Hampton Catlin originally designed Sass_ language and wrote the first
120 | reference implementation of it in Ruby.
121 |
122 | The above three are all distributed under `MIT license`_.
123 |
124 | .. _MIT license: https://mit-license.org/
125 |
126 |
127 | Open source
128 | -----------
129 |
130 | GitHub (Git repository + issues)
131 | https://github.com/sass/libsass-python
132 |
133 | GitHub Actions (linux + macos + windows)
134 |
135 | .. image:: https://github.com/sass/libsass-python/actions/workflows/main.yml/badge.svg
136 | :target: https://github.com/sass/libsass-python/actions/workflows/main.yml
137 | :alt: Build Status
138 |
139 | PyPI
140 | https://pypi.org/pypi/libsass/
141 |
142 | .. image:: https://badge.fury.io/py/libsass.svg
143 | :alt: PyPI
144 | :target: https://pypi.org/pypi/libsass/
145 |
146 | Changelog
147 | :doc:`changes`
148 |
149 |
150 | Indices and tables
151 | ------------------
152 |
153 | - :ref:`genindex`
154 | - :ref:`modindex`
155 | - :ref:`search`
156 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\libsass.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\libsass.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/frameworks/flask.rst:
--------------------------------------------------------------------------------
1 | Using with Flask
2 | ================
3 |
4 | This guide explains how to use libsass with the Flask_ web framework.
5 | :mod:`sassutils` package provides several tools that can be integrated
6 | into web applications written in Flask.
7 |
8 | .. _Flask: http://flask.pocoo.org/
9 |
10 | .. contents::
11 |
12 |
13 | Directory layout
14 | ----------------
15 |
16 | Imagine the project contained in such directory layout:
17 |
18 | - :file:`setup.py`
19 | - :file:`myapp/`
20 |
21 | - :file:`__init__.py`
22 | - :file:`static/`
23 |
24 | - :file:`sass/`
25 | - :file:`css/`
26 | - :file:`templates/`
27 |
28 | Sass/SCSS files will go inside :file:`myapp/static/sass/` directory.
29 | Compiled CSS files will go inside :file:`myapp/static/css/` directory.
30 | CSS files can be regenerated, so add :file:`myapp/static/css/` into your
31 | ignore list like :file:`.gitignore` or :file:`.hgignore`.
32 |
33 |
34 | Defining manifest
35 | -----------------
36 |
37 | The :mod:`sassutils` defines a concept named :dfn:`manifest`.
38 | Manifest is the build settings of Sass/SCSS. It specifies some paths
39 | related to building Sass/SCSS:
40 |
41 | - The path of the directory which contains Sass/SCSS source files.
42 | - The path of the directory which the compiled CSS files will go.
43 | - The path, exposed to HTTP (through WSGI), of the directory that
44 | will contain the compiled CSS files.
45 |
46 | Every package may have its own manifest. Paths have to be relative
47 | to the path of the package.
48 |
49 | For example, in the above project, the package name is :mod:`myapp`.
50 | The path of the package is :file:`myapp/`. The path of the Sass/SCSS
51 | directory is :file:`static/sass/` (relative to the package directory).
52 | The path of the CSS directory is :file:`static/css/`.
53 | The exposed path is :file:`/static/css`.
54 |
55 | These settings can be represented as the following manifests::
56 |
57 | {
58 | 'myapp': ('static/sass', 'static/css', '/static/css')
59 | }
60 |
61 | As you can see the above, the set of manifests are represented in dictionary,
62 | in which the keys are packages names and the values are tuples of paths.
63 |
64 |
65 | Building Sass/SCSS for each request
66 | -----------------------------------
67 |
68 | .. seealso::
69 |
70 | Flask --- `Hooking in WSGI Middlewares`__
71 | The section which explains how to integrate WSGI middlewares to
72 | Flask.
73 |
74 | Flask --- :ref:`flask:app-dispatch`
75 | The documentation which explains how Flask dispatches each
76 | request internally.
77 |
78 | __ http://flask.pocoo.org/docs/quickstart/#hooking-in-wsgi-middlewares
79 |
80 | In development, manually building Sass/SCSS files for each change is
81 | a tedious task. :class:`~sassutils.wsgi.SassMiddleware` makes the web
82 | application build Sass/SCSS files for each request automatically.
83 | It's a WSGI middleware, so it can be plugged into the web app written in
84 | Flask.
85 |
86 | :class:`~sassutils.wsgi.SassMiddleware` takes two required parameters:
87 |
88 | - The WSGI-compliant callable object.
89 | - The set of manifests represented as a dictionary.
90 |
91 | So::
92 |
93 | from flask import Flask
94 | from sassutils.wsgi import SassMiddleware
95 |
96 | app = Flask(__name__)
97 |
98 | app.wsgi_app = SassMiddleware(app.wsgi_app, {
99 | 'myapp': ('static/sass', 'static/css', '/static/css')
100 | })
101 |
102 | And then, if you want to link a compiled CSS file, use the
103 | :func:`~flask.url_for()` function:
104 |
105 | .. sourcecode:: html+jinja
106 |
107 |
109 |
110 | .. note::
111 |
112 | The linked filename is :file:`style.scss.css`, not just :file:`style.scss`.
113 | All compiled filenames have trailing ``.css`` suffix.
114 |
115 |
116 | Building Sass/SCSS for each deployment
117 | --------------------------------------
118 |
119 | .. note::
120 |
121 | This section assumes that you use setuptools_ for deployment.
122 |
123 | .. seealso::
124 |
125 | Flask --- :ref:`flask:distribute-deployment`
126 | How to deploy Flask application using setuptools_.
127 |
128 | If libsass is installed in the :file:`site-packages` (for example,
129 | your virtualenv), the :file:`setup.py` script also gets a new command
130 | provided by libsass: :class:`~sassutils.distutils.build_sass`.
131 | The command is aware of the ``sass_manifests`` option of :file:`setup.py` and
132 | builds all Sass/SCSS sources according to the manifests.
133 |
134 | Add these arguments to :file:`setup.py` script::
135 |
136 | setup(
137 | # ...,
138 | setup_requires=['libsass >= 0.6.0'],
139 | sass_manifests={
140 | 'myapp': ('static/sass', 'static/css', '/static/css')
141 | }
142 | )
143 |
144 | The ``setup_requires`` option makes sure that libsass is installed
145 | in :file:`site-packages` (for example, your virtualenv) before
146 | the :file:`setup.py` script. That means if you run the :file:`setup.py`
147 | script and libsass isn't installed in advance, it will automatically
148 | install libsass first.
149 |
150 | The ``sass_manifests`` specifies the manifests for libsass.
151 |
152 | Now :program:`setup.py build_sass` will compile all Sass/SCSS files
153 | in the specified path and generates compiled CSS files inside the specified
154 | path (according to the manifests).
155 |
156 | If you use it with ``sdist`` or ``bdist`` commands, the packed archive will
157 | also contain the compiled CSS files!
158 |
159 | .. sourcecode:: console
160 |
161 | $ python setup.py build_sass sdist
162 |
163 | You can add aliases to make these commands always run the ``build_sass``
164 | command first. Make :file:`setup.cfg` config:
165 |
166 | .. sourcecode:: ini
167 |
168 | [aliases]
169 | sdist = build_sass sdist
170 | bdist = build_sass bdist
171 |
172 | Now it automatically builds Sass/SCSS sources and include the compiled CSS files
173 | to the package archive when you run :program:`setup.py sdist`.
174 |
175 | .. _setuptools: https://pypi.org/pypi/setuptools/
176 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/libsass.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/libsass.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/libsass"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/libsass"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/sassutils/wsgi.py:
--------------------------------------------------------------------------------
1 | """:mod:`sassutils.wsgi` --- WSGI middleware for development purpose
2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 |
4 | """
5 | import collections.abc
6 | import logging
7 | import os.path
8 |
9 | from pkg_resources import resource_filename
10 |
11 | from .builder import Manifest
12 | from sass import CompileError
13 |
14 | __all__ = 'SassMiddleware',
15 |
16 |
17 | class SassMiddleware:
18 | r"""WSGI middleware for development purpose. Every time a CSS file has
19 | requested it finds a matched Sass/SCSS source file and then compiled
20 | it into CSS.
21 |
22 | It shows syntax errors in three ways:
23 |
24 | Heading comment
25 | The result CSS includes detailed error message in the heading
26 | CSS comment e.g.:
27 |
28 | .. code-block:: css
29 |
30 | /*
31 | Error: invalid property name
32 | */
33 |
34 | Red text in ``body:before``
35 | The result CSS draws detailed error message in ``:before``
36 | pseudo-class of ``body`` element e.g.:
37 |
38 | .. code-block:: css
39 |
40 | body:before {
41 | content: 'Error: invalid property name';
42 | color: maroon;
43 | background-color: white;
44 | }
45 |
46 | In most cases you could be aware of syntax error by refreshing your
47 | working document because it will removes all other styles and leaves
48 | only a red text.
49 |
50 | :mod:`logging`
51 | It logs syntax errors if exist during compilation to
52 | ``sassutils.wsgi.SassMiddleware`` logger with level ``ERROR``.
53 |
54 | To enable this::
55 |
56 | from logging import Formatter, StreamHandler, getLogger
57 | logger = getLogger('sassutils.wsgi.SassMiddleware')
58 | handler = StreamHandler(level=logging.ERROR)
59 | formatter = Formatter(fmt='*' * 80 + '\n%(message)s\n' + '*' * 80)
60 | handler.setFormatter(formatter)
61 | logger.addHandler(handler)
62 |
63 | Or simply::
64 |
65 | import logging
66 | logging.basicConfig()
67 |
68 | :param app: the WSGI application to wrap
69 | :type app: :class:`collections.abc.Callable`
70 | :param manifests: build settings. the same format to
71 | :file:`setup.py` script's ``sass_manifests``
72 | option
73 | :type manifests: :class:`collections.abc.Mapping`
74 | :param package_dir: optional mapping of package names to directories.
75 | the same format to :file:`setup.py` script's
76 | ``package_dir`` option
77 | :type package_dir: :class:`collections.abc.Mapping`
78 |
79 | .. versionchanged:: 0.4.0
80 | It creates also source map files with filenames followed by
81 | :file:`.map` suffix.
82 |
83 | .. versionadded:: 0.8.0
84 | It logs syntax errors if exist during compilation to
85 | ``sassutils.wsgi.SassMiddleware`` logger with level ``ERROR``.
86 |
87 | """
88 |
89 | def __init__(
90 | self, app, manifests, package_dir={},
91 | error_status='200 OK',
92 | ):
93 | if not callable(app):
94 | raise TypeError(
95 | 'app must be a WSGI-compliant callable object, '
96 | 'not ' + repr(app),
97 | )
98 | self.app = app
99 | self.manifests = Manifest.normalize_manifests(manifests)
100 | if not isinstance(package_dir, collections.abc.Mapping):
101 | raise TypeError(
102 | 'package_dir must be a mapping object, not ' +
103 | repr(package_dir),
104 | )
105 | self.error_status = error_status
106 | self.package_dir = dict(package_dir)
107 | for package_name in self.manifests:
108 | if package_name in self.package_dir:
109 | continue
110 | path = resource_filename(package_name, '')
111 | self.package_dir[package_name] = path
112 | self.paths = []
113 | for package_name, manifest in self.manifests.items():
114 | wsgi_path = manifest.wsgi_path
115 | if not wsgi_path.startswith('/'):
116 | wsgi_path = '/' + wsgi_path
117 | if not wsgi_path.endswith('/'):
118 | wsgi_path += '/'
119 | package_dir = self.package_dir[package_name]
120 | self.paths.append((wsgi_path, package_dir, manifest))
121 |
122 | def __call__(self, environ, start_response):
123 | path = environ.get('PATH_INFO', '/')
124 | if path.endswith('.css'):
125 | for prefix, package_dir, manifest in self.paths:
126 | if not path.startswith(prefix):
127 | continue
128 | css_filename = path[len(prefix):]
129 | sass_filename = manifest.unresolve_filename(
130 | package_dir, css_filename,
131 | )
132 | try:
133 | result = manifest.build_one(
134 | package_dir,
135 | sass_filename,
136 | source_map=True,
137 | )
138 | except OSError:
139 | break
140 | except CompileError as e:
141 | logger = logging.getLogger(__name__ + '.SassMiddleware')
142 | logger.error(str(e))
143 | start_response(
144 | self.error_status,
145 | [('Content-Type', 'text/css; charset=utf-8')],
146 | )
147 | return [
148 | b'/*\n', str(e).encode('utf-8'), b'\n*/\n\n',
149 | b'body:before { content: ',
150 | self.quote_css_string(str(e)).encode('utf-8'),
151 | b'; color: maroon; background-color: white',
152 | b'; white-space: pre-wrap; display: block',
153 | b'; font-family: "Courier New", monospace'
154 | b'; user-select: text; }',
155 | ]
156 |
157 | def read_file(path):
158 | with open(path, 'rb') as in_:
159 | while 1:
160 | chunk = in_.read(4096)
161 | if chunk:
162 | yield chunk
163 | else:
164 | break
165 | start_response('200 OK', [('Content-Type', 'text/css')])
166 | return read_file(os.path.join(package_dir, result))
167 | return self.app(environ, start_response)
168 |
169 | @staticmethod
170 | def quote_css_string(s):
171 | """Quotes a string as CSS string literal."""
172 | return "'" + ''.join('\\%06x' % ord(c) for c in s) + "'"
173 |
--------------------------------------------------------------------------------
/sassutils/distutils.py:
--------------------------------------------------------------------------------
1 | """:mod:`sassutils.distutils` --- :mod:`setuptools`/:mod:`distutils` integrat\
2 | ion
3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
4 | ~~~
5 |
6 | This module provides extensions (and some magical monkey-patches, sorry)
7 | of the standard :mod:`distutils` and :mod:`setuptools` (now it's named
8 | Distribute) for libsass.
9 |
10 | To use this, add ``libsass`` into ``setup_requires`` (not ``install_requires``)
11 | option of the :file:`setup.py` script::
12 |
13 | from setuptools import setup
14 |
15 | setup(
16 | # ...,
17 | setup_requires=['libsass >= 0.6.0']
18 | )
19 |
20 | It will adds :class:`build_sass` command to the :file:`setup.py` script:
21 |
22 | .. sourcecode:: console
23 |
24 | $ python setup.py build_sass
25 |
26 | This commands builds Sass/SCSS files to compiled CSS files of the project
27 | and makes the package archive (made by :class:`~distutils.command.sdist.sdist`,
28 | :class:`~distutils.command.bdist.bdist`, and so on) to include these compiled
29 | CSS files.
30 |
31 | To set the directory of Sass/SCSS source files and the directory to
32 | store compiled CSS files, specify ``sass_manifests`` option::
33 |
34 | from setuptools import find_packages, setup
35 |
36 | setup(
37 | name='YourPackage',
38 | packages=find_packages(),
39 | sass_manifests={
40 | 'your.webapp': ('static/sass', 'static/css')
41 | },
42 | setup_requires=['libsass >= 0.6.0']
43 | )
44 |
45 | The option should be a mapping of package names to pairs of paths, e.g.::
46 |
47 | {
48 | 'package': ('static/sass', 'static/css'),
49 | 'package.name': ('static/scss', 'static')
50 | }
51 |
52 | The option can also be a mapping of package names to manifest dictionaries::
53 |
54 | {
55 | 'package': {
56 | 'sass_path': 'static/sass',
57 | 'css_path': 'static/css',
58 | 'strip_extension': True,
59 | },
60 | }
61 |
62 | .. versionadded:: 0.15.0
63 | Added ``strip_extension`` so ``a.scss`` is compiled to ``a.css`` instead
64 | of ``a.scss.css``. This option will default to ``True`` in the future.
65 |
66 | .. versionadded:: 0.6.0
67 | Added ``--output-style``/``-s`` option to :class:`build_sass` command.
68 |
69 | """
70 | import functools
71 | import os.path
72 |
73 | import distutils.errors
74 | import distutils.log
75 | import distutils.util
76 | from setuptools import Command
77 | from setuptools.command.sdist import sdist
78 |
79 | from .builder import Manifest
80 | from sass import OUTPUT_STYLES
81 |
82 | __all__ = 'build_sass', 'validate_manifests'
83 |
84 |
85 | def validate_manifests(dist, attr, value):
86 | """Verifies that ``value`` is an expected mapping of package to
87 | :class:`sassutils.builder.Manifest`.
88 |
89 | """
90 | try:
91 | Manifest.normalize_manifests(value)
92 | except TypeError:
93 | raise distutils.errors.DistutilsSetupError(
94 | attr + "must be a mapping object like: {'package.name': "
95 | "sassutils.distutils.Manifest('sass/path')}, or as shorten form: "
96 | "{'package.name': ('sass/path', 'css/path'}), not " +
97 | repr(value),
98 | )
99 |
100 |
101 | class build_sass(Command):
102 | """Builds Sass/SCSS files to CSS files."""
103 |
104 | description = __doc__
105 | user_options = [
106 | (
107 | 'output-style=', 's',
108 | 'Coding style of the compiled result. Choose one of ' +
109 | ', '.join(OUTPUT_STYLES),
110 | ),
111 | ]
112 |
113 | def initialize_options(self):
114 | self.package_dir = None
115 | self.output_style = 'nested'
116 |
117 | def finalize_options(self):
118 | self.package_dir = {}
119 | if self.distribution.package_dir:
120 | self.package_dir = {}
121 | for name, path in self.distribution.package_dir.items():
122 | self.package_dir[name] = distutils.util.convert_path(path)
123 |
124 | def run(self):
125 | manifests = self.distribution.sass_manifests
126 | manifests = Manifest.normalize_manifests(manifests)
127 | self.distribution.sass_manifests = manifests
128 | package_data = self.distribution.package_data
129 | data_files = self.distribution.data_files or []
130 | for package_name, manifest in manifests.items():
131 | package_dir = self.get_package_dir(package_name)
132 | distutils.log.info("building '%s' sass", package_name)
133 | css_files = manifest.build(
134 | package_dir,
135 | output_style=self.output_style,
136 | )
137 | map(distutils.log.info, css_files)
138 | package_data.setdefault(package_name, []).extend(css_files)
139 | data_files.append(
140 | (
141 | package_dir,
142 | [os.path.join(package_dir, f) for f in css_files],
143 | ),
144 | )
145 | self.distribution.package_data = package_data
146 | self.distribution.data_files = data_files
147 | self.distribution.has_data_files = lambda: True
148 | # See the below monkey patch (end of this source code).
149 | self.distribution.compiled_sass_files = data_files
150 |
151 | def get_package_dir(self, package):
152 | """Returns the directory, relative to the top of the source
153 | distribution, where package ``package`` should be found
154 | (at least according to the :attr:`package_dir` option, if any).
155 |
156 | Copied from :meth:`distutils.command.build_py.get_package_dir()`
157 | method.
158 |
159 | """
160 | path = package.split('.')
161 | if not self.package_dir:
162 | if path:
163 | return os.path.join(*path)
164 | return ''
165 | tail = []
166 | while path:
167 | try:
168 | pdir = self.package_dir['.'.join(path)]
169 | except KeyError:
170 | tail.insert(0, path[-1])
171 | del path[-1]
172 | else:
173 | tail.insert(0, pdir)
174 | return os.path.join(*tail)
175 | else:
176 | pdir = self.package_dir.get('')
177 | if pdir is not None:
178 | tail.insert(0, pdir)
179 | if tail:
180 | return os.path.join(*tail)
181 | return ''
182 |
183 |
184 | # Does monkey-patching the setuptools.command.sdist.sdist.check_readme()
185 | # method to include compiled Sass files as data files.
186 | if not hasattr(sdist, '_wrapped_check_readme'):
187 | @functools.wraps(sdist.check_readme)
188 | def check_readme(self):
189 | try:
190 | files = self.distribution.compiled_sass_files
191 | except AttributeError:
192 | pass
193 | else:
194 | for _, css_files in files:
195 | self.filelist.extend(css_files)
196 | return self._wrapped_check_readme()
197 | sdist._wrapped_check_readme = sdist.check_readme
198 | sdist.check_readme = check_readme
199 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import atexit
2 | import os.path
3 | import platform
4 | import shutil
5 | import subprocess
6 | import sys
7 | import tempfile
8 |
9 | import distutils.cmd
10 | import distutils.log
11 | import distutils.sysconfig
12 | from setuptools import Extension
13 | from setuptools import setup
14 |
15 | MACOS_FLAG = ['-mmacosx-version-min=10.7']
16 | FLAGS_POSIX = [
17 | '-fPIC', '-std=gnu++0x', '-Wall', '-Wno-parentheses', '-Werror=switch',
18 | ]
19 | FLAGS_CLANG = ['-c', '-O3'] + FLAGS_POSIX + ['-stdlib=libc++']
20 | LFLAGS_POSIX = ['-fPIC', '-lstdc++']
21 | LFLAGS_CLANG = ['-fPIC', '-stdlib=libc++']
22 |
23 | sources = ['_sass.c']
24 | headers = []
25 |
26 | if sys.platform == 'win32':
27 | extra_compile_args = ['/Od', '/EHsc', '/MT']
28 | extra_link_args = []
29 | elif platform.system() == 'Darwin':
30 | extra_compile_args = FLAGS_CLANG + MACOS_FLAG
31 | extra_link_args = LFLAGS_CLANG + MACOS_FLAG
32 | elif platform.system() in {'FreeBSD', 'OpenBSD'}:
33 | extra_compile_args = FLAGS_CLANG
34 | extra_link_args = LFLAGS_CLANG
35 | else:
36 | extra_compile_args = FLAGS_POSIX
37 | extra_link_args = LFLAGS_POSIX
38 |
39 | if platform.system() in {'Darwin', 'FreeBSD', 'OpenBSD'}:
40 | os.environ.setdefault('CC', 'clang')
41 | os.environ.setdefault('CXX', 'clang++')
42 | orig_customize_compiler = distutils.sysconfig.customize_compiler
43 |
44 | def customize_compiler(compiler):
45 | orig_customize_compiler(compiler)
46 | compiler.compiler[0] = os.environ['CC']
47 | compiler.compiler_so[0] = os.environ['CXX']
48 | compiler.compiler_cxx[0] = os.environ['CXX']
49 | compiler.linker_so[0] = os.environ['CXX']
50 | return compiler
51 | distutils.sysconfig.customize_compiler = customize_compiler
52 |
53 | if os.environ.get('SYSTEM_SASS', False):
54 | libraries = ['sass']
55 | include_dirs = []
56 | else:
57 | LIBSASS_SOURCE_DIR = os.path.join('libsass', 'src')
58 |
59 | if (
60 | not os.path.isfile(os.path.join('libsass', 'Makefile')) and
61 | os.path.isdir('.git')
62 | ):
63 | print(file=sys.stderr)
64 | print('Missing the libsass sumbodule. Try:', file=sys.stderr)
65 | print(' git submodule update --init', file=sys.stderr)
66 | print(file=sys.stderr)
67 | exit(1)
68 |
69 | # Determine the libsass version from the git checkout
70 | if os.path.exists(os.path.join('libsass', '.git')):
71 | out = subprocess.check_output((
72 | 'git', '-C', 'libsass', 'describe',
73 | '--abbrev=4', '--dirty', '--always', '--tags',
74 | ))
75 | with open('.libsass-upstream-version', 'wb') as libsass_version_file:
76 | libsass_version_file.write(out)
77 |
78 | # The version file should always exist at this point
79 | with open('.libsass-upstream-version', 'rb') as libsass_version_file:
80 | libsass_version = libsass_version_file.read().decode('UTF-8').strip()
81 | if sys.platform == 'win32':
82 | # This looks wrong, but is required for some reason :(
83 | define = fr'/DLIBSASS_VERSION="\"{libsass_version}\""'
84 | else:
85 | define = f'-DLIBSASS_VERSION="{libsass_version}"'
86 |
87 | for directory in (
88 | os.path.join('libsass', 'src'),
89 | os.path.join('libsass', 'include'),
90 | ):
91 | for pth, _, filenames in os.walk(directory):
92 | for filename in filenames:
93 | filename = os.path.join(pth, filename)
94 | if filename.endswith(('.c', '.cpp')):
95 | sources.append(filename)
96 | elif filename.endswith('.h'):
97 | headers.append(filename)
98 |
99 | if platform.system() in {'Darwin', 'FreeBSD', 'OpenBSD'}:
100 | # Dirty workaround to avoid link error...
101 | # Python distutils doesn't provide any way
102 | # to configure different flags for each cc and c++.
103 | cencode_path = os.path.join(LIBSASS_SOURCE_DIR, 'cencode.c')
104 | cencode_body = ''
105 | with open(cencode_path) as f:
106 | cencode_body = f.read()
107 | with open(cencode_path, 'w') as f:
108 | f.write(
109 | '#ifdef __cplusplus\n'
110 | 'extern "C" {\n'
111 | '#endif\n',
112 | )
113 | f.write(cencode_body)
114 | f.write(
115 | '#ifdef __cplusplus\n'
116 | '}\n'
117 | '#endif\n',
118 | )
119 |
120 | @atexit.register
121 | def restore_cencode():
122 | if os.path.isfile(cencode_path):
123 | with open(cencode_path, 'w') as f:
124 | f.write(cencode_body)
125 |
126 | libraries = []
127 | include_dirs = [os.path.join('.', 'libsass', 'include')]
128 | extra_compile_args.append(define)
129 |
130 | # Py_LIMITED_API does not work for pypy
131 | # https://foss.heptapod.net/pypy/pypy/issues/3173
132 | if not hasattr(sys, 'pypy_version_info'):
133 | py_limited_api = True
134 | define_macros = [('Py_LIMITED_API', None)]
135 | else:
136 | py_limited_api = False
137 | define_macros = []
138 |
139 | sass_extension = Extension(
140 | '_sass',
141 | sorted(sources),
142 | include_dirs=include_dirs,
143 | depends=headers,
144 | extra_compile_args=extra_compile_args,
145 | extra_link_args=extra_link_args,
146 | libraries=libraries,
147 | py_limited_api=py_limited_api,
148 | define_macros=define_macros,
149 | )
150 |
151 |
152 | class upload_doc(distutils.cmd.Command):
153 | """Uploads the documentation to GitHub pages."""
154 |
155 | description = __doc__
156 | user_options = []
157 |
158 | def initialize_options(self):
159 | if sys.version_info < (3,):
160 | raise SystemExit('upload_doc must be run with python 3')
161 |
162 | def finalize_options(self):
163 | pass
164 |
165 | def run(self):
166 | path = tempfile.mkdtemp()
167 | build = os.path.join(
168 | os.path.dirname(os.path.abspath(__file__)),
169 | 'build', 'sphinx', 'html',
170 | )
171 | os.chdir(path)
172 | os.system(
173 | 'git clone -b gh-pages --depth 5 '
174 | 'git@github.com:sass/libsass-python.git .',
175 | )
176 | os.system('git rm -r .')
177 | os.system('touch .nojekyll')
178 | os.system('cp -r ' + build + '/* .')
179 | os.system('git stage .')
180 | os.system('git commit -a -m "Documentation updated."')
181 | os.system('git push origin gh-pages')
182 | shutil.rmtree(path)
183 |
184 |
185 | cmdclass = {'upload_doc': upload_doc}
186 |
187 | if sys.version_info >= (3,) and platform.python_implementation() == 'CPython':
188 | try:
189 | import wheel.bdist_wheel
190 | except ImportError:
191 | pass
192 | else:
193 | class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
194 | def finalize_options(self):
195 | self.py_limited_api = f'cp3{sys.version_info[1]}'
196 | super().finalize_options()
197 |
198 | cmdclass['bdist_wheel'] = bdist_wheel
199 |
200 |
201 | setup(
202 | ext_modules=[sass_extension],
203 | cmdclass=cmdclass,
204 | )
205 |
--------------------------------------------------------------------------------
/pysassc.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | r""":mod:`pysassc` --- SassC compliant command line interface
3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 |
5 | This provides SassC_ compliant CLI executable named :program:`pysassc`:
6 |
7 | .. sourcecode:: console
8 |
9 | $ pysassc
10 | Usage: pysassc [options] SCSS_FILE [CSS_FILE]
11 |
12 | There are options as well:
13 |
14 | .. option:: -t