Import Upstream version 0.22.0

This commit is contained in:
luoyaoming 2024-05-07 09:43:48 +08:00
parent be11e18fcc
commit f81cc0bca9
35 changed files with 246 additions and 511 deletions

1
.gitignore vendored
View File

@ -6,7 +6,6 @@
.*.swp .*.swp
.DS_Store .DS_Store
._.DS_Store ._.DS_Store
.pytest_cache/
.coverage .coverage
.tox .tox
/.libsass-upstream-version /.libsass-upstream-version

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "libsass"] [submodule "libsass"]
path = libsass path = libsass
url = git://github.com/sass/libsass.git url = https://github.com/sass/libsass

View File

@ -1,21 +1,35 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1 rev: v4.3.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- id: double-quote-string-fixer
- id: name-tests-test
- id: requirements-txt-fixer
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.8.5
hooks:
- id: reorder-python-imports
args: [--py36-plus]
- repo: https://github.com/asottile/add-trailing-comma
rev: v2.3.0
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/asottile/pyupgrade
rev: v3.1.0
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.7.0
hooks:
- id: autopep8
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 3.9.2 rev: 5.0.4
hooks: hooks:
- id: flake8 - id: flake8
exclude: ^docs/conf.py 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

View File

@ -26,7 +26,7 @@ Tests
- All commits will be tested by `Azure Pipelines`_ (Linux and Windows). - All commits will be tested by `Azure Pipelines`_ (Linux and Windows).
.. _tox: https://tox.readthedocs.io/ .. _tox: https://tox.readthedocs.io/
.. _`Azure Pipelines`: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master .. _`Azure Pipelines`: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
Maintainer's guide Maintainer's guide

View File

@ -5,16 +5,16 @@ libsass-python: Sass_/SCSS for Python
:alt: PyPI :alt: PyPI
:target: https://pypi.org/pypi/libsass/ :target: https://pypi.org/pypi/libsass/
.. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=master .. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=main
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
:alt: Build Status :alt: Build Status
.. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/master.svg .. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/main.svg
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
:alt: Coverage Status :alt: Coverage Status
.. image:: https://results.pre-commit.ci/badge/github/sass/libsass-python/master.svg .. image:: https://results.pre-commit.ci/badge/github/sass/libsass-python/main.svg
:target: https://results.pre-commit.ci/latest/github/sass/libsass-python/master :target: https://results.pre-commit.ci/latest/github/sass/libsass-python/main
:alt: pre-commit.ci status :alt: pre-commit.ci status
This package provides a simple Python extension module ``sass`` which is This package provides a simple Python extension module ``sass`` which is
@ -22,9 +22,9 @@ binding LibSass_ (written in C/C++ by Hampton Catlin and Aaron Leung).
It's very straightforward and there isn't any headache related Python It's very straightforward and there isn't any headache related Python
distribution/deployment. That means you can add just ``libsass`` into distribution/deployment. That means you can add just ``libsass`` into
your ``setup.py``'s ``install_requires`` list or ``requirements.txt`` file. your ``setup.py``'s ``install_requires`` list or ``requirements.txt`` file.
Need no Ruby nor Node.js. No need for Ruby nor Node.js.
It currently supports CPython 2.7, 3.6--3.8, and PyPy 2.3+! It currently supports CPython 3.6+, and PyPy 3!
.. _Sass: https://sass-lang.com/ .. _Sass: https://sass-lang.com/
.. _LibSass: https://github.com/sass/libsass .. _LibSass: https://github.com/sass/libsass

View File

@ -1,6 +1,6 @@
trigger: trigger:
branches: branches:
include: [master, test-me-*] include: [main, test-me-*]
tags: tags:
include: ['*'] include: ['*']
@ -13,30 +13,21 @@ resources:
type: github type: github
endpoint: github endpoint: github
name: asottile/azure-pipeline-templates name: asottile/azure-pipeline-templates
ref: refs/tags/v2.1.0 ref: refs/tags/v2.4.0
jobs: jobs:
- template: job--python-tox.yml@asottile - template: job--python-tox.yml@asottile
parameters: parameters:
toxenvs: [py27, py36] toxenvs: [py37]
os: macos os: macos
wheel_tags: true wheel_tags: true
- template: job--python-tox.yml@asottile - template: job--python-tox.yml@asottile
parameters: parameters:
toxenvs: [py27] toxenvs: [py37]
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 os: windows
architectures: [x64, x86] architectures: [x64, x86]
wheel_tags: true wheel_tags: true
- template: job--python-tox.yml@asottile - template: job--python-tox.yml@asottile
parameters: parameters:
toxenvs: [pypy, pypy3, py27, py36, py37, py38, py39] toxenvs: [pypy3, py36, py37, py38, py39]
os: linux os: linux

View File

@ -23,13 +23,13 @@ def main():
os.makedirs('dist', exist_ok=True) os.makedirs('dist', exist_ok=True)
for python in ('cp27-cp27mu', 'cp36-cp36m'): for python in ('cp27-cp27mu', 'cp36-cp36m'):
with tempfile.TemporaryDirectory() as work: with tempfile.TemporaryDirectory() as work:
pip = '/opt/python/{}/bin/pip'.format(python) pip = f'/opt/python/{python}/bin/pip'
check_call( check_call(
'docker', 'run', '-ti', 'docker', 'run', '-ti',
# Use this so the files are not owned by root # Use this so the files are not owned by root
'--user', '{}:{}'.format(os.getuid(), os.getgid()), '--user', f'{os.getuid()}:{os.getgid()}',
# We'll do building in /work and copy results to /dist # We'll do building in /work and copy results to /dist
'-v', '{}:/work:rw'.format(work), '-v', f'{work}:/work:rw',
'-v', '{}:/dist:rw'.format(os.path.abspath('dist')), '-v', '{}:/dist:rw'.format(os.path.abspath('dist')),
'quay.io/pypa/manylinux1_x86_64:latest', 'quay.io/pypa/manylinux1_x86_64:latest',
'bash', '-exc', 'bash', '-exc',
@ -40,4 +40,4 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
exit(main()) raise SystemExit(main())

View File

@ -56,4 +56,4 @@ def main() -> int:
if __name__ == '__main__': if __name__ == '__main__':
exit(main()) raise SystemExit(main())

12
debian/changelog vendored
View File

@ -1,12 +0,0 @@
libsass-python (0.21.0-ok2) yangtze; urgency=medium
* Fix command 'install' has no such option 'install_layout'.
Add pysassc-man.patch.
-- sufang <sufang@kylinos.cn> Tue, 11 Oct 2022 11:07:40 +0800
libsass-python (0.21.0-ok1) yangtze; urgency=medium
* Build for openkylin.
-- sufang <sufang@kylinos.cn> Tue, 11 Oct 2022 10:19:07 +0800

47
debian/control vendored
View File

@ -1,47 +0,0 @@
Source: libsass-python
Section: python
Priority: optional
Maintainer: OpenKylin Developers <packaging@lists.openkylin.top>
Build-Depends:
debhelper-compat (= 13),
dh-python,
python3-all-dev,
python3-setuptools,
python3-six,
python3-sphinx,
python3-werkzeug,
python3-flake8,
python3-pytest,
python3-doc,
python-flask-doc,
python-setuptools-doc,
libsass-dev (>= 3.6.5)
Standards-Version: 4.6.0
Vcs-Browser: https://gitee.com/openkylin/libsass-python
Vcs-Git: https://gitee.com/openkylin/libsass-python.git
Homepage: https://sass.github.io/libsass-python
Rules-Requires-Root: no
X-Python-Version: >= 2.6
X-Python3-Version: >= 3.2
Package: python3-libsass
Architecture: any
Depends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends}
Description: SASS for Python 3: a straightforward binding of libsass for Python
This package provides a simple Python 3 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.
Package: pysassc
Section: web
Architecture: all
Depends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends}, python3-libsass
Description: SASS for Python: command line utility for libsass
This package provides a simple Python script to access libsass
functionnalities.
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.

86
debian/copyright vendored
View File

@ -1,86 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: libsass-python
Source: <url://example.com>
#
# Please double check copyright with the licensecheck(1) command.
Files: .ackrc
.coveragerc
.gitignore
.gitmodules
.pre-commit-config.yaml
CODE_OF_CONDUCT.md
CONTRIBUTING.rst
MANIFEST.in
README.rst
_sass.c
azure-pipelines.yml
bin/build-manylinux-wheels
bin/download-windows-wheels
docs/Makefile
docs/changes.rst
docs/conf.py
docs/frameworks/flask.rst
docs/index.rst
docs/make.bat
docs/pysassc.rst
docs/sass.rst
docs/sassutils.rst
docs/sassutils/builder.rst
docs/sassutils/distutils.rst
docs/sassutils/wsgi.rst
pysassc.py
requirements-dev.txt
sass.py
sassc.py
sasstests.py
sassutils/__init__.py
sassutils/_compat.py
sassutils/builder.py
sassutils/distutils.py
sassutils/wsgi.py
setup.cfg
setup.py
test/_f.scss
test/a.scss
test/b.scss
test/c.scss
test/d.scss
test/e.scss
test/g.scss
test/h.sass
test/subdir/_sub.scss
test/subdir/recur.scss
testpkg/setup.py
testpkg/testpkg/__init__.py
testpkg/testpkg/static/css/README
testpkg/testpkg/static/scss/a.scss
tox.ini
Copyright: __NO_COPYRIGHT_NOR_LICENSE__
License: __NO_COPYRIGHT_NOR_LICENSE__
#----------------------------------------------------------------------------
# Files marked as NO_LICENSE_TEXT_FOUND may be covered by the following
# license/copyright files.
#----------------------------------------------------------------------------
# License file: LICENSE
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.

View File

@ -1 +0,0 @@
../docs/changes.rst

View File

@ -1,2 +0,0 @@
usr/bin/sassc
usr/lib/*/dist-packages/__pycache__/*.pyc

View File

@ -1 +0,0 @@
# You must remove unused comment lines for the released package.

View File

@ -1 +0,0 @@
usr/bin/pysassc

View File

@ -1 +0,0 @@
debian/tmp/usr/share/man/man1/pysassc.1

View File

@ -1,4 +0,0 @@
usr/lib/python3*/*-packages/*.py
usr/lib/python3*/*-packages/_sass*.so
usr/lib/python3*/*-packages/*.egg-info/
usr/lib/python3*/*-packages/sassutils/

View File

@ -1 +0,0 @@
usr/share/man/man3/libsass.3

41
debian/rules vendored
View File

@ -1,41 +0,0 @@
#!/usr/bin/make -f
export DH_VERBOSE=1
export PYTHON_EGG_CACHE=$(CURDIR)/debian/tmp
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export SYSTEM_SASS = 1
export SETUPTOOLS_USE_DISTUTILS=stdlib
%: .libsass-upstream-version
dh $@ --with python3 --buildsystem=pybuild
.libsass-upstream-version:
dpkg-query -f '$${Version}\n' -W libsass-dev|sed 's/-.*//' > .libsass-upstream-version
override_dh_auto_build:
pybuild --build -p "$(shell py3versions -vr)"
# build doc once
pybuild -s custom --build -p $(shell py3versions -vd) \
--build-args="env PYTHONPATH={build_dir} python3 -m sphinx -N -bman docs/ build/man"
override_dh_auto_install:
dh_auto_install -O--buildsystem=pybuild
mkdir -p $(CURDIR)/debian/tmp/usr/share/man/man1
mkdir -p $(CURDIR)/debian/tmp/usr/share/man/man3
cp $(CURDIR)/build/man/pysassc.1 $(CURDIR)/debian/tmp/usr/share/man/man1/
cp $(CURDIR)/build/man/libsass.3 $(CURDIR)/debian/tmp/usr/share/man/man3/
rm $(CURDIR)/debian/tmp/usr/lib/python*/dist-packages/sasstests.py
sed -i '1c#!/usr/bin/python3' $(CURDIR)/debian/tmp/usr/bin/pysassc
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
# Skip DistutilsTestCase.* to not rebuild sass again
pybuild -s custom --test -p "$(shell py3versions -vr)" \
--test-args="cd {build_dir}; \
{interpreter} -m pytest -k 'not DistutilsTestCase' sasstests.py"
endif
override_dh_auto_clean:
dh_auto_clean -O--buildsystem=pybuild
rm -fr libsass.egg-info .libsass-upstream-version

View File

@ -1 +0,0 @@
3.0 (native)

View File

@ -1,5 +0,0 @@
---
Bug-Database: https://github.com/dahlia/libsass-python/issues
Bug-Submit: https://github.com/dahlia/libsass-python/issues/new
Repository: https://github.com/dahlia/libsass-python.git
Repository-Browse: https://github.com/dahlia/libsass-python

4
debian/watch vendored
View File

@ -1,4 +0,0 @@
version=4
opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%" \
https://github.com/dahlia/libsass-python/tags \
(?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate

View File

@ -1,7 +1,15 @@
Changelog Changelog
========= =========
Version 0.21.1 Version 0.22.0
--------------
Released on November 12, 2022.
- Remove python 2.x support [:issue:`373` by anthony sottile].
- Remove deprecated ``sassc`` cli [:issue:`379` by anthony sottile].
Version 0.21.0
-------------- --------------
Released on May 20, 2021. Released on May 20, 2021.

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# libsass documentation build configuration file, created by # libsass documentation build configuration file, created by
# sphinx-quickstart on Sun Aug 19 22:45:57 2012. # sphinx-quickstart on Sun Aug 19 22:45:57 2012.
@ -14,12 +13,13 @@ import os
import sys import sys
import warnings import warnings
import sass
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
import sass
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
@ -48,8 +48,8 @@ source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'libsass' project = 'libsass'
copyright = u'2012, Hong Minhee' copyright = '2012, Hong Minhee'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@ -194,8 +194,8 @@ latex_elements = {
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
( (
'index', 'libsass.tex', u'libsass Documentation', 'index', 'libsass.tex', 'libsass Documentation',
u'Hong Minhee', 'manual', 'Hong Minhee', 'manual',
), ),
] ]
@ -226,10 +226,8 @@ latex_documents = [
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
( (
'index', 'libsass', u'libsass Documentation', [u'Hong Minhee'], 3, 'index', 'libsass', 'libsass Documentation',
), ['Hong Minhee'], 1,
(
'pysassc', 'pysassc', u'pysassc Documentation', [u'Hong Minhee'], 1,
), ),
] ]
@ -244,8 +242,8 @@ man_pages = [
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
( (
'index', 'libsass', u'libsass Documentation', 'index', 'libsass', 'libsass Documentation',
u'Hong Minhee', 'libsass', 'One line description of project.', 'Hong Minhee', 'libsass', 'One line description of project.',
'Miscellaneous', 'Miscellaneous',
), ),
] ]
@ -271,7 +269,7 @@ intersphinx_mapping = {
extlinks = { extlinks = {
'issue': ('https://github.com/sass/libsass-python/issues/%s', '#'), 'issue': ('https://github.com/sass/libsass-python/issues/%s', '#'),
'branch': ( 'branch': (
'https://github.com/sass/libsass-python/compare/master...%s', 'https://github.com/sass/libsass-python/compare/main...%s',
'', '',
), ),
'commit': ('https://github.com/sass/libsass-python/commit/%s', ''), 'commit': ('https://github.com/sass/libsass-python/commit/%s', ''),

View File

@ -8,7 +8,7 @@ distribution/deployment. That means you can add just ``libsass`` into
your :file:`setup.py`'s ``install_requires`` list or :file:`requirements.txt` your :file:`setup.py`'s ``install_requires`` list or :file:`requirements.txt`
file. file.
It currently supports CPython 2.6, 2.7, 3.5--3.7, and PyPy 2.3+! It currently supports CPython 3.6+ and PyPy 3!
.. _Sass: https://sass-lang.com/ .. _Sass: https://sass-lang.com/
.. _LibSass: https://github.com/sass/libsass .. _LibSass: https://github.com/sass/libsass
@ -133,17 +133,17 @@ GitHub (Git repository + issues)
https://github.com/sass/libsass-python https://github.com/sass/libsass-python
Azure Pipelines CI (linux + windows) Azure Pipelines CI (linux + windows)
https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
.. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=master .. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=main
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
:alt: Build Status :alt: Build Status
Azure Pipelines Coverage (Test coverage) Azure Pipelines Coverage (Test coverage)
https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
.. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/master.svg .. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/main.svg
:target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main
:alt: Coverage Status :alt: Coverage Status
PyPI PyPI

View File

@ -88,10 +88,7 @@ There are options as well:
.. _SassC: https://github.com/sass/sassc .. _SassC: https://github.com/sass/sassc
""" """
from __future__ import print_function
import functools import functools
import io
import optparse import optparse
import sys import sys
import warnings import warnings
@ -219,7 +216,7 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr):
include_paths=options.include_paths, include_paths=options.include_paths,
precision=options.precision, precision=options.precision,
) )
except (IOError, OSError) as e: except OSError as e:
error(e) error(e)
return 3 return 3
except sass.CompileError as e: except sass.CompileError as e:
@ -229,10 +226,10 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr):
if len(args) < 2: if len(args) < 2:
print(css, file=stdout) print(css, file=stdout)
else: else:
with io.open(args[1], 'w', encoding='utf-8', newline='') as f: with open(args[1], 'w', encoding='utf-8', newline='') as f:
f.write(css) f.write(css)
if source_map_filename: if source_map_filename:
with io.open( with open(
source_map_filename, 'w', encoding='utf-8', newline='', source_map_filename, 'w', encoding='utf-8', newline='',
) as f: ) as f:
f.write(source_map) f.write(source_map)

83
sass.py
View File

@ -10,28 +10,21 @@ type.
'a b {\n color: blue; }\n' 'a b {\n color: blue; }\n'
""" """
from __future__ import absolute_import import collections.abc
import collections
import inspect import inspect
import io
import os
import os.path import os.path
import re import re
import sys import sys
import warnings import warnings
from six import string_types, text_type, PY2
import _sass import _sass
from sassutils._compat import collections_abc
__all__ = ( __all__ = (
'MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError', 'SassColor', 'MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError', 'SassColor',
'SassError', 'SassFunction', 'SassList', 'SassMap', 'SassNumber', 'SassError', 'SassFunction', 'SassList', 'SassMap', 'SassNumber',
'SassWarning', 'and_join', 'compile', 'libsass_version', 'SassWarning', 'and_join', 'compile', 'libsass_version',
) )
__version__ = '0.21.0' __version__ = '0.22.0'
libsass_version = _sass.libsass_version libsass_version = _sass.libsass_version
@ -52,10 +45,9 @@ MODES = frozenset(('string', 'filename', 'dirname'))
def to_native_s(s): def to_native_s(s):
if isinstance(s, bytes) and not PY2: # pragma: no cover (py3) if isinstance(s, bytes):
s = s.decode('UTF-8') return s.decode('UTF-8')
elif isinstance(s, text_type) and PY2: # pragma: no cover (py2) else:
s = s.encode('UTF-8')
return s return s
@ -63,8 +55,9 @@ class CompileError(ValueError):
"""The exception type that is raised by :func:`compile()`. """The exception type that is raised by :func:`compile()`.
It is a subtype of :exc:`exceptions.ValueError`. It is a subtype of :exc:`exceptions.ValueError`.
""" """
def __init__(self, msg): def __init__(self, msg):
super(CompileError, self).__init__(to_native_s(msg)) super().__init__(to_native_s(msg))
def mkdirp(path): def mkdirp(path):
@ -76,7 +69,7 @@ def mkdirp(path):
raise raise
class SassFunction(object): class SassFunction:
"""Custom function for Sass. It can be instantiated using """Custom function for Sass. It can be instantiated using
:meth:`from_lambda()` and :meth:`from_named_function()` as well. :meth:`from_lambda()` and :meth:`from_named_function()` as well.
@ -107,12 +100,6 @@ class SassFunction(object):
:rtype: :class:`SassFunction` :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_) a = inspect.getfullargspec(lambda_)
varargs, varkw, defaults, kwonlyargs = ( varargs, varkw, defaults, kwonlyargs = (
a.varargs, a.varkw, a.defaults, a.kwonlyargs, a.varargs, a.varkw, a.defaults, a.kwonlyargs,
@ -142,9 +129,9 @@ class SassFunction(object):
return cls.from_lambda(function.__name__, function) return cls.from_lambda(function.__name__, function)
def __init__(self, name, arguments, callable_): def __init__(self, name, arguments, callable_):
if not isinstance(name, string_types): if not isinstance(name, str):
raise TypeError('name must be a string, not ' + repr(name)) raise TypeError('name must be a string, not ' + repr(name))
elif not isinstance(arguments, collections_abc.Sequence): elif not isinstance(arguments, collections.abc.Sequence):
raise TypeError( raise TypeError(
'arguments must be a sequence, not ' + 'arguments must be a sequence, not ' +
repr(arguments), repr(arguments),
@ -263,7 +250,7 @@ def compile_dirname(
if s: if s:
v = v.decode('UTF-8') v = v.decode('UTF-8')
mkdirp(os.path.dirname(output_filename)) mkdirp(os.path.dirname(output_filename))
with io.open( with open(
output_filename, 'w', encoding='UTF-8', newline='', output_filename, 'w', encoding='UTF-8', newline='',
) as output_file: ) as output_file:
output_file.write(v) output_file.write(v)
@ -277,7 +264,7 @@ def _check_no_remaining_kwargs(func, kwargs):
raise TypeError( raise TypeError(
'{}() got unexpected keyword argument(s) {}'.format( '{}() got unexpected keyword argument(s) {}'.format(
func.__name__, func.__name__,
', '.join("'{}'".format(arg) for arg in sorted(kwargs)), ', '.join(f"'{arg}'" for arg in sorted(kwargs)),
), ),
) )
@ -563,7 +550,7 @@ def compile(**kwargs):
) )
precision = kwargs.pop('precision', 5) precision = kwargs.pop('precision', 5)
output_style = kwargs.pop('output_style', 'nested') output_style = kwargs.pop('output_style', 'nested')
if not isinstance(output_style, string_types): if not isinstance(output_style, str):
raise TypeError( raise TypeError(
'output_style must be a string, not ' + 'output_style must be a string, not ' +
repr(output_style), repr(output_style),
@ -586,7 +573,7 @@ def compile(**kwargs):
elif source_comments in ('line_numbers', 'default'): elif source_comments in ('line_numbers', 'default'):
deprecation_message = ( deprecation_message = (
'you can simply pass True to ' 'you can simply pass True to '
"source_comments instead of " + 'source_comments instead of ' +
repr(source_comments) repr(source_comments)
) )
source_comments = True source_comments = True
@ -612,9 +599,9 @@ def compile(**kwargs):
def _get_file_arg(key): def _get_file_arg(key):
ret = kwargs.pop(key, None) ret = kwargs.pop(key, None)
if ret is not None and not isinstance(ret, string_types): if ret is not None and not isinstance(ret, str):
raise TypeError('{} must be a string, not {!r}'.format(key, ret)) raise TypeError(f'{key} must be a string, not {ret!r}')
elif isinstance(ret, text_type): elif isinstance(ret, str):
ret = ret.encode(fs_encoding) ret = ret.encode(fs_encoding)
if ret and 'filename' not in modes: if ret and 'filename' not in modes:
raise CompileError( raise CompileError(
@ -631,25 +618,25 @@ def compile(**kwargs):
omit_source_map_url = kwargs.pop('omit_source_map_url', False) omit_source_map_url = kwargs.pop('omit_source_map_url', False)
source_map_root = kwargs.pop('source_map_root', None) source_map_root = kwargs.pop('source_map_root', None)
if isinstance(source_map_root, text_type): if isinstance(source_map_root, str):
source_map_root = source_map_root.encode('utf-8') source_map_root = source_map_root.encode('utf-8')
# #208: cwd is always included in include paths # #208: cwd is always included in include paths
include_paths = (os.getcwd(),) include_paths = (os.getcwd(),)
include_paths += tuple(kwargs.pop('include_paths', ()) or ()) include_paths += tuple(kwargs.pop('include_paths', ()) or ())
include_paths = os.pathsep.join(include_paths) include_paths = os.pathsep.join(include_paths)
if isinstance(include_paths, text_type): if isinstance(include_paths, str):
include_paths = include_paths.encode(fs_encoding) include_paths = include_paths.encode(fs_encoding)
custom_functions = kwargs.pop('custom_functions', ()) custom_functions = kwargs.pop('custom_functions', ())
if isinstance(custom_functions, collections_abc.Mapping): if isinstance(custom_functions, collections.abc.Mapping):
custom_functions = [ custom_functions = [
SassFunction.from_lambda(name, lambda_) SassFunction.from_lambda(name, lambda_)
for name, lambda_ in custom_functions.items() for name, lambda_ in custom_functions.items()
] ]
elif isinstance( elif isinstance(
custom_functions, custom_functions,
(collections_abc.Set, collections_abc.Sequence), (collections.abc.Set, collections.abc.Sequence),
): ):
custom_functions = [ custom_functions = [
func if isinstance(func, SassFunction) func if isinstance(func, SassFunction)
@ -676,7 +663,7 @@ def compile(**kwargs):
if 'string' in modes: if 'string' in modes:
string = kwargs.pop('string') string = kwargs.pop('string')
if isinstance(string, text_type): if isinstance(string, str):
string = string.encode('utf-8') string = string.encode('utf-8')
indented = kwargs.pop('indented', False) indented = kwargs.pop('indented', False)
if not isinstance(indented, bool): if not isinstance(indented, bool):
@ -695,11 +682,11 @@ def compile(**kwargs):
return v.decode('utf-8') return v.decode('utf-8')
elif 'filename' in modes: elif 'filename' in modes:
filename = kwargs.pop('filename') filename = kwargs.pop('filename')
if not isinstance(filename, string_types): if not isinstance(filename, str):
raise TypeError('filename must be a string, not ' + repr(filename)) raise TypeError('filename must be a string, not ' + repr(filename))
elif not os.path.isfile(filename): elif not os.path.isfile(filename):
raise IOError('{!r} seems not a file'.format(filename)) raise OSError(f'{filename!r} seems not a file')
elif isinstance(filename, text_type): elif isinstance(filename, str):
filename = filename.encode(fs_encoding) filename = filename.encode(fs_encoding)
_check_no_remaining_kwargs(compile, kwargs) _check_no_remaining_kwargs(compile, kwargs)
s, v, source_map = _sass.compile_filename( s, v, source_map = _sass.compile_filename(
@ -780,9 +767,9 @@ class SassNumber(collections.namedtuple('SassNumber', ('value', 'unit'))):
def __new__(cls, value, unit): def __new__(cls, value, unit):
value = float(value) value = float(value)
if not isinstance(unit, text_type): if not isinstance(unit, str):
unit = unit.decode('UTF-8') unit = unit.decode('UTF-8')
return super(SassNumber, cls).__new__(cls, value, unit) return super().__new__(cls, value, unit)
class SassColor(collections.namedtuple('SassColor', ('r', 'g', 'b', 'a'))): class SassColor(collections.namedtuple('SassColor', ('r', 'g', 'b', 'a'))):
@ -792,7 +779,7 @@ class SassColor(collections.namedtuple('SassColor', ('r', 'g', 'b', 'a'))):
g = float(g) g = float(g)
b = float(b) b = float(b)
a = float(a) a = float(a)
return super(SassColor, cls).__new__(cls, r, g, b, a) return super().__new__(cls, r, g, b, a)
SASS_SEPARATOR_COMMA = collections.namedtuple('SASS_SEPARATOR_COMMA', ())() SASS_SEPARATOR_COMMA = collections.namedtuple('SASS_SEPARATOR_COMMA', ())()
@ -810,26 +797,26 @@ class SassList(
items = tuple(items) items = tuple(items)
assert separator in SEPARATORS, separator assert separator in SEPARATORS, separator
assert isinstance(bracketed, bool), bracketed assert isinstance(bracketed, bool), bracketed
return super(SassList, cls).__new__(cls, items, separator, bracketed) return super().__new__(cls, items, separator, bracketed)
class SassError(collections.namedtuple('SassError', ('msg',))): class SassError(collections.namedtuple('SassError', ('msg',))):
def __new__(cls, msg): def __new__(cls, msg):
if not isinstance(msg, text_type): if not isinstance(msg, str):
msg = msg.decode('UTF-8') msg = msg.decode('UTF-8')
return super(SassError, cls).__new__(cls, msg) return super().__new__(cls, msg)
class SassWarning(collections.namedtuple('SassWarning', ('msg',))): class SassWarning(collections.namedtuple('SassWarning', ('msg',))):
def __new__(cls, msg): def __new__(cls, msg):
if not isinstance(msg, text_type): if not isinstance(msg, str):
msg = msg.decode('UTF-8') msg = msg.decode('UTF-8')
return super(SassWarning, cls).__new__(cls, msg) return super().__new__(cls, msg)
class SassMap(collections_abc.Mapping): class SassMap(collections.abc.Mapping):
"""Because sass maps can have mapping types as keys, we need an immutable """Because sass maps can have mapping types as keys, we need an immutable
hashable mapping type. hashable mapping type.
@ -858,7 +845,7 @@ class SassMap(collections_abc.Mapping):
# Our interface # Our interface
def __repr__(self): def __repr__(self):
return '{}({})'.format(type(self).__name__, frozenset(self.items())) return f'{type(self).__name__}({frozenset(self.items())})'
def __hash__(self): def __hash__(self):
return self._hash return self._hash

View File

@ -1,15 +0,0 @@
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())

View File

@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-
import base64 import base64
import collections.abc
import contextlib import contextlib
import functools import functools
import glob import glob
import json
import io import io
import os import json
import os.path import os.path
import re import re
import shutil import shutil
@ -17,15 +15,13 @@ import traceback
import unittest import unittest
import pytest import pytest
from six import StringIO, b, string_types, text_type
from werkzeug.test import Client from werkzeug.test import Client
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
import pysassc import pysassc
import sass import sass
import sassc from sassutils.builder import build_directory
from sassutils._compat import collections_abc from sassutils.builder import Manifest
from sassutils.builder import Manifest, build_directory
from sassutils.wsgi import SassMiddleware from sassutils.wsgi import SassMiddleware
@ -71,7 +67,7 @@ A_EXPECTED_MAP = {
), ),
} }
with io.open('test/a.scss', newline='') as f: with open('test/a.scss', newline='') as f:
A_EXPECTED_MAP_CONTENTS = dict(A_EXPECTED_MAP, sourcesContent=[f.read()]) A_EXPECTED_MAP_CONTENTS = dict(A_EXPECTED_MAP, sourcesContent=[f.read()])
B_EXPECTED_CSS = '''\ B_EXPECTED_CSS = '''\
@ -95,7 +91,7 @@ h1 a {
color: green; } color: green; }
''' '''
D_EXPECTED_CSS = u'''\ D_EXPECTED_CSS = '''\
@charset "UTF-8"; @charset "UTF-8";
body { body {
background-color: green; } background-color: green; }
@ -103,7 +99,7 @@ body {
font: '나눔고딕', sans-serif; } font: '나눔고딕', sans-serif; }
''' '''
D_EXPECTED_CSS_WITH_MAP = u'''\ D_EXPECTED_CSS_WITH_MAP = '''\
@charset "UTF-8"; @charset "UTF-8";
body { body {
background-color: green; } background-color: green; }
@ -149,7 +145,7 @@ re_base64_data_uri = re.compile(r'^data:[^;]*?;base64,(.+)$')
def _map_in_output_dir(s): def _map_in_output_dir(s):
def cb(match): def cb(match):
filename = os.path.basename(match.group(1)) filename = os.path.basename(match.group(1))
return '/*# sourceMappingURL={} */'.format(filename) return f'/*# sourceMappingURL={filename} */'
return re_sourcemap_url.sub(cb, s) return re_sourcemap_url.sub(cb, s)
@ -163,9 +159,9 @@ def no_warnings(recwarn):
class BaseTestCase(unittest.TestCase): class BaseTestCase(unittest.TestCase):
def assert_source_map_equal(self, expected, actual): def assert_source_map_equal(self, expected, actual):
if isinstance(expected, string_types): if isinstance(expected, str):
expected = json.loads(expected) expected = json.loads(expected)
if isinstance(actual, string_types): if isinstance(actual, str):
actual = json.loads(actual) actual = json.loads(actual)
assert expected == actual assert expected == actual
@ -175,7 +171,7 @@ class BaseTestCase(unittest.TestCase):
tree = json.load(f) tree = json.load(f)
except ValueError as e: # pragma: no cover except ValueError as e: # pragma: no cover
f.seek(0) f.seek(0)
msg = '{!s}\n\n{}:\n\n{}'.format(e, filename, f.read()) msg = f'{e!s}\n\n{filename}:\n\n{f.read()}'
raise ValueError(msg) raise ValueError(msg)
self.assert_source_map_equal(expected, tree) self.assert_source_map_equal(expected, tree)
@ -196,7 +192,7 @@ class SassTestCase(BaseTestCase):
assert re.match(r'^\d+\.\d+\.\d+$', sass.__version__) assert re.match(r'^\d+\.\d+\.\d+$', sass.__version__)
def test_output_styles(self): def test_output_styles(self):
assert isinstance(sass.OUTPUT_STYLES, collections_abc.Mapping) assert isinstance(sass.OUTPUT_STYLES, collections.abc.Mapping)
assert 'nested' in sass.OUTPUT_STYLES assert 'nested' in sass.OUTPUT_STYLES
def test_and_join(self): def test_and_join(self):
@ -294,9 +290,9 @@ a {
a b { a b {
color: blue; } color: blue; }
''' '''
actual = sass.compile(string=u'a { color: blue; } /* 유니코드 */') actual = sass.compile(string='a { color: blue; } /* 유니코드 */')
self.assertEqual( self.assertEqual(
u'''@charset "UTF-8"; '''@charset "UTF-8";
a { a {
color: blue; } color: blue; }
@ -330,11 +326,11 @@ a {
def test_importer_one_arg(self): def test_importer_one_arg(self):
"""Demonstrates one-arg importers + chaining.""" """Demonstrates one-arg importers + chaining."""
def importer_returning_one_argument(path): def importer_returning_one_argument(path):
assert type(path) is text_type assert type(path) is str
return ( return (
# Trigger the import of an actual file # Trigger the import of an actual file
('test/b.scss',), ('test/b.scss',),
(path, '.{0}-one-arg {{ color: blue; }}'.format(path)), (path, f'.{path}-one-arg {{ color: blue; }}'),
) )
ret = sass.compile( ret = sass.compile(
@ -428,11 +424,11 @@ a {
path, path,
'a { color: red; }', 'a { color: red; }',
json.dumps({ json.dumps({
"version": 3, 'version': 3,
"sources": [ 'sources': [
path + ".db", path + '.db',
], ],
"mappings": ";AAAA,CAAC,CAAC;EAAE,KAAK,EAAE,GAAI,GAAI", 'mappings': ';AAAA,CAAC,CAAC;EAAE,KAAK,EAAE,GAAI,GAAI',
}), }),
), ),
) )
@ -448,7 +444,7 @@ a {
def test_importers_raises_exception(self): def test_importers_raises_exception(self):
def importer(path): def importer(path):
raise ValueError('Bad path: {}'.format(path)) raise ValueError(f'Bad path: {path}')
with assert_raises_compile_error( with assert_raises_compile_error(
RegexMatcher( RegexMatcher(
@ -640,31 +636,31 @@ class BuilderTestCase(BaseTestCase):
result_files = build_directory(self.sass_path, css_path) result_files = build_directory(self.sass_path, css_path)
assert len(result_files) == 8 assert len(result_files) == 8
assert 'a.scss.css' == result_files['a.scss'] assert 'a.scss.css' == result_files['a.scss']
with io.open( with open(
os.path.join(css_path, 'a.scss.css'), encoding='UTF-8', os.path.join(css_path, 'a.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
assert A_EXPECTED_CSS == css assert A_EXPECTED_CSS == css
assert 'b.scss.css' == result_files['b.scss'] assert 'b.scss.css' == result_files['b.scss']
with io.open( with open(
os.path.join(css_path, 'b.scss.css'), encoding='UTF-8', os.path.join(css_path, 'b.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
assert B_EXPECTED_CSS == css assert B_EXPECTED_CSS == css
assert 'c.scss.css' == result_files['c.scss'] assert 'c.scss.css' == result_files['c.scss']
with io.open( with open(
os.path.join(css_path, 'c.scss.css'), encoding='UTF-8', os.path.join(css_path, 'c.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
assert C_EXPECTED_CSS == css assert C_EXPECTED_CSS == css
assert 'd.scss.css' == result_files['d.scss'] assert 'd.scss.css' == result_files['d.scss']
with io.open( with open(
os.path.join(css_path, 'd.scss.css'), encoding='UTF-8', os.path.join(css_path, 'd.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
assert D_EXPECTED_CSS == css assert D_EXPECTED_CSS == css
assert 'e.scss.css' == result_files['e.scss'] assert 'e.scss.css' == result_files['e.scss']
with io.open( with open(
os.path.join(css_path, 'e.scss.css'), encoding='UTF-8', os.path.join(css_path, 'e.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
@ -673,7 +669,7 @@ class BuilderTestCase(BaseTestCase):
os.path.join('subdir', 'recur.scss.css'), os.path.join('subdir', 'recur.scss.css'),
result_files[os.path.join('subdir', 'recur.scss')], result_files[os.path.join('subdir', 'recur.scss')],
) )
with io.open( with open(
os.path.join(css_path, 'g.scss.css'), encoding='UTF-8', os.path.join(css_path, 'g.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
@ -683,12 +679,12 @@ class BuilderTestCase(BaseTestCase):
result_files[os.path.join('subdir', 'recur.scss')], result_files[os.path.join('subdir', 'recur.scss')],
) )
assert 'h.sass.css' == result_files['h.sass'] assert 'h.sass.css' == result_files['h.sass']
with io.open( with open(
os.path.join(css_path, 'h.sass.css'), encoding='UTF-8', os.path.join(css_path, 'h.sass.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
assert H_EXPECTED_CSS == css assert H_EXPECTED_CSS == css
with io.open( with open(
os.path.join(css_path, 'subdir', 'recur.scss.css'), os.path.join(css_path, 'subdir', 'recur.scss.css'),
encoding='UTF-8', encoding='UTF-8',
) as f: ) as f:
@ -703,7 +699,7 @@ class BuilderTestCase(BaseTestCase):
) )
assert len(result_files) == 8 assert len(result_files) == 8
assert 'a.scss.css' == result_files['a.scss'] assert 'a.scss.css' == result_files['a.scss']
with io.open( with open(
os.path.join(css_path, 'a.scss.css'), encoding='UTF-8', os.path.join(css_path, 'a.scss.css'), encoding='UTF-8',
) as f: ) as f:
css = f.read() css = f.read()
@ -755,7 +751,7 @@ class ManifestTestCase(BaseTestCase):
with open(os.path.join(d, 'css', 'a.scss.css')) as f: with open(os.path.join(d, 'css', 'a.scss.css')) as f:
assert A_EXPECTED_CSS == f.read() assert A_EXPECTED_CSS == f.read()
m.build_one(d, 'b.scss', source_map=True) m.build_one(d, 'b.scss', source_map=True)
with io.open( with open(
os.path.join(d, 'css', 'b.scss.css'), encoding='UTF-8', os.path.join(d, 'css', 'b.scss.css'), encoding='UTF-8',
) as f: ) as f:
assert f.read() == _map_in_output_dir(B_EXPECTED_CSS_WITH_MAP) assert f.read() == _map_in_output_dir(B_EXPECTED_CSS_WITH_MAP)
@ -773,7 +769,7 @@ class ManifestTestCase(BaseTestCase):
os.path.join(d, 'css', 'b.scss.css.map'), os.path.join(d, 'css', 'b.scss.css.map'),
) )
m.build_one(d, 'd.scss', source_map=True) m.build_one(d, 'd.scss', source_map=True)
with io.open( with open(
os.path.join(d, 'css', 'd.scss.css'), encoding='UTF-8', os.path.join(d, 'css', 'd.scss.css'), encoding='UTF-8',
) as f: ) as f:
assert f.read() == _map_in_output_dir(D_EXPECTED_CSS_WITH_MAP) assert f.read() == _map_in_output_dir(D_EXPECTED_CSS_WITH_MAP)
@ -838,7 +834,7 @@ class WsgiTestCase(BaseTestCase):
r = client.get('/static/a.scss.css') r = client.get('/static/a.scss.css')
assert r.status_code == 200 assert r.status_code == 200
self.assertEqual( self.assertEqual(
b(_map_in_output_dir(A_EXPECTED_CSS_WITH_MAP)), _map_in_output_dir(A_EXPECTED_CSS_WITH_MAP).encode(),
r.data, r.data,
) )
assert r.mimetype == 'text/css' assert r.mimetype == 'text/css'
@ -903,7 +899,7 @@ class DistutilsTestCase(BaseTestCase):
return os.path.join( return os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'testpkg', 'testpkg', 'static', 'css', 'testpkg', 'testpkg', 'static', 'css',
*args *args,
) )
def list_built_css(self): def list_built_css(self):
@ -942,8 +938,8 @@ class DistutilsTestCase(BaseTestCase):
class SasscTestCase(BaseTestCase): class SasscTestCase(BaseTestCase):
def setUp(self): def setUp(self):
self.out = StringIO() self.out = io.StringIO()
self.err = StringIO() self.err = io.StringIO()
def test_no_args(self): def test_no_args(self):
exit_code = pysassc.main(['pysassc'], self.out, self.err) exit_code = pysassc.main(['pysassc'], self.out, self.err)
@ -973,17 +969,6 @@ class SasscTestCase(BaseTestCase):
assert self.err.getvalue() == '' assert self.err.getvalue() == ''
assert A_EXPECTED_CSS.strip() == self.out.getvalue().strip() assert A_EXPECTED_CSS.strip() == self.out.getvalue().strip()
def test_sassc_stdout(self):
with pytest.warns(FutureWarning) as warninfo:
exit_code = sassc.main(
['sassc', 'test/a.scss'],
self.out, self.err,
)
assert 'use `pysassc`' in warninfo[0].message.args[0]
assert exit_code == 0
assert self.err.getvalue() == ''
assert A_EXPECTED_CSS.strip() == self.out.getvalue().strip()
def test_pysassc_output(self): def test_pysassc_output(self):
fd, tmp = tempfile.mkstemp('.css') fd, tmp = tempfile.mkstemp('.css')
try: try:
@ -995,7 +980,7 @@ class SasscTestCase(BaseTestCase):
assert exit_code == 0 assert exit_code == 0
assert self.err.getvalue() == '' assert self.err.getvalue() == ''
assert self.out.getvalue() == '' assert self.out.getvalue() == ''
with io.open(tmp, encoding='UTF-8', newline='') as f: with open(tmp, encoding='UTF-8', newline='') as f:
assert A_EXPECTED_CSS.strip() == f.read().strip() assert A_EXPECTED_CSS.strip() == f.read().strip()
finally: finally:
os.remove(tmp) os.remove(tmp)
@ -1011,7 +996,7 @@ class SasscTestCase(BaseTestCase):
assert exit_code == 0 assert exit_code == 0
assert self.err.getvalue() == '' assert self.err.getvalue() == ''
assert self.out.getvalue() == '' assert self.out.getvalue() == ''
with io.open(tmp, encoding='UTF-8') as f: with open(tmp, encoding='UTF-8') as f:
assert D_EXPECTED_CSS.strip() == f.read().strip() assert D_EXPECTED_CSS.strip() == f.read().strip()
finally: finally:
os.remove(tmp) os.remove(tmp)
@ -1093,10 +1078,10 @@ class CompileDirectoriesTest(unittest.TestCase):
input_dir = os.path.join(tmpdir, 'input') input_dir = os.path.join(tmpdir, 'input')
output_dir = os.path.join(tmpdir, 'output') output_dir = os.path.join(tmpdir, 'output')
os.makedirs(input_dir) os.makedirs(input_dir)
with io.open( with open(
os.path.join(input_dir, 'test.scss'), 'w', encoding='UTF-8', os.path.join(input_dir, 'test.scss'), 'w', encoding='UTF-8',
) as f: ) as f:
f.write(u'a { content: ""; }') f.write('a { content: ""; }')
# Raised a UnicodeEncodeError in py2 before #82 (issue #72) # Raised a UnicodeEncodeError in py2 before #82 (issue #72)
# Also raised a UnicodeEncodeError in py3 if the default encoding # Also raised a UnicodeEncodeError in py3 if the default encoding
# couldn't represent it (such as cp1252 on windows) # couldn't represent it (such as cp1252 on windows)
@ -1131,7 +1116,7 @@ class CompileDirectoriesTest(unittest.TestCase):
class SassFunctionTest(unittest.TestCase): class SassFunctionTest(unittest.TestCase):
def test_from_lambda(self): def test_from_lambda(self):
lambda_ = lambda abc, d: None # pragma: no branch # noqa: E731 def lambda_(abc, d): return None # pragma: no branch # noqa: E731
sf = sass.SassFunction.from_lambda('func_name', lambda_) sf = sass.SassFunction.from_lambda('func_name', lambda_)
assert 'func_name' == sf.name assert 'func_name' == sf.name
assert ('$abc', '$d') == sf.arguments assert ('$abc', '$d') == sf.arguments
@ -1164,14 +1149,14 @@ def test_sass_func_type_errors(func):
class SassTypesTest(unittest.TestCase): class SassTypesTest(unittest.TestCase):
def test_number_no_conversion(self): def test_number_no_conversion(self):
num = sass.SassNumber(123., u'px') num = sass.SassNumber(123., 'px')
assert type(num.value) is float, type(num.value) assert type(num.value) is float, type(num.value)
assert type(num.unit) is text_type, type(num.unit) assert type(num.unit) is str, type(num.unit)
def test_number_conversion(self): def test_number_conversion(self):
num = sass.SassNumber(123, b'px') num = sass.SassNumber(123, b'px')
assert type(num.value) is float, type(num.value) assert type(num.value) is float, type(num.value)
assert type(num.unit) is text_type, type(num.unit) assert type(num.unit) is str, type(num.unit)
def test_color_no_conversion(self): def test_color_no_conversion(self):
color = sass.SassColor(1., 2., 3., .5) color = sass.SassColor(1., 2., 3., .5)
@ -1198,20 +1183,20 @@ class SassTypesTest(unittest.TestCase):
assert lst.separator is sass.SASS_SEPARATOR_SPACE, lst.separator assert lst.separator is sass.SASS_SEPARATOR_SPACE, lst.separator
def test_sass_warning_no_conversion(self): def test_sass_warning_no_conversion(self):
warn = sass.SassWarning(u'error msg') warn = sass.SassWarning('error msg')
assert type(warn.msg) is text_type, type(warn.msg) assert type(warn.msg) is str, type(warn.msg)
def test_sass_warning_no_conversion_bytes_message(self): def test_sass_warning_no_conversion_bytes_message(self):
warn = sass.SassWarning(b'error msg') warn = sass.SassWarning(b'error msg')
assert type(warn.msg) is text_type, type(warn.msg) assert type(warn.msg) is str, type(warn.msg)
def test_sass_error_no_conversion(self): def test_sass_error_no_conversion(self):
err = sass.SassError(u'error msg') err = sass.SassError('error msg')
assert type(err.msg) is text_type, type(err.msg) assert type(err.msg) is str, type(err.msg)
def test_sass_error_conversion(self): def test_sass_error_conversion(self):
err = sass.SassError(b'error msg') err = sass.SassError(b'error msg')
assert type(err.msg) is text_type, type(err.msg) assert type(err.msg) is str, type(err.msg)
def raises(): def raises():
@ -1244,11 +1229,11 @@ def returns_none():
def returns_unicode(): def returns_unicode():
return u'' return ''
def returns_bytes(): def returns_bytes():
return u''.encode('UTF-8') return ''.encode()
def returns_number(): def returns_number():
@ -1380,7 +1365,7 @@ def assert_raises_compile_error(expected):
assert msg == expected, (msg, expected) assert msg == expected, (msg, expected)
class RegexMatcher(object): class RegexMatcher:
def __init__(self, reg, flags=None): def __init__(self, reg, flags=None):
self.reg = re.compile(reg, re.MULTILINE | re.DOTALL) self.reg = re.compile(reg, re.MULTILINE | re.DOTALL)
@ -1472,13 +1457,13 @@ class CustomFunctionsTest(unittest.TestCase):
def test_unicode(self): def test_unicode(self):
self.assertEqual( self.assertEqual(
compile_with_func('a { content: returns_unicode(); }'), compile_with_func('a { content: returns_unicode(); }'),
u'\ufeffa{content:☃}\n', '\ufeffa{content:☃}\n',
) )
def test_bytes(self): def test_bytes(self):
self.assertEqual( self.assertEqual(
compile_with_func('a { content: returns_bytes(); }'), compile_with_func('a { content: returns_bytes(); }'),
u'\ufeffa{content:☃}\n', '\ufeffa{content:☃}\n',
) )
def test_number(self): def test_number(self):
@ -1550,7 +1535,7 @@ class CustomFunctionsTest(unittest.TestCase):
def test_identity_strings(self): def test_identity_strings(self):
self.assertEqual( self.assertEqual(
compile_with_func('a { content: identity(returns_unicode()); }'), compile_with_func('a { content: identity(returns_unicode()); }'),
u'\ufeffa{content:☃}\n', '\ufeffa{content:☃}\n',
) )
def test_identity_number(self): def test_identity_number(self):
@ -1626,7 +1611,7 @@ class CustomFunctionsTest(unittest.TestCase):
def test_stack_trace_formatting(): def test_stack_trace_formatting():
try: try:
sass.compile(string=u'a{') sass.compile(string='a{')
raise AssertionError('expected to raise CompileError') raise AssertionError('expected to raise CompileError')
except sass.CompileError: except sass.CompileError:
tb = traceback.format_exc() tb = traceback.format_exc()

View File

@ -1,7 +0,0 @@
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

View File

@ -2,17 +2,12 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
""" """
import collections.abc
import io
import os
import os.path import os.path
import re import re
import warnings import warnings
from six import string_types
from sass import compile from sass import compile
from sassutils._compat import collections_abc
__all__ = 'SUFFIXES', 'SUFFIX_PATTERN', 'Manifest', 'build_directory' __all__ = 'SUFFIXES', 'SUFFIX_PATTERN', 'Manifest', 'build_directory'
@ -69,7 +64,7 @@ def build_directory(
output_style=output_style, output_style=output_style,
include_paths=[_root_sass], include_paths=[_root_sass],
) )
with io.open( with open(
css_fullname, 'w', encoding='utf-8', newline='', css_fullname, 'w', encoding='utf-8', newline='',
) as css_file: ) as css_file:
css_file.write(css) css_file.write(css)
@ -88,7 +83,7 @@ def build_directory(
return result return result
class Manifest(object): class Manifest:
"""Building manifest of Sass/SCSS. """Building manifest of Sass/SCSS.
:param sass_path: the path of the directory that contains Sass/SCSS :param sass_path: the path of the directory that contains Sass/SCSS
@ -105,7 +100,7 @@ class Manifest(object):
def normalize_manifests(cls, manifests): def normalize_manifests(cls, manifests):
if manifests is None: if manifests is None:
manifests = {} manifests = {}
elif isinstance(manifests, collections_abc.Mapping): elif isinstance(manifests, collections.abc.Mapping):
manifests = dict(manifests) manifests = dict(manifests)
else: else:
raise TypeError( raise TypeError(
@ -113,7 +108,7 @@ class Manifest(object):
repr(manifests), repr(manifests),
) )
for package_name, manifest in manifests.items(): for package_name, manifest in manifests.items():
if not isinstance(package_name, string_types): if not isinstance(package_name, str):
raise TypeError( raise TypeError(
'manifest keys must be a string of package ' 'manifest keys must be a string of package '
'name, not ' + repr(package_name), 'name, not ' + repr(package_name),
@ -122,9 +117,9 @@ class Manifest(object):
continue continue
elif isinstance(manifest, tuple): elif isinstance(manifest, tuple):
manifest = Manifest(*manifest) manifest = Manifest(*manifest)
elif isinstance(manifest, collections_abc.Mapping): elif isinstance(manifest, collections.abc.Mapping):
manifest = Manifest(**manifest) manifest = Manifest(**manifest)
elif isinstance(manifest, string_types): elif isinstance(manifest, str):
manifest = Manifest(manifest) manifest = Manifest(manifest)
else: else:
raise TypeError( raise TypeError(
@ -142,21 +137,21 @@ class Manifest(object):
wsgi_path=None, wsgi_path=None,
strip_extension=None, strip_extension=None,
): ):
if not isinstance(sass_path, string_types): if not isinstance(sass_path, str):
raise TypeError( raise TypeError(
'sass_path must be a string, not ' + 'sass_path must be a string, not ' +
repr(sass_path), repr(sass_path),
) )
if css_path is None: if css_path is None:
css_path = sass_path css_path = sass_path
elif not isinstance(css_path, string_types): elif not isinstance(css_path, str):
raise TypeError( raise TypeError(
'css_path must be a string, not ' + 'css_path must be a string, not ' +
repr(css_path), repr(css_path),
) )
if wsgi_path is None: if wsgi_path is None:
wsgi_path = css_path wsgi_path = css_path
elif not isinstance(wsgi_path, string_types): elif not isinstance(wsgi_path, str):
raise TypeError( raise TypeError(
'wsgi_path must be a string, not ' + 'wsgi_path must be a string, not ' +
repr(wsgi_path), repr(wsgi_path),
@ -292,11 +287,11 @@ class Manifest(object):
css_folder = os.path.dirname(css_path) css_folder = os.path.dirname(css_path)
if not os.path.exists(css_folder): if not os.path.exists(css_folder):
os.makedirs(css_folder) os.makedirs(css_folder)
with io.open(css_path, 'w', encoding='utf-8', newline='') as f: with open(css_path, 'w', encoding='utf-8', newline='') as f:
f.write(css) f.write(css)
if source_map: if source_map:
# Source maps are JSON, and JSON has to be UTF-8 encoded # Source maps are JSON, and JSON has to be UTF-8 encoded
with io.open( with open(
source_map_path, 'w', encoding='utf-8', newline='', source_map_path, 'w', encoding='utf-8', newline='',
) as f: ) as f:
f.write(source_map) f.write(source_map)

View File

@ -67,19 +67,17 @@ The option can also be a mapping of package names to manifest dictionaries::
Added ``--output-style``/``-s`` option to :class:`build_sass` command. Added ``--output-style``/``-s`` option to :class:`build_sass` command.
""" """
from __future__ import absolute_import import functools
import os.path
import distutils.errors import distutils.errors
import distutils.log import distutils.log
import distutils.util import distutils.util
import functools
import os.path
from setuptools import Command from setuptools import Command
from setuptools.command.sdist import sdist from setuptools.command.sdist import sdist
from sass import OUTPUT_STYLES
from .builder import Manifest from .builder import Manifest
from sass import OUTPUT_STYLES
__all__ = 'build_sass', 'validate_manifests' __all__ = 'build_sass', 'validate_manifests'

View File

@ -2,22 +2,19 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
""" """
from __future__ import absolute_import import collections.abc
import logging import logging
import os
import os.path import os.path
from pkg_resources import resource_filename from pkg_resources import resource_filename
from sass import CompileError
from sassutils._compat import collections_abc
from .builder import Manifest from .builder import Manifest
from sass import CompileError
__all__ = 'SassMiddleware', __all__ = 'SassMiddleware',
class SassMiddleware(object): class SassMiddleware:
r"""WSGI middleware for development purpose. Every time a CSS file has r"""WSGI middleware for development purpose. Every time a CSS file has
requested it finds a matched Sass/SCSS source file and then compiled requested it finds a matched Sass/SCSS source file and then compiled
it into CSS. it into CSS.
@ -100,7 +97,7 @@ class SassMiddleware(object):
) )
self.app = app self.app = app
self.manifests = Manifest.normalize_manifests(manifests) self.manifests = Manifest.normalize_manifests(manifests)
if not isinstance(package_dir, collections_abc.Mapping): if not isinstance(package_dir, collections.abc.Mapping):
raise TypeError( raise TypeError(
'package_dir must be a mapping object, not ' + 'package_dir must be a mapping object, not ' +
repr(package_dir), repr(package_dir),
@ -138,7 +135,7 @@ class SassMiddleware(object):
sass_filename, sass_filename,
source_map=True, source_map=True,
) )
except (IOError, OSError): except OSError:
break break
except CompileError as e: except CompileError as e:
logger = logging.getLogger(__name__ + '.SassMiddleware') logger = logging.getLogger(__name__ + '.SassMiddleware')

View File

@ -1,11 +1,5 @@
#!/usr/bin/env python
from __future__ import print_function
import ast import ast
import atexit import atexit
import distutils.cmd
import distutils.log
import distutils.sysconfig
import os.path import os.path
import platform import platform
import shutil import shutil
@ -13,7 +7,11 @@ import subprocess
import sys import sys
import tempfile import tempfile
from setuptools import Extension, setup import distutils.cmd
import distutils.log
import distutils.sysconfig
from setuptools import Extension
from setuptools import setup
MACOS_FLAG = ['-mmacosx-version-min=10.7'] MACOS_FLAG = ['-mmacosx-version-min=10.7']
FLAGS_POSIX = [ FLAGS_POSIX = [
@ -83,9 +81,9 @@ else:
libsass_version = libsass_version_file.read().decode('UTF-8').strip() libsass_version = libsass_version_file.read().decode('UTF-8').strip()
if sys.platform == 'win32': if sys.platform == 'win32':
# This looks wrong, but is required for some reason :( # This looks wrong, but is required for some reason :(
define = r'/DLIBSASS_VERSION="\"{}\""'.format(libsass_version) define = fr'/DLIBSASS_VERSION="\"{libsass_version}\""'
else: else:
define = '-DLIBSASS_VERSION="{}"'.format(libsass_version) define = f'-DLIBSASS_VERSION="{libsass_version}"'
for directory in ( for directory in (
os.path.join('libsass', 'src'), os.path.join('libsass', 'src'),
@ -185,7 +183,7 @@ def readme():
try: try:
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f:
return f.read() return f.read()
except IOError: except OSError:
pass pass
@ -232,7 +230,7 @@ if sys.version_info >= (3,) and platform.python_implementation() == 'CPython':
else: else:
class bdist_wheel(wheel.bdist_wheel.bdist_wheel): class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
def finalize_options(self): def finalize_options(self):
self.py_limited_api = 'cp3{}'.format(sys.version_info[1]) self.py_limited_api = f'cp3{sys.version_info[1]}'
super().finalize_options() super().finalize_options()
cmdclass['bdist_wheel'] = bdist_wheel cmdclass['bdist_wheel'] = bdist_wheel
@ -246,7 +244,7 @@ setup(
version=version(), version=version(),
ext_modules=[sass_extension], ext_modules=[sass_extension],
packages=['sassutils'], packages=['sassutils'],
py_modules=['pysassc', 'sass', 'sassc', 'sasstests'], py_modules=['pysassc', 'sass', 'sasstests'],
package_data={ package_data={
'': [ '': [
'README.rst', 'README.rst',
@ -267,11 +265,8 @@ setup(
], ],
'console_scripts': [ 'console_scripts': [
['pysassc = pysassc:main'], ['pysassc = pysassc:main'],
# TODO: remove `sassc` entry (#134)
['sassc = sassc:main'],
], ],
}, },
install_requires=['six'],
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment', 'Environment :: Web Environment',
@ -280,7 +275,6 @@ setup(
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: C', 'Programming Language :: C',
'Programming Language :: C++', 'Programming Language :: C++',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
@ -294,5 +288,6 @@ setup(
'Topic :: Software Development :: Code Generators', 'Topic :: Software Development :: Code Generators',
'Topic :: Software Development :: Compilers', 'Topic :: Software Development :: Compilers',
], ],
python_requires='>=3.6',
cmdclass=cmdclass, cmdclass=cmdclass,
) )

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = pypy,pypy3,py27,py36,py37,py38,py39,pre-commit envlist = pypy3,py36,py37,py38,py39,pre-commit
[testenv] [testenv]
usedevelop = true usedevelop = true