diff --git a/.gitignore b/.gitignore index ecba708..9cb00cc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ .*.swp .DS_Store ._.DS_Store -.pytest_cache/ .coverage .tox /.libsass-upstream-version diff --git a/.gitmodules b/.gitmodules index 975b971..77bd127 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libsass"] path = libsass -url = git://github.com/sass/libsass.git +url = https://github.com/sass/libsass diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b869a00..bf5f87c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,35 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - 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 - rev: 3.9.2 + rev: 5.0.4 hooks: - id: flake8 exclude: ^docs/conf.py -- repo: https://github.com/asottile/pyupgrade - rev: v2.16.0 - hooks: - - id: pyupgrade -- repo: https://github.com/asottile/add-trailing-comma - rev: v2.1.0 - hooks: - - id: add-trailing-comma diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8e73415..101beec 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -26,7 +26,7 @@ Tests - All commits will be tested by `Azure Pipelines`_ (Linux and Windows). .. _tox: https://tox.readthedocs.io/ -.. _`Azure Pipelines`: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master +.. _`Azure Pipelines`: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main Maintainer's guide diff --git a/README.rst b/README.rst index 5eab138..27ab487 100644 --- a/README.rst +++ b/README.rst @@ -5,16 +5,16 @@ libsass-python: Sass_/SCSS for Python :alt: PyPI :target: https://pypi.org/pypi/libsass/ -.. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=master - :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master +.. 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=main :alt: Build Status -.. image:: https://img.shields.io/azure-devops/coverage/asottile/asottile/22/master.svg - :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master +.. 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=main :alt: Coverage Status -.. image:: https://results.pre-commit.ci/badge/github/sass/libsass-python/master.svg - :target: https://results.pre-commit.ci/latest/github/sass/libsass-python/master +.. image:: https://results.pre-commit.ci/badge/github/sass/libsass-python/main.svg + :target: https://results.pre-commit.ci/latest/github/sass/libsass-python/main :alt: pre-commit.ci status 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 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. +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/ .. _LibSass: https://github.com/sass/libsass diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7a1584c..7991731 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,6 +1,6 @@ trigger: branches: - include: [master, test-me-*] + include: [main, test-me-*] tags: include: ['*'] @@ -13,30 +13,21 @@ resources: type: github endpoint: github name: asottile/azure-pipeline-templates - ref: refs/tags/v2.1.0 + ref: refs/tags/v2.4.0 jobs: - template: job--python-tox.yml@asottile parameters: - toxenvs: [py27, py36] + toxenvs: [py37] os: macos wheel_tags: true - template: job--python-tox.yml@asottile parameters: - toxenvs: [py27] - os: windows - architectures: [x64, x86] - name_postfix: _py27 - wheel_tags: true - pre_test: - - script: rm -rf libsass/test -- template: job--python-tox.yml@asottile - parameters: - toxenvs: [py36] + toxenvs: [py37] os: windows architectures: [x64, x86] wheel_tags: true - template: job--python-tox.yml@asottile parameters: - toxenvs: [pypy, pypy3, py27, py36, py37, py38, py39] + toxenvs: [pypy3, py36, py37, py38, py39] os: linux diff --git a/bin/build-manylinux-wheels b/bin/build-manylinux-wheels index 6d42531..70958b4 100755 --- a/bin/build-manylinux-wheels +++ b/bin/build-manylinux-wheels @@ -23,13 +23,13 @@ def main(): os.makedirs('dist', exist_ok=True) for python in ('cp27-cp27mu', 'cp36-cp36m'): with tempfile.TemporaryDirectory() as work: - pip = '/opt/python/{}/bin/pip'.format(python) + pip = f'/opt/python/{python}/bin/pip' check_call( 'docker', 'run', '-ti', # 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 - '-v', '{}:/work:rw'.format(work), + '-v', f'{work}:/work:rw', '-v', '{}:/dist:rw'.format(os.path.abspath('dist')), 'quay.io/pypa/manylinux1_x86_64:latest', 'bash', '-exc', @@ -40,4 +40,4 @@ def main(): if __name__ == '__main__': - exit(main()) + raise SystemExit(main()) diff --git a/bin/download-windows-wheels b/bin/download-windows-wheels index 56f95d2..dee25a2 100755 --- a/bin/download-windows-wheels +++ b/bin/download-windows-wheels @@ -56,4 +56,4 @@ def main() -> int: if __name__ == '__main__': - exit(main()) + raise SystemExit(main()) diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index f5790aa..0000000 --- a/debian/changelog +++ /dev/null @@ -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 Tue, 11 Oct 2022 11:07:40 +0800 - -libsass-python (0.21.0-ok1) yangtze; urgency=medium - - * Build for openkylin. - - -- sufang Tue, 11 Oct 2022 10:19:07 +0800 diff --git a/debian/control b/debian/control deleted file mode 100644 index 480e394..0000000 --- a/debian/control +++ /dev/null @@ -1,47 +0,0 @@ -Source: libsass-python -Section: python -Priority: optional -Maintainer: OpenKylin Developers -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. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index cc1b9d1..0000000 --- a/debian/copyright +++ /dev/null @@ -1,86 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: libsass-python -Source: -# -# 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 - . - 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. diff --git a/debian/libsass-python.changelog b/debian/libsass-python.changelog deleted file mode 120000 index bad05bf..0000000 --- a/debian/libsass-python.changelog +++ /dev/null @@ -1 +0,0 @@ -../docs/changes.rst \ No newline at end of file diff --git a/debian/not-installed b/debian/not-installed deleted file mode 100644 index 0f5a216..0000000 --- a/debian/not-installed +++ /dev/null @@ -1,2 +0,0 @@ -usr/bin/sassc -usr/lib/*/dist-packages/__pycache__/*.pyc diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 4a97dfa..0000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -# You must remove unused comment lines for the released package. diff --git a/debian/pysassc.install b/debian/pysassc.install deleted file mode 100644 index 68024ca..0000000 --- a/debian/pysassc.install +++ /dev/null @@ -1 +0,0 @@ -usr/bin/pysassc diff --git a/debian/pysassc.manpages b/debian/pysassc.manpages deleted file mode 100644 index 4b62b4a..0000000 --- a/debian/pysassc.manpages +++ /dev/null @@ -1 +0,0 @@ -debian/tmp/usr/share/man/man1/pysassc.1 diff --git a/debian/python3-libsass.install b/debian/python3-libsass.install deleted file mode 100644 index cda8c95..0000000 --- a/debian/python3-libsass.install +++ /dev/null @@ -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/ diff --git a/debian/python3-libsass.manpages b/debian/python3-libsass.manpages deleted file mode 100644 index 613e620..0000000 --- a/debian/python3-libsass.manpages +++ /dev/null @@ -1 +0,0 @@ -usr/share/man/man3/libsass.3 diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 3f6df4b..0000000 --- a/debian/rules +++ /dev/null @@ -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 diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 89ae9db..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/debian/upstream/metadata b/debian/upstream/metadata deleted file mode 100644 index f5b5e35..0000000 --- a/debian/upstream/metadata +++ /dev/null @@ -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 diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 2704253..0000000 --- a/debian/watch +++ /dev/null @@ -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 diff --git a/docs/changes.rst b/docs/changes.rst index 67c7858..0cc472e 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,7 +1,15 @@ 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. diff --git a/docs/conf.py b/docs/conf.py index d462a93..d6f6b1d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # libsass documentation build configuration file, created by # sphinx-quickstart on Sun Aug 19 22:45:57 2012. @@ -14,12 +13,13 @@ import os import sys import warnings +import sass + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) -import sass # -- General configuration ----------------------------------------------------- @@ -48,8 +48,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'libsass' -copyright = u'2012, Hong Minhee' +project = 'libsass' +copyright = '2012, Hong Minhee' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -180,23 +180,23 @@ htmlhelp_basename = 'libsassdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ( - 'index', 'libsass.tex', u'libsass Documentation', - u'Hong Minhee', 'manual', - ), + ( + 'index', 'libsass.tex', 'libsass Documentation', + 'Hong Minhee', 'manual', + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -226,10 +226,8 @@ latex_documents = [ # (source start file, name, description, authors, manual section). man_pages = [ ( - 'index', 'libsass', u'libsass Documentation', [u'Hong Minhee'], 3, - ), - ( - 'pysassc', 'pysassc', u'pysassc Documentation', [u'Hong Minhee'], 1, + 'index', 'libsass', 'libsass Documentation', + ['Hong Minhee'], 1, ), ] @@ -243,11 +241,11 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ( - 'index', 'libsass', u'libsass Documentation', - u'Hong Minhee', 'libsass', 'One line description of project.', - 'Miscellaneous', - ), + ( + 'index', 'libsass', 'libsass Documentation', + 'Hong Minhee', 'libsass', 'One line description of project.', + 'Miscellaneous', + ), ] # Documents to append as an appendix to all manuals. @@ -271,7 +269,7 @@ intersphinx_mapping = { extlinks = { 'issue': ('https://github.com/sass/libsass-python/issues/%s', '#'), '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', ''), diff --git a/docs/index.rst b/docs/index.rst index 2613f79..69775ba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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` 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/ .. _LibSass: https://github.com/sass/libsass @@ -133,17 +133,17 @@ GitHub (Git repository + issues) https://github.com/sass/libsass-python Azure Pipelines CI (linux + windows) - https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master + 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 - :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master + .. image:: https://dev.azure.com/asottile/asottile/_apis/build/status/sass.libsass-python?branchName=main + :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=main :alt: Build Status 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 - :target: https://dev.azure.com/asottile/asottile/_build/latest?definitionId=22&branchName=master + .. 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=main :alt: Coverage Status PyPI diff --git a/pysassc.py b/pysassc.py index b3ef1b0..fa2c8f8 100755 --- a/pysassc.py +++ b/pysassc.py @@ -88,10 +88,7 @@ There are options as well: .. _SassC: https://github.com/sass/sassc """ -from __future__ import print_function - import functools -import io import optparse import sys import warnings @@ -219,7 +216,7 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): include_paths=options.include_paths, precision=options.precision, ) - except (IOError, OSError) as e: + except OSError as e: error(e) return 3 except sass.CompileError as e: @@ -229,10 +226,10 @@ def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr): if len(args) < 2: print(css, file=stdout) 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) if source_map_filename: - with io.open( + with open( source_map_filename, 'w', encoding='utf-8', newline='', ) as f: f.write(source_map) diff --git a/sass.py b/sass.py index 8bc0dd8..4e66088 100644 --- a/sass.py +++ b/sass.py @@ -10,28 +10,21 @@ type. 'a b {\n color: blue; }\n' """ -from __future__ import absolute_import - -import collections +import collections.abc import inspect -import io -import os import os.path import re import sys import warnings -from six import string_types, text_type, PY2 - import _sass -from sassutils._compat import collections_abc __all__ = ( 'MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError', 'SassColor', 'SassError', 'SassFunction', 'SassList', 'SassMap', 'SassNumber', 'SassWarning', 'and_join', 'compile', 'libsass_version', ) -__version__ = '0.21.0' +__version__ = '0.22.0' libsass_version = _sass.libsass_version @@ -52,19 +45,19 @@ MODES = frozenset(('string', 'filename', 'dirname')) def to_native_s(s): - if isinstance(s, bytes) and not PY2: # pragma: no cover (py3) - s = s.decode('UTF-8') - elif isinstance(s, text_type) and PY2: # pragma: no cover (py2) - s = s.encode('UTF-8') - return s + if isinstance(s, bytes): + return s.decode('UTF-8') + else: + return s class CompileError(ValueError): """The exception type that is raised by :func:`compile()`. It is a subtype of :exc:`exceptions.ValueError`. """ + def __init__(self, msg): - super(CompileError, self).__init__(to_native_s(msg)) + super().__init__(to_native_s(msg)) def mkdirp(path): @@ -76,7 +69,7 @@ def mkdirp(path): raise -class SassFunction(object): +class SassFunction: """Custom function for Sass. It can be instantiated using :meth:`from_lambda()` and :meth:`from_named_function()` as well. @@ -107,16 +100,10 @@ class SassFunction(object): :rtype: :class:`SassFunction` """ - if PY2: # pragma: no cover - a = inspect.getargspec(lambda_) - varargs, varkw, defaults, kwonlyargs = ( - a.varargs, a.keywords, a.defaults, None, - ) - else: # pragma: no cover - a = inspect.getfullargspec(lambda_) - varargs, varkw, defaults, kwonlyargs = ( - a.varargs, a.varkw, a.defaults, a.kwonlyargs, - ) + a = inspect.getfullargspec(lambda_) + varargs, varkw, defaults, kwonlyargs = ( + a.varargs, a.varkw, a.defaults, a.kwonlyargs, + ) if varargs or varkw or defaults or kwonlyargs: raise TypeError( @@ -142,9 +129,9 @@ class SassFunction(object): return cls.from_lambda(function.__name__, function) 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)) - elif not isinstance(arguments, collections_abc.Sequence): + elif not isinstance(arguments, collections.abc.Sequence): raise TypeError( 'arguments must be a sequence, not ' + repr(arguments), @@ -263,7 +250,7 @@ def compile_dirname( if s: v = v.decode('UTF-8') mkdirp(os.path.dirname(output_filename)) - with io.open( + with open( output_filename, 'w', encoding='UTF-8', newline='', ) as output_file: output_file.write(v) @@ -277,7 +264,7 @@ def _check_no_remaining_kwargs(func, kwargs): raise TypeError( '{}() got unexpected keyword argument(s) {}'.format( 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) output_style = kwargs.pop('output_style', 'nested') - if not isinstance(output_style, string_types): + if not isinstance(output_style, str): raise TypeError( 'output_style must be a string, not ' + repr(output_style), @@ -586,7 +573,7 @@ def compile(**kwargs): elif source_comments in ('line_numbers', 'default'): deprecation_message = ( 'you can simply pass True to ' - "source_comments instead of " + + 'source_comments instead of ' + repr(source_comments) ) source_comments = True @@ -612,9 +599,9 @@ def compile(**kwargs): def _get_file_arg(key): ret = kwargs.pop(key, None) - if ret is not None and not isinstance(ret, string_types): - raise TypeError('{} must be a string, not {!r}'.format(key, ret)) - elif isinstance(ret, text_type): + if ret is not None and not isinstance(ret, str): + raise TypeError(f'{key} must be a string, not {ret!r}') + elif isinstance(ret, str): ret = ret.encode(fs_encoding) if ret and 'filename' not in modes: raise CompileError( @@ -631,25 +618,25 @@ def compile(**kwargs): omit_source_map_url = kwargs.pop('omit_source_map_url', False) source_map_root = kwargs.pop('source_map_root', None) - if isinstance(source_map_root, text_type): + if isinstance(source_map_root, str): source_map_root = source_map_root.encode('utf-8') # #208: cwd is always included in include paths include_paths = (os.getcwd(),) include_paths += tuple(kwargs.pop('include_paths', ()) or ()) include_paths = os.pathsep.join(include_paths) - if isinstance(include_paths, text_type): + if isinstance(include_paths, str): include_paths = include_paths.encode(fs_encoding) custom_functions = kwargs.pop('custom_functions', ()) - if isinstance(custom_functions, collections_abc.Mapping): + if isinstance(custom_functions, collections.abc.Mapping): custom_functions = [ SassFunction.from_lambda(name, lambda_) for name, lambda_ in custom_functions.items() ] elif isinstance( custom_functions, - (collections_abc.Set, collections_abc.Sequence), + (collections.abc.Set, collections.abc.Sequence), ): custom_functions = [ func if isinstance(func, SassFunction) @@ -676,7 +663,7 @@ def compile(**kwargs): if 'string' in modes: string = kwargs.pop('string') - if isinstance(string, text_type): + if isinstance(string, str): string = string.encode('utf-8') indented = kwargs.pop('indented', False) if not isinstance(indented, bool): @@ -695,11 +682,11 @@ def compile(**kwargs): return v.decode('utf-8') elif 'filename' in modes: 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)) elif not os.path.isfile(filename): - raise IOError('{!r} seems not a file'.format(filename)) - elif isinstance(filename, text_type): + raise OSError(f'{filename!r} seems not a file') + elif isinstance(filename, str): filename = filename.encode(fs_encoding) _check_no_remaining_kwargs(compile, kwargs) s, v, source_map = _sass.compile_filename( @@ -780,9 +767,9 @@ class SassNumber(collections.namedtuple('SassNumber', ('value', 'unit'))): def __new__(cls, value, unit): value = float(value) - if not isinstance(unit, text_type): + if not isinstance(unit, str): 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'))): @@ -792,7 +779,7 @@ class SassColor(collections.namedtuple('SassColor', ('r', 'g', 'b', 'a'))): g = float(g) b = float(b) 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', ())() @@ -802,7 +789,7 @@ SEPARATORS = frozenset((SASS_SEPARATOR_COMMA, SASS_SEPARATOR_SPACE)) class SassList( collections.namedtuple( - 'SassList', ('items', 'separator', 'bracketed'), + 'SassList', ('items', 'separator', 'bracketed'), ), ): @@ -810,26 +797,26 @@ class SassList( items = tuple(items) assert separator in SEPARATORS, separator 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',))): def __new__(cls, msg): - if not isinstance(msg, text_type): + if not isinstance(msg, str): msg = msg.decode('UTF-8') - return super(SassError, cls).__new__(cls, msg) + return super().__new__(cls, msg) class SassWarning(collections.namedtuple('SassWarning', ('msg',))): def __new__(cls, msg): - if not isinstance(msg, text_type): + if not isinstance(msg, str): 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 hashable mapping type. @@ -858,7 +845,7 @@ class SassMap(collections_abc.Mapping): # Our interface def __repr__(self): - return '{}({})'.format(type(self).__name__, frozenset(self.items())) + return f'{type(self).__name__}({frozenset(self.items())})' def __hash__(self): return self._hash diff --git a/sassc.py b/sassc.py deleted file mode 100644 index 2415342..0000000 --- a/sassc.py +++ /dev/null @@ -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()) diff --git a/sasstests.py b/sasstests.py index 6aaf553..efd103a 100644 --- a/sasstests.py +++ b/sasstests.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- - import base64 +import collections.abc import contextlib import functools import glob -import json import io -import os +import json import os.path import re import shutil @@ -17,15 +15,13 @@ import traceback import unittest import pytest -from six import StringIO, b, string_types, text_type from werkzeug.test import Client from werkzeug.wrappers import Response import pysassc import sass -import sassc -from sassutils._compat import collections_abc -from sassutils.builder import Manifest, build_directory +from sassutils.builder import build_directory +from sassutils.builder import Manifest 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()]) B_EXPECTED_CSS = '''\ @@ -95,7 +91,7 @@ h1 a { color: green; } ''' -D_EXPECTED_CSS = u'''\ +D_EXPECTED_CSS = '''\ @charset "UTF-8"; body { background-color: green; } @@ -103,7 +99,7 @@ body { font: '나눔고딕', sans-serif; } ''' -D_EXPECTED_CSS_WITH_MAP = u'''\ +D_EXPECTED_CSS_WITH_MAP = '''\ @charset "UTF-8"; body { background-color: green; } @@ -149,7 +145,7 @@ re_base64_data_uri = re.compile(r'^data:[^;]*?;base64,(.+)$') def _map_in_output_dir(s): def cb(match): filename = os.path.basename(match.group(1)) - return '/*# sourceMappingURL={} */'.format(filename) + return f'/*# sourceMappingURL={filename} */' return re_sourcemap_url.sub(cb, s) @@ -163,9 +159,9 @@ def no_warnings(recwarn): class BaseTestCase(unittest.TestCase): def assert_source_map_equal(self, expected, actual): - if isinstance(expected, string_types): + if isinstance(expected, str): expected = json.loads(expected) - if isinstance(actual, string_types): + if isinstance(actual, str): actual = json.loads(actual) assert expected == actual @@ -175,7 +171,7 @@ class BaseTestCase(unittest.TestCase): tree = json.load(f) except ValueError as e: # pragma: no cover 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) self.assert_source_map_equal(expected, tree) @@ -196,7 +192,7 @@ class SassTestCase(BaseTestCase): assert re.match(r'^\d+\.\d+\.\d+$', sass.__version__) 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 def test_and_join(self): @@ -294,9 +290,9 @@ a { a b { color: blue; } ''' - actual = sass.compile(string=u'a { color: blue; } /* 유니코드 */') + actual = sass.compile(string='a { color: blue; } /* 유니코드 */') self.assertEqual( - u'''@charset "UTF-8"; + '''@charset "UTF-8"; a { color: blue; } @@ -330,11 +326,11 @@ a { def test_importer_one_arg(self): """Demonstrates one-arg importers + chaining.""" def importer_returning_one_argument(path): - assert type(path) is text_type + assert type(path) is str return ( # Trigger the import of an actual file ('test/b.scss',), - (path, '.{0}-one-arg {{ color: blue; }}'.format(path)), + (path, f'.{path}-one-arg {{ color: blue; }}'), ) ret = sass.compile( @@ -428,11 +424,11 @@ a { path, 'a { color: red; }', json.dumps({ - "version": 3, - "sources": [ - path + ".db", + 'version': 3, + 'sources': [ + path + '.db', ], - "mappings": ";AAAA,CAAC,CAAC;EAAE,KAAK,EAAE,GAAI,GAAI", + 'mappings': ';AAAA,CAAC,CAAC;EAAE,KAAK,EAAE,GAAI,GAAI', }), ), ) @@ -448,17 +444,17 @@ a { def test_importers_raises_exception(self): def importer(path): - raise ValueError('Bad path: {}'.format(path)) + raise ValueError(f'Bad path: {path}') with assert_raises_compile_error( RegexMatcher( - r'^Error: \n' - r' Traceback \(most recent call last\):\n' - r'.+' - r'ValueError: Bad path: hi\n' - r' on line 1:9 of stdin\n' - r'>> @import "hi";\n' - r' --------\^\n', + r'^Error: \n' + r' Traceback \(most recent call last\):\n' + r'.+' + r'ValueError: Bad path: hi\n' + r' on line 1:9 of stdin\n' + r'>> @import "hi";\n' + r' --------\^\n', ), ): sass.compile(string='@import "hi";', importers=((0, importer),)) @@ -469,14 +465,14 @@ a { with assert_raises_compile_error( RegexMatcher( - r'^Error: \n' - r' Traceback \(most recent call last\):\n' - r'.+' - r'ValueError: Expected importer result to be a tuple of ' - r'length \(1, 2, 3\) but got 0: \(\)\n' - r' on line 1:9 of stdin\n' - r'>> @import "hi";\n' - r' --------\^\n', + r'^Error: \n' + r' Traceback \(most recent call last\):\n' + r'.+' + r'ValueError: Expected importer result to be a tuple of ' + r'length \(1, 2, 3\) but got 0: \(\)\n' + r' on line 1:9 of stdin\n' + r'>> @import "hi";\n' + r' --------\^\n', ), ): sass.compile(string='@import "hi";', importers=((0, importer),)) @@ -487,14 +483,14 @@ a { with assert_raises_compile_error( RegexMatcher( - r'^Error: \n' - r' Traceback \(most recent call last\):\n' - r'.+' - r'ValueError: Expected importer result to be a tuple of ' - r"length \(1, 2, 3\) but got 4: \('a', 'b', 'c', 'd'\)\n" - r' on line 1:9 of stdin\n' - r'>> @import "hi";\n' - r' --------\^\n', + r'^Error: \n' + r' Traceback \(most recent call last\):\n' + r'.+' + r'ValueError: Expected importer result to be a tuple of ' + r"length \(1, 2, 3\) but got 4: \('a', 'b', 'c', 'd'\)\n" + r' on line 1:9 of stdin\n' + r'>> @import "hi";\n' + r' --------\^\n', ), ): sass.compile(string='@import "hi";', importers=((0, importer),)) @@ -640,31 +636,31 @@ class BuilderTestCase(BaseTestCase): result_files = build_directory(self.sass_path, css_path) assert len(result_files) == 8 assert 'a.scss.css' == result_files['a.scss'] - with io.open( + with open( os.path.join(css_path, 'a.scss.css'), encoding='UTF-8', ) as f: css = f.read() assert A_EXPECTED_CSS == css assert 'b.scss.css' == result_files['b.scss'] - with io.open( + with open( os.path.join(css_path, 'b.scss.css'), encoding='UTF-8', ) as f: css = f.read() assert B_EXPECTED_CSS == css assert 'c.scss.css' == result_files['c.scss'] - with io.open( + with open( os.path.join(css_path, 'c.scss.css'), encoding='UTF-8', ) as f: css = f.read() assert C_EXPECTED_CSS == css assert 'd.scss.css' == result_files['d.scss'] - with io.open( + with open( os.path.join(css_path, 'd.scss.css'), encoding='UTF-8', ) as f: css = f.read() assert D_EXPECTED_CSS == css assert 'e.scss.css' == result_files['e.scss'] - with io.open( + with open( os.path.join(css_path, 'e.scss.css'), encoding='UTF-8', ) as f: css = f.read() @@ -673,7 +669,7 @@ class BuilderTestCase(BaseTestCase): os.path.join('subdir', 'recur.scss.css'), result_files[os.path.join('subdir', 'recur.scss')], ) - with io.open( + with open( os.path.join(css_path, 'g.scss.css'), encoding='UTF-8', ) as f: css = f.read() @@ -683,12 +679,12 @@ class BuilderTestCase(BaseTestCase): result_files[os.path.join('subdir', 'recur.scss')], ) assert 'h.sass.css' == result_files['h.sass'] - with io.open( + with open( os.path.join(css_path, 'h.sass.css'), encoding='UTF-8', ) as f: css = f.read() assert H_EXPECTED_CSS == css - with io.open( + with open( os.path.join(css_path, 'subdir', 'recur.scss.css'), encoding='UTF-8', ) as f: @@ -703,7 +699,7 @@ class BuilderTestCase(BaseTestCase): ) assert len(result_files) == 8 assert 'a.scss.css' == result_files['a.scss'] - with io.open( + with open( os.path.join(css_path, 'a.scss.css'), encoding='UTF-8', ) as f: css = f.read() @@ -755,7 +751,7 @@ class ManifestTestCase(BaseTestCase): with open(os.path.join(d, 'css', 'a.scss.css')) as f: assert A_EXPECTED_CSS == f.read() 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', ) as f: 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'), ) 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', ) as f: 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') assert r.status_code == 200 self.assertEqual( - b(_map_in_output_dir(A_EXPECTED_CSS_WITH_MAP)), + _map_in_output_dir(A_EXPECTED_CSS_WITH_MAP).encode(), r.data, ) assert r.mimetype == 'text/css' @@ -903,7 +899,7 @@ class DistutilsTestCase(BaseTestCase): return os.path.join( os.path.dirname(__file__), 'testpkg', 'testpkg', 'static', 'css', - *args + *args, ) def list_built_css(self): @@ -942,8 +938,8 @@ class DistutilsTestCase(BaseTestCase): class SasscTestCase(BaseTestCase): def setUp(self): - self.out = StringIO() - self.err = StringIO() + self.out = io.StringIO() + self.err = io.StringIO() def test_no_args(self): exit_code = pysassc.main(['pysassc'], self.out, self.err) @@ -973,17 +969,6 @@ class SasscTestCase(BaseTestCase): assert self.err.getvalue() == '' 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): fd, tmp = tempfile.mkstemp('.css') try: @@ -995,7 +980,7 @@ class SasscTestCase(BaseTestCase): assert exit_code == 0 assert self.err.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() finally: os.remove(tmp) @@ -1011,7 +996,7 @@ class SasscTestCase(BaseTestCase): assert exit_code == 0 assert self.err.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() finally: os.remove(tmp) @@ -1093,10 +1078,10 @@ class CompileDirectoriesTest(unittest.TestCase): input_dir = os.path.join(tmpdir, 'input') output_dir = os.path.join(tmpdir, 'output') os.makedirs(input_dir) - with io.open( + with open( os.path.join(input_dir, 'test.scss'), 'w', encoding='UTF-8', ) as f: - f.write(u'a { content: "☃"; }') + f.write('a { content: "☃"; }') # Raised a UnicodeEncodeError in py2 before #82 (issue #72) # Also raised a UnicodeEncodeError in py3 if the default encoding # couldn't represent it (such as cp1252 on windows) @@ -1131,7 +1116,7 @@ class CompileDirectoriesTest(unittest.TestCase): class SassFunctionTest(unittest.TestCase): 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_) assert 'func_name' == sf.name assert ('$abc', '$d') == sf.arguments @@ -1164,14 +1149,14 @@ def test_sass_func_type_errors(func): class SassTypesTest(unittest.TestCase): 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.unit) is text_type, type(num.unit) + assert type(num.unit) is str, type(num.unit) def test_number_conversion(self): num = sass.SassNumber(123, b'px') 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): 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 def test_sass_warning_no_conversion(self): - warn = sass.SassWarning(u'error msg') - assert type(warn.msg) is text_type, type(warn.msg) + warn = sass.SassWarning('error msg') + assert type(warn.msg) is str, type(warn.msg) def test_sass_warning_no_conversion_bytes_message(self): 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): - err = sass.SassError(u'error msg') - assert type(err.msg) is text_type, type(err.msg) + err = sass.SassError('error msg') + assert type(err.msg) is str, type(err.msg) def test_sass_error_conversion(self): 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(): @@ -1244,11 +1229,11 @@ def returns_none(): def returns_unicode(): - return u'☃' + return '☃' def returns_bytes(): - return u'☃'.encode('UTF-8') + return '☃'.encode() def returns_number(): @@ -1380,7 +1365,7 @@ def assert_raises_compile_error(expected): assert msg == expected, (msg, expected) -class RegexMatcher(object): +class RegexMatcher: def __init__(self, reg, flags=None): self.reg = re.compile(reg, re.MULTILINE | re.DOTALL) @@ -1393,14 +1378,14 @@ class CustomFunctionsTest(unittest.TestCase): def test_raises(self): with assert_raises_compile_error( RegexMatcher( - r'^Error: error in C function raises: \n' - r' Traceback \(most recent call last\):\n' - r'.+' - r'AssertionError: foo\n' - r' on line 1:14 of stdin, in function `raises`\n' - r' from line 1:14 of stdin\n' - r'>> a { content: raises\(\); }\n' - r' -------------\^\n$', + r'^Error: error in C function raises: \n' + r' Traceback \(most recent call last\):\n' + r'.+' + r'AssertionError: foo\n' + r' on line 1:14 of stdin, in function `raises`\n' + r' from line 1:14 of stdin\n' + r'>> a { content: raises\(\); }\n' + r' -------------\^\n$', ), ): compile_with_func('a { content: raises(); }') @@ -1472,13 +1457,13 @@ class CustomFunctionsTest(unittest.TestCase): def test_unicode(self): self.assertEqual( compile_with_func('a { content: returns_unicode(); }'), - u'\ufeffa{content:☃}\n', + '\ufeffa{content:☃}\n', ) def test_bytes(self): self.assertEqual( compile_with_func('a { content: returns_bytes(); }'), - u'\ufeffa{content:☃}\n', + '\ufeffa{content:☃}\n', ) def test_number(self): @@ -1550,7 +1535,7 @@ class CustomFunctionsTest(unittest.TestCase): def test_identity_strings(self): self.assertEqual( compile_with_func('a { content: identity(returns_unicode()); }'), - u'\ufeffa{content:☃}\n', + '\ufeffa{content:☃}\n', ) def test_identity_number(self): @@ -1626,7 +1611,7 @@ class CustomFunctionsTest(unittest.TestCase): def test_stack_trace_formatting(): try: - sass.compile(string=u'a{☃') + sass.compile(string='a{☃') raise AssertionError('expected to raise CompileError') except sass.CompileError: tb = traceback.format_exc() diff --git a/sassutils/_compat.py b/sassutils/_compat.py deleted file mode 100644 index 8f794ac..0000000 --- a/sassutils/_compat.py +++ /dev/null @@ -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 diff --git a/sassutils/builder.py b/sassutils/builder.py index b07dfad..a1d6845 100644 --- a/sassutils/builder.py +++ b/sassutils/builder.py @@ -2,17 +2,12 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ - -import io -import os +import collections.abc import os.path import re import warnings -from six import string_types - from sass import compile -from sassutils._compat import collections_abc __all__ = 'SUFFIXES', 'SUFFIX_PATTERN', 'Manifest', 'build_directory' @@ -69,7 +64,7 @@ def build_directory( output_style=output_style, include_paths=[_root_sass], ) - with io.open( + with open( css_fullname, 'w', encoding='utf-8', newline='', ) as css_file: css_file.write(css) @@ -88,7 +83,7 @@ def build_directory( return result -class Manifest(object): +class Manifest: """Building manifest of 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): if manifests is None: manifests = {} - elif isinstance(manifests, collections_abc.Mapping): + elif isinstance(manifests, collections.abc.Mapping): manifests = dict(manifests) else: raise TypeError( @@ -113,7 +108,7 @@ class Manifest(object): repr(manifests), ) for package_name, manifest in manifests.items(): - if not isinstance(package_name, string_types): + if not isinstance(package_name, str): raise TypeError( 'manifest keys must be a string of package ' 'name, not ' + repr(package_name), @@ -122,9 +117,9 @@ class Manifest(object): continue elif isinstance(manifest, tuple): manifest = Manifest(*manifest) - elif isinstance(manifest, collections_abc.Mapping): + elif isinstance(manifest, collections.abc.Mapping): manifest = Manifest(**manifest) - elif isinstance(manifest, string_types): + elif isinstance(manifest, str): manifest = Manifest(manifest) else: raise TypeError( @@ -142,21 +137,21 @@ class Manifest(object): wsgi_path=None, strip_extension=None, ): - if not isinstance(sass_path, string_types): + if not isinstance(sass_path, str): raise TypeError( 'sass_path must be a string, not ' + repr(sass_path), ) if css_path is None: css_path = sass_path - elif not isinstance(css_path, string_types): + elif not isinstance(css_path, str): raise TypeError( 'css_path must be a string, not ' + repr(css_path), ) if wsgi_path is None: wsgi_path = css_path - elif not isinstance(wsgi_path, string_types): + elif not isinstance(wsgi_path, str): raise TypeError( 'wsgi_path must be a string, not ' + repr(wsgi_path), @@ -292,11 +287,11 @@ class Manifest(object): css_folder = os.path.dirname(css_path) if not os.path.exists(css_folder): os.makedirs(css_folder) - with io.open(css_path, 'w', encoding='utf-8', newline='') as f: + with open(css_path, 'w', encoding='utf-8', newline='') as f: f.write(css) if source_map: # Source maps are JSON, and JSON has to be UTF-8 encoded - with io.open( + with open( source_map_path, 'w', encoding='utf-8', newline='', ) as f: f.write(source_map) diff --git a/sassutils/distutils.py b/sassutils/distutils.py index 52b19c6..27f628c 100644 --- a/sassutils/distutils.py +++ b/sassutils/distutils.py @@ -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. """ -from __future__ import absolute_import +import functools +import os.path import distutils.errors import distutils.log import distutils.util -import functools -import os.path - from setuptools import Command from setuptools.command.sdist import sdist -from sass import OUTPUT_STYLES from .builder import Manifest +from sass import OUTPUT_STYLES __all__ = 'build_sass', 'validate_manifests' diff --git a/sassutils/wsgi.py b/sassutils/wsgi.py index 2d8a554..d29fa82 100644 --- a/sassutils/wsgi.py +++ b/sassutils/wsgi.py @@ -2,22 +2,19 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import - +import collections.abc import logging -import os import os.path from pkg_resources import resource_filename -from sass import CompileError -from sassutils._compat import collections_abc from .builder import Manifest +from sass import CompileError __all__ = 'SassMiddleware', -class SassMiddleware(object): +class SassMiddleware: r"""WSGI middleware for development purpose. Every time a CSS file has requested it finds a matched Sass/SCSS source file and then compiled it into CSS. @@ -100,7 +97,7 @@ class SassMiddleware(object): ) self.app = app self.manifests = Manifest.normalize_manifests(manifests) - if not isinstance(package_dir, collections_abc.Mapping): + if not isinstance(package_dir, collections.abc.Mapping): raise TypeError( 'package_dir must be a mapping object, not ' + repr(package_dir), @@ -138,7 +135,7 @@ class SassMiddleware(object): sass_filename, source_map=True, ) - except (IOError, OSError): + except OSError: break except CompileError as e: logger = logging.getLogger(__name__ + '.SassMiddleware') diff --git a/setup.py b/setup.py index ff15265..a3719a9 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,5 @@ -#!/usr/bin/env python -from __future__ import print_function - import ast import atexit -import distutils.cmd -import distutils.log -import distutils.sysconfig import os.path import platform import shutil @@ -13,7 +7,11 @@ import subprocess import sys 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'] FLAGS_POSIX = [ @@ -83,9 +81,9 @@ else: libsass_version = libsass_version_file.read().decode('UTF-8').strip() if sys.platform == 'win32': # This looks wrong, but is required for some reason :( - define = r'/DLIBSASS_VERSION="\"{}\""'.format(libsass_version) + define = fr'/DLIBSASS_VERSION="\"{libsass_version}\""' else: - define = '-DLIBSASS_VERSION="{}"'.format(libsass_version) + define = f'-DLIBSASS_VERSION="{libsass_version}"' for directory in ( os.path.join('libsass', 'src'), @@ -185,7 +183,7 @@ def readme(): try: with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: return f.read() - except IOError: + except OSError: pass @@ -232,7 +230,7 @@ if sys.version_info >= (3,) and platform.python_implementation() == 'CPython': else: class bdist_wheel(wheel.bdist_wheel.bdist_wheel): 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() cmdclass['bdist_wheel'] = bdist_wheel @@ -246,7 +244,7 @@ setup( version=version(), ext_modules=[sass_extension], packages=['sassutils'], - py_modules=['pysassc', 'sass', 'sassc', 'sasstests'], + py_modules=['pysassc', 'sass', 'sasstests'], package_data={ '': [ 'README.rst', @@ -267,11 +265,8 @@ setup( ], 'console_scripts': [ ['pysassc = pysassc:main'], - # TODO: remove `sassc` entry (#134) - ['sassc = sassc:main'], ], }, - install_requires=['six'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', @@ -280,7 +275,6 @@ setup( 'Operating System :: OS Independent', 'Programming Language :: C', 'Programming Language :: C++', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', @@ -294,5 +288,6 @@ setup( 'Topic :: Software Development :: Code Generators', 'Topic :: Software Development :: Compilers', ], + python_requires='>=3.6', cmdclass=cmdclass, ) diff --git a/tox.ini b/tox.ini index 38047b9..f4b84ad 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = pypy,pypy3,py27,py36,py37,py38,py39,pre-commit +envlist = pypy3,py36,py37,py38,py39,pre-commit [testenv] usedevelop = true