libsass-python/sassutils/distutils.py

199 lines
6.2 KiB
Python

""":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.
"""
import functools
import os.path
import distutils.errors
import distutils.log
import distutils.util
from setuptools import Command
from setuptools.command.sdist import sdist
from .builder import Manifest
from sass import OUTPUT_STYLES
__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