qtsass-0.3.0+git20200324.06f1519/0000755000175000017500000000000013636454105013437 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/.mailmap0000644000175000017500000000170413636454105015062 0ustar jdgjdg# This file was autogenerated by rever: https://regro.github.io/rever-docs/ # This prevent git from showing duplicates with various logging commands. # See the git documentation for more details. The syntax is: # # good-name bad-name # # You can skip bad-name if it is the same as good-name and is unique in the repo. # # This file is up-to-date if the command git log --format="%aN <%aE>" | sort -u # gives no duplicates. Andrey Galkin Andrey Galkin C.A.M. Gerlach Carlos Cordoba Dan Bradham Eric Werner Gonzalo Peña-Castellanos Gonzalo Pena-Castellanos Gonzalo Peña-Castellanos goanpeca Matthew Joyce Yann Lanthony yann.lanthony qtsass-0.3.0+git20200324.06f1519/.github/0000755000175000017500000000000013636454105014777 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/.github/FUNDING.yml0000644000175000017500000000003013636454105016605 0ustar jdgjdgopen_collective: spyder qtsass-0.3.0+git20200324.06f1519/.github/workflows/0000755000175000017500000000000013636454105017034 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/.github/workflows/ci.yml0000644000175000017500000001235513636454105020160 0ustar jdgjdgname: Tests on: # This avoids having duplicate builds for a pull request push: branches: - master pull_request: branches: - master jobs: smoke: name: Linux smoke test Py${{ matrix.PYTHON_VERSION }} runs-on: ubuntu-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} QT_DEBUG_PLUGINS: 1 strategy: fail-fast: false matrix: PYTHON_VERSION: ['2.7', '3.5', '3.8'] steps: - name: Checkout branch uses: actions/checkout@v1 - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test python-version: ${{ matrix.PYTHON_VERSION }} - name: Install Dependencies shell: bash -l {0} run: pip install -r requirements/install.txt - name: Install Test Dependencies shell: bash -l {0} run: pip install -r requirements/dev.txt - name: Install Package shell: bash -l {0} run: python setup.py develop - name: Show Environment shell: bash -l {0} run: | conda info conda list - name: Format if: matrix.PYTHON_VERSION == '2.7' shell: bash -l {0} run: python run_checks_and_format.py - name: Run tests shell: bash -l {0} run: xvfb-run --auto-servernum pytest tests --cov=qtsass --cov-report=term-missing -x -vv - name: Upload coverage to Codecov shell: bash -l {0} run: codecov -t 74b56e56-6c81-4b43-b830-c46638da84a7 linux: name: Linux Py${{ matrix.PYTHON_VERSION }} needs: smoke runs-on: ubuntu-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} QT_DEBUG_PLUGINS: 1 strategy: fail-fast: false matrix: PYTHON_VERSION: ['3.6', '3.7'] steps: - name: Checkout branch uses: actions/checkout@v1 - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test python-version: ${{ matrix.PYTHON_VERSION }} - name: Install Dependencies shell: bash -l {0} run: pip install -r requirements/install.txt - name: Install Test Dependencies shell: bash -l {0} run: pip install -r requirements/dev.txt - name: Install Package shell: bash -l {0} run: python setup.py develop - name: Show Environment shell: bash -l {0} run: | conda info conda list - name: Run tests shell: bash -l {0} run: xvfb-run --auto-servernum pytest tests --cov=qtsass --cov-report=term-missing -x -vv - name: Upload coverage to Codecov shell: bash -l {0} run: codecov -t 74b56e56-6c81-4b43-b830-c46638da84a7 macos: name: Mac Py${{ matrix.PYTHON_VERSION }} needs: smoke runs-on: macos-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} QT_DEBUG_PLUGINS: 1 strategy: fail-fast: false matrix: PYTHON_VERSION: ['2.7', '3.5', '3.6', '3.7', '3.8'] steps: - name: Checkout branch uses: actions/checkout@v1 - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test python-version: ${{ matrix.PYTHON_VERSION }} - name: Install Dependencies shell: bash -l {0} run: pip install -r requirements/install.txt - name: Install Test Dependencies shell: bash -l {0} run: pip install -r requirements/dev.txt - name: Install Package shell: bash -l {0} run: python setup.py develop - name: Show Environment shell: bash -l {0} run: | conda info conda list - name: Run tests shell: bash -l {0} run: pytest tests --cov=qtsass --cov-report=term-missing -x -vv - name: Upload coverage to Codecov shell: bash -l {0} run: codecov -t 74b56e56-6c81-4b43-b830-c46638da84a7 windows: name: Windows Py${{ matrix.PYTHON_VERSION }} needs: smoke runs-on: windows-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} QT_DEBUG_PLUGINS: 1 strategy: fail-fast: false matrix: PYTHON_VERSION: ['2.7', '3.5', '3.6', '3.7', '3.8'] steps: - name: Checkout branch uses: actions/checkout@v1 - name: Install Conda uses: goanpeca/setup-miniconda@v1 with: activate-environment: test python-version: ${{ matrix.PYTHON_VERSION }} - name: Install Dependencies shell: bash -l {0} run: pip install -r requirements/install.txt - name: Install Test Dependencies shell: bash -l {0} run: pip install -r requirements/dev.txt - name: Install Package shell: bash -l {0} run: python setup.py develop - name: Show Environment shell: bash -l {0} run: | conda info conda list - name: Run tests shell: bash -l {0} run: pytest tests --cov=qtsass --cov-report=term-missing -x -vv - name: Upload coverage to Codecov shell: bash -l {0} run: codecov -t 74b56e56-6c81-4b43-b830-c46638da84a7 qtsass-0.3.0+git20200324.06f1519/LICENSE.txt0000644000175000017500000000214213636454105015261 0ustar jdgjdgMIT License Copyright (c) 2015 Yann Lanthony Copyright (c) 2017-2018 Spyder Project Contributors 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. qtsass-0.3.0+git20200324.06f1519/CHANGELOG.md0000644000175000017500000001427613636454105015262 0ustar jdgjdg# History of changes ## Version qtsass v0.3.0 (2020/03/18) ### Issues Closed * [Issue 50](https://github.com/spyder-ide/qtsass/issues/50) - Add rever to release process ([PR 51](https://github.com/spyder-ide/qtsass/pull/51) by [@goanpeca](https://github.com/goanpeca)) * [Issue 48](https://github.com/spyder-ide/qtsass/issues/48) - Move CI to github actions ([PR 49](https://github.com/spyder-ide/qtsass/pull/49) by [@goanpeca](https://github.com/goanpeca)) In this release 2 issues were closed. ### Pull Requests Merged * [PR 51](https://github.com/spyder-ide/qtsass/pull/51) - PR: Add rever to release process, by [@goanpeca](https://github.com/goanpeca) ([50](https://github.com/spyder-ide/qtsass/issues/50)) * [PR 49](https://github.com/spyder-ide/qtsass/pull/49) - PR: Move to github actions, by [@goanpeca](https://github.com/goanpeca) ([48](https://github.com/spyder-ide/qtsass/issues/48)) In this release 2 pull requests were closed. ## Version 0.2 (2020-02-23) ### Issues Closed * [Issue 43](https://github.com/spyder-ide/qtsass/issues/43) - rgba function breaks on incoming 8bit ints ([PR 40](https://github.com/spyder-ide/qtsass/pull/40) by [@ewerybody](https://github.com/ewerybody)) * [Issue 42](https://github.com/spyder-ide/qtsass/issues/42) - qlineargradient x1,y1,x2,y2 values can be floats! ([PR 40](https://github.com/spyder-ide/qtsass/pull/40) by [@ewerybody](https://github.com/ewerybody)) * [Issue 41](https://github.com/spyder-ide/qtsass/issues/41) - make qtsass watchdog dependence optional In this release 3 issues were closed. ### Pull Requests Merged * [PR 44](https://github.com/spyder-ide/qtsass/pull/44) - Add Watcher api, by [@danbradham](https://github.com/danbradham) * [PR 40](https://github.com/spyder-ide/qtsass/pull/40) - added support for incomplete coords and incoming %-rgba values ..., by [@ewerybody](https://github.com/ewerybody) ([43](https://github.com/spyder-ide/qtsass/issues/43), [42](https://github.com/spyder-ide/qtsass/issues/42)) In this release 2 pull requests were closed. ## Version 0.1.0 (2019-05-05) ### Issues Closed * [Issue 21](https://github.com/spyder-ide/qtsass/issues/21) - Reorganize qtsass package * [Issue 18](https://github.com/spyder-ide/qtsass/issues/18) - CLI - build/watch directory ([PR 23](https://github.com/spyder-ide/qtsass/pull/23) by [@danbradham](https://github.com/danbradham)) * [Issue 17](https://github.com/spyder-ide/qtsass/issues/17) - Create PyPI package ([PR 32](https://github.com/spyder-ide/qtsass/pull/32) by [@goanpeca](https://github.com/goanpeca)) * [Issue 16](https://github.com/spyder-ide/qtsass/issues/16) - Add Windows CI - Appveyor ([PR 19](https://github.com/spyder-ide/qtsass/pull/19) by [@danbradham](https://github.com/danbradham)) * [Issue 12](https://github.com/spyder-ide/qtsass/issues/12) - Move test to a tests folder ([PR 13](https://github.com/spyder-ide/qtsass/pull/13) by [@danbradham](https://github.com/danbradham)) * [Issue 11](https://github.com/spyder-ide/qtsass/issues/11) - Add badges for coverage and travis CI builds ([PR 14](https://github.com/spyder-ide/qtsass/pull/14) by [@danbradham](https://github.com/danbradham)) * [Issue 10](https://github.com/spyder-ide/qtsass/issues/10) - Add coverage to the CI tests with codecov.io ([PR 15](https://github.com/spyder-ide/qtsass/pull/15) by [@goanpeca](https://github.com/goanpeca)) * [Issue 9](https://github.com/spyder-ide/qtsass/issues/9) - Add MIT LICENSE.txt file ([PR 2](https://github.com/spyder-ide/qtsass/pull/2) by [@danbradham](https://github.com/danbradham)) * [Issue 8](https://github.com/spyder-ide/qtsass/issues/8) - Add Travis CI integration for building and running tests ([PR 2](https://github.com/spyder-ide/qtsass/pull/2) by [@danbradham](https://github.com/danbradham)) * [Issue 7](https://github.com/spyder-ide/qtsass/issues/7) - Create RELEASE.md instructions ([PR 32](https://github.com/spyder-ide/qtsass/pull/32) by [@goanpeca](https://github.com/goanpeca)) * [Issue 6](https://github.com/spyder-ide/qtsass/issues/6) - Create conda-forge package * [Issue 5](https://github.com/spyder-ide/qtsass/issues/5) - Create a changelog ([PR 32](https://github.com/spyder-ide/qtsass/pull/32) by [@goanpeca](https://github.com/goanpeca)) * [Issue 4](https://github.com/spyder-ide/qtsass/issues/4) - Include @import support ([PR 2](https://github.com/spyder-ide/qtsass/pull/2) by [@danbradham](https://github.com/danbradham)) * [Issue 3](https://github.com/spyder-ide/qtsass/issues/3) - Make package pip installable ([PR 2](https://github.com/spyder-ide/qtsass/pull/2) by [@danbradham](https://github.com/danbradham)) In this release 14 issues were closed. ### Pull Requests Merged * [PR 32](https://github.com/spyder-ide/qtsass/pull/32) - PR: Prepare release, by [@goanpeca](https://github.com/goanpeca) ([7](https://github.com/spyder-ide/qtsass/issues/7), [5](https://github.com/spyder-ide/qtsass/issues/5), [17](https://github.com/spyder-ide/qtsass/issues/17)) * [PR 23](https://github.com/spyder-ide/qtsass/pull/23) - PR: Add support for compiling directories of QtSass, by [@danbradham](https://github.com/danbradham) ([18](https://github.com/spyder-ide/qtsass/issues/18)) * [PR 19](https://github.com/spyder-ide/qtsass/pull/19) - PR: windows ci - appveyor, by [@danbradham](https://github.com/danbradham) ([16](https://github.com/spyder-ide/qtsass/issues/16)) * [PR 15](https://github.com/spyder-ide/qtsass/pull/15) - PR: Add code coverage, by [@goanpeca](https://github.com/goanpeca) ([10](https://github.com/spyder-ide/qtsass/issues/10)) * [PR 14](https://github.com/spyder-ide/qtsass/pull/14) - PR: Add badges, by [@danbradham](https://github.com/danbradham) ([11](https://github.com/spyder-ide/qtsass/issues/11)) * [PR 13](https://github.com/spyder-ide/qtsass/pull/13) - PR: Moved tests to qtsass/tests/, by [@danbradham](https://github.com/danbradham) ([12](https://github.com/spyder-ide/qtsass/issues/12)) * [PR 2](https://github.com/spyder-ide/qtsass/pull/2) - PR: Make project pip installable and add @import support, by [@danbradham](https://github.com/danbradham) ([9](https://github.com/spyder-ide/qtsass/issues/9), [8](https://github.com/spyder-ide/qtsass/issues/8), [4](https://github.com/spyder-ide/qtsass/issues/4), [3](https://github.com/spyder-ide/qtsass/issues/3)) In this release 7 pull requests were closed. qtsass-0.3.0+git20200324.06f1519/README.md0000644000175000017500000001632113636454105014721 0ustar jdgjdg# QtSASS: Compile SCSS files to Qt stylesheets [![License - MIT](https://img.shields.io/github/license/spyder-ide/qtsass.svg)](./LICENSE.txt) [![OpenCollective Backers](https://opencollective.com/spyder/backers/badge.svg?color=blue)](#backers) [![Join the chat at https://gitter.im/spyder-ide/public](https://badges.gitter.im/spyder-ide/spyder.svg)](https://gitter.im/spyder-ide/public)
[![Github build status](https://github.com/spyder-ide/qtsass/workflows/Tests/badge.svg)](https://github.com/spyder-ide/qtsass/actions) [![Codecov coverage](https://img.shields.io/codecov/c/github/spyder-ide/qtsass/master.svg)](https://codecov.io/gh/spyder-ide/qtsass) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/spyder-ide/qtsass/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/spyder-ide/qtsass/?branch=master) *Copyright © 2015 Yann Lanthony* *Copyright © 2017–2018 Spyder Project Contributors* ## Overview [SASS](http://sass-lang.com/) brings countless amazing features to CSS. Besides being used in web development, CSS is also the way to stylize Qt-based desktop applications. However, Qt's CSS has a few variations that prevent the direct use of SASS compiler. The purpose of this tool is to fill the gap between SASS and Qt-CSS by handling those variations. ## Qt's CSS specificities The goal of QtSASS is to be able to generate a Qt-CSS stylesheet based on a 100% valid SASS file. This is how it deals with Qt's specifics and how you should modify your CSS stylesheet to use QtSASS. #### "!" in selectors Qt allows to define the style of a widget according to its states, like this: ```css QLineEdit:enabled { ... } ``` However, a "not" state is problematic because it introduces an exclamation mark in the selector's name, which is not valid SASS/CSS: ```css QLineEdit:!editable { ... } ``` QtSASS allows "!" in selectors' names; the SASS file is preprocessed and any occurence of `:!` is replaced by `:_qnot_` (for "Qt not"). However, using this feature prevents from having a 100% valid SASS file, so this support of `!` might change in the future. This can be replaced by the direct use of the `_qnot_` keyword in your SASS file: ```css QLineEdit:_qnot_editable { /* will generate QLineEdit:!editable { */ ... } ``` #### qlineargradient The qlineargradient function also has a non-valid CSS syntax. ```css qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.1 blue, stop: 0.8 green) ``` To support qlineargradient QtSASS provides a preprocessor and a SASS implementation of the qlineargradient function. The above QSS syntax will be replaced with the following: ```css qlineargradient(0, 0, 0, 1, (0.1 blue, 0.8 green)) ``` You may also use this syntax directly in your QtSASS. ``` qlineargradient(0, 0, 0, 1, (0.1 blue, 0.8 green)) # the stops parameter is a list, so you can also use variables: $stops = 0.1 blue, 0.8 green qlineargradient(0, 0, 0, 0, $stops) ``` #### qrgba Qt's rgba: ```css rgba(255, 128, 128, 50%) ``` is replaced by CSS rgba: ```css rgba(255, 128, 128, 0.5) ``` ## Executable usage To compile your SASS stylesheet to a Qt compliant CSS file: ```bash # If -o is omitted, output will be printed to console qtsass style.scss -o style.css ``` To use the watch mode and get your stylesheet auto recompiled on each file save: ```bash # If -o is omitted, output will be print to console qtsass style.scss -o style.css -w ``` To compile a directory containing SASS stylesheets to Qt compliant CSS files: ```bash qtsass ./static/scss -o ./static/css ``` You can also use watch mode to watch the entire directory for changes. ```bash qtsass ./static/scss -o ./static/css -w ``` Set the Environment Variable QTSASS_DEBUG to 1 or pass the --debug flag to enable logging. ```bash qtsass ./static/scss -o ./static/css --debug ``` ## API methods ### `compile(string, **kwargs)` Conform and Compile QtSASS source code to CSS. This function conforms QtSASS to valid SCSS before passing it to sass.compile. Any keyword arguments you provide will be combined with qtsass's default keyword arguments and passed to sass.compile. Examples: ```bash >>> import qtsass >>> qtsass.compile("QWidget {background: rgb(0, 0, 0);}") QWidget {background:black;} ``` Arguments: - string: QtSASS source code to conform and compile. - kwargs: Keyword arguments to pass to sass.compile Returns: - Qt compliant CSS string ### `compile_filename(input_file, dest_file, **kwargs)`: Compile and save QtSASS file as Qt compliant CSS. Examples: ```bash >>> import qtsass >>> qtsass.compile_filename('dummy.scss', 'dummy.css') ``` Arguments: - input_file: Path to QtSass file. - dest_file: Path to destination Qt compliant CSS file. - kwargs: Keyword arguments to pass to sass.compile ### `compile_filename(input_file, output_file, **kwargs)`: Compile and save QtSASS file as Qt compliant CSS. Examples: ```bash >>> import qtsass >>> qtsass.compile_filename('dummy.scss', 'dummy.css') ``` Arguments: - input_file: Path to QtSass file. - output_file: Path to write Qt compliant CSS. - kwargs: Keyword arguments to pass to sass.compile ### `compile_dirname(input_dir, output_dir, **kwargs)`: Compiles QtSASS files in a directory including subdirectories. ```bash >>> import qtsass >>> qtsass.compile_dirname("./scss", "./css") ``` Arguments: - input_dir: Path to directory containing QtSass files. - output_dir: Directory to write compiled Qt compliant CSS files to. - kwargs: Keyword arguments to pass to sass.compile ### `enable_logging(level=None, handler=None)`: Enable logging for qtsass. Sets the qtsass logger's level to: 1. the provided logging level 2. logging.DEBUG if the QTSASS_DEBUG envvar is a True value 3. logging.WARNING ```bash >>> import logging >>> import qtsass >>> handler = logging.StreamHandler() >>> formatter = logging.Formatter('%(level)-8s: %(name)s> %(message)s') >>> handler.setFormatter(formatter) >>> qtsass.enable_logging(level=logging.DEBUG, handler=handler) ``` Arguments: - level: Optional logging level - handler: Optional handler to add ### `watch(source, destination, compiler=None, Watcher=None)`: Watches a source file or directory, compiling QtSass files when modified. The compiler function defaults to compile_filename when source is a file and compile_dirname when source is a directory. Arguments: - source: Path to source QtSass file or directory. - destination: Path to output css file or directory. - compiler: Compile function (optional) - Watcher: Defaults to qtsass.watchers.Watcher (optional) Returns: - qtsass.watchers.Watcher instance ## Contributing Everyone is welcome to contribute! ## Sponsors Spyder and its subprojects are funded thanks to the generous support of [![Quansight](https://static.wixstatic.com/media/095d2c_2508c560e87d436ea00357abc404cf1d~mv2.png/v1/crop/x_0,y_9,w_915,h_329/fill/w_380,h_128,al_c,usm_0.66_1.00_0.01/095d2c_2508c560e87d436ea00357abc404cf1d~mv2.png)](https://www.quansight.com/)[![Numfocus](https://i2.wp.com/numfocus.org/wp-content/uploads/2017/07/NumFocus_LRG.png?fit=320%2C148&ssl=1)](https://numfocus.org/) and the donations we have received from our users around the world through [Open Collective](https://opencollective.com/spyder/): [![Sponsors](https://opencollective.com/spyder/sponsors.svg)](https://opencollective.com/spyder#support) Please consider becoming a sponsor! qtsass-0.3.0+git20200324.06f1519/.authors.yml0000644000175000017500000000217413636454105015731 0ustar jdgjdg- name: Andrey Galkin email: andrey@futoin.eu alternate_emails: - andrey@futoin.org num_commits: 5 first_commit: 2019-02-25 16:41:43 github: tmpfork - name: Eric Werner email: ewerybody+github@gmail.com num_commits: 16 first_commit: 2019-08-29 09:56:15 github: ewerybody - name: Matthew Joyce email: matsjoyce@gmail.com num_commits: 2 first_commit: 2019-07-25 07:53:58 github: matsjoyce - name: Dan Bradham email: danielbradham@gmail.com num_commits: 66 first_commit: 2018-05-01 16:28:41 github: danbradham - name: Gonzalo Peña-Castellanos email: goanpeca@gmail.com aliases: - Gonzalo Pena-Castellanos - goanpeca num_commits: 27 first_commit: 2017-11-22 18:15:12 github: goanpeca - name: Carlos Cordoba email: ccordoba12@gmail.com num_commits: 3 first_commit: 2019-04-26 04:36:01 github: ccordoba12 - name: C.A.M. Gerlach email: widenetservices@gmail.com num_commits: 3 first_commit: 2018-06-11 16:08:54 github: CAM-Gerlach - name: Yann Lanthony email: yann.lanthony@gmail.com aliases: - yann.lanthony num_commits: 22 first_commit: 2015-08-16 05:22:05 github: yann-lty qtsass-0.3.0+git20200324.06f1519/setup.py0000644000175000017500000000502213636454105015150 0ustar jdgjdg#!/usr/bin/env python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Setup script for qtsass.""" # Standard library imports from io import open import ast import os # Third party imports from setuptools import find_packages, setup # Constants HERE = os.path.abspath(os.path.dirname(__file__)) def get_version(module='qtsass'): """Get version.""" with open(os.path.join(HERE, module, '__init__.py'), 'r') as f: data = f.read() lines = data.split('\n') for line in lines: if line.startswith('__version__'): version = ast.literal_eval(line.split('=')[-1].strip()) break return version def get_description(): """Get long description.""" with open(os.path.join(HERE, 'README.md'), 'r', encoding='utf-8') as f: data = f.read() return data setup( name='qtsass', version=get_version(), description='Compile SCSS files to valid Qt stylesheets.', long_description=get_description(), long_description_content_type='text/markdown', author='Yann Lanthony', maintainer='The Spyder Project Contributors', maintainer_email='qtsass@spyder-ide.org', url='https://github.com/spyder-ide/qtsass', license='MIT', packages=find_packages(exclude=['contrib', 'docs', 'tests*']), entry_points={ 'console_scripts': [ 'qtsass = qtsass.cli:main' ] }, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Libraries :: Python Modules', ), install_requires=[ 'libsass', ], keywords='qt sass qtsass scss css qss stylesheets', ) qtsass-0.3.0+git20200324.06f1519/tests/0000755000175000017500000000000013636454105014601 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/tests/test_cli.py0000644000175000017500000001442713636454105016771 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Test qtsass cli.""" from __future__ import absolute_import # Standard library imports from collections import namedtuple from os.path import basename, exists from subprocess import PIPE, Popen import time # Local imports from . import PROJECT_DIR, await_condition, example, touch SLEEP_INTERVAL = 1 Result = namedtuple('Result', "code stdout stderr") def indent(text, prefix=' '): """Like textwrap.indent""" return ''.join([prefix + line for line in text.splitlines(True)]) def invoke(args): """Invoke qtsass cli with specified args""" kwargs = dict( stdout=PIPE, stderr=PIPE, cwd=PROJECT_DIR ) proc = Popen(['python', '-m', 'qtsass'] + args, **kwargs) return proc def invoke_with_result(args): """Invoke qtsass cli and return a Result obj""" proc = invoke(args) out, err = proc.communicate() out = out.decode('ascii', errors="ignore") err = err.decode('ascii', errors="ignore") return Result(proc.returncode, out, err) def kill(proc, timeout=1): """Kill a subprocess and return a Result obj""" proc.kill() out, err = proc.communicate() out = out.decode('ascii', errors="ignore") err = err.decode('ascii', errors="ignore") return Result(proc.returncode, out, err) def format_result(result): """Format a subprocess Result obj""" out = [ 'Subprocess Report...', 'Exit code: %s' % result.code, ] if result.stdout: out.append('stdout:') out.append(indent(result.stdout, ' ')) if result.stderr: out.append('stderr:') out.append(indent(result.stderr, ' ')) return '\n'.join(out) def test_compile_dummy_to_stdout(): """CLI compile dummy example to stdout.""" args = [example('dummy.scss')] result = invoke_with_result(args) assert result.code == 0 assert result.stdout def test_compile_dummy_to_file(tmpdir): """CLI compile dummy example to file.""" input = example('dummy.scss') output = tmpdir.join('dummy.css') args = [input, '-o', output.strpath] result = invoke_with_result(args) assert result.code == 0 assert exists(output.strpath) def test_watch_dummy(tmpdir): """CLI watch dummy example.""" input = example('dummy.scss') output = tmpdir.join('dummy.css') args = [input, '-o', output.strpath, '-w'] proc = invoke(args) # Wait for initial compile output_exists = lambda: exists(output.strpath) if not await_condition(output_exists): result = kill(proc) report = format_result(result) err = "Failed to compile dummy.scss\n" err += report assert False, report # Ensure subprocess is still alive assert proc.poll() is None # Touch input file, triggering a recompile created = output.mtime() file_modified = lambda: output.mtime() > created time.sleep(SLEEP_INTERVAL) touch(input) if not await_condition(file_modified): result = kill(proc) report = format_result(result) err = 'Modifying %s did not trigger recompile.\n' % basename(input) err += report assert False, err kill(proc) def test_compile_complex(tmpdir): """CLI compile complex example.""" input = example('complex') output = tmpdir.mkdir('output') args = [input, '-o', output.strpath] result = invoke_with_result(args) assert result.code == 0 expected_files = [output.join('light.css'), output.join('dark.css')] for file in expected_files: assert exists(file.strpath) def test_watch_complex(tmpdir): """CLI watch complex example.""" input = example('complex') output = tmpdir.mkdir('output') args = [input, '-o', output.strpath, '-w'] proc = invoke(args) expected_files = [output.join('light.css'), output.join('dark.css')] # Wait for initial compile files_created = lambda: all([exists(f.strpath) for f in expected_files]) if not await_condition(files_created): result = kill(proc) report = format_result(result) err = 'All expected files have not been created...' err += report assert False, err # Ensure subprocess is still alive assert proc.poll() is None # Input files to touch input_full = example('complex', 'light.scss') input_partial = example('complex', '_base.scss') input_nested = example('complex', 'widgets', '_qwidget.scss') def touch_and_wait(input_file, timeout=2000): """Touch a file, triggering a recompile""" filename = basename(input_file) old_mtimes = [f.mtime() for f in expected_files] files_modified = lambda: all( [f.mtime() > old_mtimes[i] for i, f in enumerate(expected_files)] ) time.sleep(SLEEP_INTERVAL) touch(input_file) if not await_condition(files_modified, timeout): result = kill(proc) report = format_result(result) err = 'Modifying %s did not trigger recompile.\n' % filename err += report for i, f in enumerate(expected_files): err += str(f) + '\n' err += str(old_mtimes[i]) + '\n' err += str(f.mtime()) + '\n' err += str(bool(f.mtime() > old_mtimes[i])) + '\n' assert False, err return True assert touch_and_wait(input_full) assert touch_and_wait(input_partial) assert touch_and_wait(input_nested) kill(proc) def test_invalid_input(): """CLI input is not a file or dir.""" proc = invoke_with_result(['file_does_not_exist.scss']) assert proc.code == 1 assert 'Error: input must be' in proc.stdout proc = invoke_with_result(['./dir/does/not/exist']) assert proc.code == 1 assert 'Error: input must be' in proc.stdout def test_dir_missing_output(): """CLI dir missing output option""" proc = invoke_with_result([example('complex')]) assert proc.code == 1 assert 'Error: missing required option' in proc.stdout qtsass-0.3.0+git20200324.06f1519/tests/test_watchers.py0000644000175000017500000001010013636454105020022 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Test qtsass cli.""" from __future__ import absolute_import # Standard library imports from os.path import dirname, exists import os import shutil import sys import time # Third party imports import pytest # Local imports #Local imports from qtsass import compile_filename from qtsass.watchers import PollingWatcher, QtWatcher from qtsass.watchers.api import retry # Local imports from . import EXAMPLES_DIR, await_condition, example, touch class CallCounter(object): def __init__(self): self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 @pytest.mark.parametrize( 'Watcher', (PollingWatcher, QtWatcher), ) def test_watchers(Watcher, tmpdir): """Stress test Watcher implementations""" # Skip when QtWatcher is None - when Qt is not installed. if not Watcher: return watch_dir = tmpdir.join('src').strpath os.makedirs(watch_dir) shutil.copy2(example('dummy.scss'), watch_dir) input = tmpdir.join('src/dummy.scss').strpath output = tmpdir.join('build/dummy.css').strpath output_exists = lambda: exists(output) c = CallCounter() w = Watcher( watch_dir=watch_dir, compiler=compile_filename, args=(input, output), ) w.connect(c) # Output should not yet exist assert not exists(output) w.start() touch(input) time.sleep(0.5) if not await_condition(output_exists): assert False, 'Output file not created...' # Removing the watch_dir should not kill the Watcher # simply stop dispatching callbacks shutil.rmtree(watch_dir) time.sleep(0.5) assert c.count == 1 # Watcher should recover once the input file is there again os.makedirs(watch_dir) shutil.copy2(example('dummy.scss'), watch_dir) time.sleep(0.5) assert c.count == 2 # Stop watcher w.stop() w.join() for _ in range(5): touch(input) # Count should not change assert c.count == 2 @pytest.mark.skipif(sys.platform.startswith('linux') or not QtWatcher, reason="Fails on linux") def test_qtwatcher(tmpdir): """Test QtWatcher implementation.""" # Constructing a QApplication will cause the QtWatcher constructed # below to use a Signal to dispatch callbacks. from qtsass.watchers.qt import QApplication qt_app = QApplication.instance() if not qt_app: qt_app = QApplication([]) watch_dir = tmpdir.join('src').strpath os.makedirs(watch_dir) shutil.copy2(example('dummy.scss'), watch_dir) input = tmpdir.join('src/dummy.scss').strpath output = tmpdir.join('build/dummy.css').strpath output_exists = lambda: exists(output) c = CallCounter() w = QtWatcher( watch_dir=watch_dir, compiler=compile_filename, args=(input, output), ) # We connect a counter directly to the Watcher's Qt Signal in order to # verify that the Watcher is actually using a Qt Signal. w.qtdispatcher.signal.connect(c) w.start() touch(input) time.sleep(0.5) if not await_condition(output_exists, qt_app=qt_app): assert False, 'Output file not created...' assert c.count == 1 # Stop watcher w.stop() w.join() def test_retry(): """Test retry decorator""" @retry(5, interval=0) def succeeds_after(n, counter): counter() if n <= counter.count: return True raise ValueError # Succeed when attempts < retries assert succeeds_after(4, CallCounter()) # Fails when retries < attemps with pytest.raises(ValueError): assert succeeds_after(6, CallCounter()) @retry(5, interval=0) def fails(): raise ValueError # Most obvious case with pytest.raises(ValueError): fails() qtsass-0.3.0+git20200324.06f1519/tests/test_functions.py0000644000175000017500000000405713636454105020230 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Test qtsass custom functions.""" from __future__ import absolute_import # Standard library imports import unittest # Local imports from qtsass.api import compile class BaseCompileTest(unittest.TestCase): def compile_scss(self, string): # NOTE: revise for better future compatibility wstr = '*{{t: {0};}}'.format(string) res = compile(wstr) return res.replace('* {\n t: ', '').replace('; }\n', '') class TestRgbaFunc(BaseCompileTest): def test_rgba(self): self.assertEqual( self.compile_scss('rgba(0, 1, 2, 0.3)'), 'rgba(0, 1, 2, 30%)' ) def test_rgba_percentage_alpha(self): result = self.compile_scss('rgba(255, 0, 125, 75%)') self.assertEqual(result, 'rgba(255, 0, 125, 75%)') def test_rgba_8bit_int_alpha(self): for in_val, out_val in ((0, 0), (128, 50), (255, 100)): result = self.compile_scss('rgba(255, 0, 125, %i)' % in_val) self.assertEqual(result, 'rgba(255, 0, 125, %i%%)' % out_val) class TestQLinearGradientFunc(BaseCompileTest): def test_color(self): self.assertEqual( self.compile_scss('qlineargradient(1, 2, 3, 4, (0 red, 1 blue))'), 'qlineargradient(x1: 1.0, y1: 2.0, x2: 3.0, y2: 4.0, ' 'stop: 0.0 rgba(255, 0, 0, 100%), stop: 1.0 rgba(0, 0, 255, 100%))' ) def test_rgba(self): self.assertEqual( self.compile_scss('qlineargradient(1, 2, 3, 4, (0 red, 0.2 rgba(5, 6, 7, 0.8)))'), 'qlineargradient(x1: 1.0, y1: 2.0, x2: 3.0, y2: 4.0, ' 'stop: 0.0 rgba(255, 0, 0, 100%), stop: 0.2 rgba(5, 6, 7, 80%))' ) if __name__ == "__main__": unittest.main(verbosity=2) qtsass-0.3.0+git20200324.06f1519/tests/__init__.py0000644000175000017500000000220313636454105016707 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- # Standard library imports from os.path import dirname, join, normpath import os import time PROJECT_DIR = normpath(dirname(dirname(__file__))) EXAMPLES_DIR = normpath(join(PROJECT_DIR, 'examples')) def example(*paths): """Get path to an example.""" return normpath(join(dirname(__file__), '..', 'examples', *paths)) def touch(file): """Touch a file.""" with open(str(file), 'a'): os.utime(str(file), None) def await_condition(condition, timeout=20, qt_app=None): """Return True if a condition is met in the given timeout period""" for _ in range(timeout): if qt_app: # pump event loop while waiting for condition qt_app.processEvents() if condition(): return True time.sleep(0.1) return False qtsass-0.3.0+git20200324.06f1519/tests/test_conformers.py0000644000175000017500000001156113636454105020373 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Test qtsass conformers.""" from __future__ import absolute_import # Standard library imports from textwrap import dedent import unittest # Local imports from qtsass.conformers import NotConformer, QLinearGradientConformer class TestNotConformer(unittest.TestCase): qss_str = 'QAbstractItemView::item:!active' css_str = 'QAbstractItemView::item:_qnot_active' def test_conform_to_scss(self): """NotConformer qss to scss.""" c = NotConformer() self.assertEqual(c.to_scss(self.qss_str), self.css_str) def test_conform_to_qss(self): """NotConformer css to qss.""" c = NotConformer() self.assertEqual(c.to_qss(self.css_str), self.qss_str) def test_round_trip(self): """NotConformer roundtrip.""" c = NotConformer() conformed_css = c.to_scss(self.qss_str) self.assertEqual(c.to_qss(conformed_css), self.qss_str) class TestQLinearGradientConformer(unittest.TestCase): css_vars_str = 'qlineargradient($x1, $y1, $x2, $y2, (0 $red, 1 $blue))' qss_vars_str = ( 'qlineargradient(x1:$x1, x2:$x2, y1:$y1, y2:$y2' 'stop: 0 $red, stop: 1 $blue)' ) css_nostops_str = 'qlineargradient(0, 0, 0, 0)' qss_nostops_str = 'qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0)' css_str = 'qlineargradient(0, 0, 0, 0, (0 red, 1 blue))' qss_singleline_str = ( 'qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0, ' 'stop: 0 red, stop: 1 blue)' ) qss_multiline_str = dedent(""" qlineargradient( x1: 0, y1: 0, x2: 0, y2: 0, stop: 0 red, stop: 1 blue ) """).strip() qss_weird_whitespace_str = ( 'qlineargradient( x1: 0, y1:0, x2: 0, y2:0, ' ' stop:0 red, stop: 1 blue )' ) css_rgba_str = ( 'qlineargradient(0, 0, 0, 0, ' '(0 rgba(0, 1, 2, 30%), 0.99 rgba(7, 8, 9, 100%)))' ) qss_rgba_str = ( 'qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0, ' 'stop: 0 rgba(0, 1, 2, 30%), stop: 0.99 rgba(7, 8, 9, 100%))' ) css_incomplete_coords_str = ( 'qlineargradient(0, 1, 0, 0, (0 red, 1 blue))' ) qss_incomplete_coords_str = ( 'qlineargradient(y1:1, stop:0 red, stop: 1 blue)' ) css_float_coords_str = ( 'qlineargradient(0, 0.75, 0, 0, (0 green, 1 pink))' ) qss_float_coords_str = ( 'qlineargradient(y1:0.75, stop:0 green, stop: 1 pink)' ) def test_does_not_affect_css_form(self): """QLinearGradientConformer no affect on css qlineargradient func.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.css_str), self.css_str) self.assertEqual(c.to_qss(self.css_str), self.css_str) def test_conform_singleline_str(self): """QLinearGradientConformer singleline qss to scss.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_singleline_str), self.css_str) def test_conform_multiline_str(self): """QLinearGradientConformer multiline qss to scss.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_multiline_str), self.css_str) def test_conform_weird_whitespace_str(self): """QLinearGradientConformer weird whitespace qss to scss.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_weird_whitespace_str), self.css_str) def test_conform_nostops_str(self): """QLinearGradientConformer qss with no stops to scss.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_nostops_str), self.css_nostops_str) def test_conform_vars_str(self): """QLinearGradientConformer qss with vars to scss.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_vars_str), self.css_vars_str) def test_conform_rgba_str(self): """QLinearGradientConformer qss with rgba to scss.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_rgba_str), self.css_rgba_str) def test_incomplete_coords(self): """QLinearGradientConformer qss with not all 4 coordinates given.""" c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_incomplete_coords_str), self.css_incomplete_coords_str) def test_float_coords(self): c = QLinearGradientConformer() self.assertEqual(c.to_scss(self.qss_float_coords_str), self.css_float_coords_str) if __name__ == "__main__": unittest.main(verbosity=2) qtsass-0.3.0+git20200324.06f1519/tests/test_api.py0000644000175000017500000000710613636454105016767 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Test qtsass api.""" from __future__ import absolute_import # Standard library imports from os.path import exists import logging # Third party imports import pytest import sass # Local imports import qtsass # Local imports from . import EXAMPLES_DIR, PROJECT_DIR, example COLORS_STR = """ QWidget { background: rgba(127, 127, 127, 100%); color: rgb(255, 255, 255); } """ QLINEARGRADIENTS_STR = """ QWidget { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.1 blue, stop: 0.8 green ); } """ QNOT_STR = """ QLineEdit:!editable { background: white; } """ IMPORT_STR = """ @import 'dummy'; """ CUSTOM_BORDER_STR = """ QWidget { border: custom_border(); } """ def setup_module(): qtsass.enable_logging(level=logging.DEBUG) def teardown_module(): qtsass.enable_logging(level=logging.WARNING) def test_compile_strings(): """compile various strings.""" qtsass.compile(COLORS_STR) qtsass.compile(QLINEARGRADIENTS_STR) qtsass.compile(QNOT_STR) def test_compile_import_raises(): """compile string with import raises.""" with pytest.raises(sass.CompileError): qtsass.compile(IMPORT_STR) def test_compile_import_with_include_paths(): """compile string with include_paths""" qtsass.compile(IMPORT_STR, include_paths=[EXAMPLES_DIR]) def test_compile_raises_ValueError(): """compile raises ValueError with invalid arguments""" # Pass invalid type to importers - must be sequence with pytest.raises(ValueError): qtsass.compile(COLORS_STR, importers=lambda x: None) # Pass invalid type to custom_functions with pytest.raises(ValueError): qtsass.compile(COLORS_STR, custom_functions=lambda x: None) def test_compile_custom_function(): """compile string with custom_functions""" custom_str = ( 'QWidget {\n' ' border: custom_border();\n' '}' ) def custom_border(): return '1px solid' css = qtsass.compile(custom_str, custom_functions=[custom_border]) assert '1px solid' in css assert 'custom_border()' not in css def test_compile_filename(tmpdir): """compile_filename simple.""" output = tmpdir.join('dummy.css') qtsass.compile_filename(example('dummy.scss'), output.strpath) assert exists(output.strpath) def test_compile_filename_imports(tmpdir): """compile_filename with imports.""" output = tmpdir.join('dark.css') qtsass.compile_filename(example('complex', 'dark.scss'), output.strpath) assert exists(output.strpath) def test_compile_dirname(tmpdir): """compile_dirname complex.""" output = tmpdir.join('complex') qtsass.compile_dirname(example('complex'), output.strpath) assert exists(output.join('dark.css').strpath) assert exists(output.join('light.css').strpath) def test_watch_raises_ValueError(tmpdir): """watch raises ValueError when source does not exist.""" # Watch file does not raise _ = qtsass.watch(example('dummy.scss'), tmpdir.join('dummy.scss').strpath) # Watch dir does not raise _ = qtsass.watch(example('complex'), tmpdir.join('complex').strpath) with pytest.raises(ValueError): _ = qtsass.watch('does_not_exist', 'does_not_exist') qtsass-0.3.0+git20200324.06f1519/rever.xsh0000644000175000017500000002043413636454105015311 0ustar jdgjdg# Standard library imports import ast import os # Third party imports from rever.activity import activity from rever.tools import replace_in_file $ACTIVITIES = [ 'checkout', 'clean_repo', 'update_repo', 'install_deps', 'format_code', 'run_tests', 'update_release_version', 'create_python_distributions', 'upload_test_distributions', 'install_test_distributions', 'run_install_tests', 'create_changelog', 'commit_release_version', 'authors', 'add_tag', 'upload_python_distributions', 'update_dev_version', 'commit_dev_version', 'push', ] $PROJECT = "qtsass" $MODULE = $PROJECT $GITHUB_ORG = 'spyder-ide' $GITHUB_REPO = $PROJECT $VERSION_BUMP_PATTERNS = [ # These note where/how to find the version numbers ($MODULE + '/__init__.py', r'__version__\s*=.*', '__version__ = "$VERSION"'), ] $AUTHORS_FILENAME = "AUTHORS.md" $AUTHORS_TEMPLATE = """# Authors The $PROJECT project has some great contributors! They are: {authors} These have been sorted {sorting_text}. """ $AUTHORS_FORMAT= "- [{name}](https://github.com/{github})\n" $AUTHORS_SORTBY = "alpha" $TEMP_ENV = 'tmp-' + $PROJECT $CONDA_ACTIVATE_SCRIPT = 'activate.xsh' $HERE = os.path.abspath(os.path.dirname(__file__)) # --- Helpers # ---------------------------------------------------------------------------- class Colors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def cprint(text, color): """Print colored text.""" print(color + text + Colors.ENDC) def get_version(version_type, module=$MODULE): """ Get version info. Tuple with three items, major.minor.patch """ with open(os.path.join($HERE, module, "__init__.py")) as fh: data = fh.read() major, minor, patch = 'MAJOR', 'MINOR', 'PATCH' lines = data.split("\n") for line in lines: if line.startswith("__version__"): version = ast.literal_eval(line.split("=")[-1].strip()) major, minor, patch = [int(v) for v in version.split('.')[:3]] version_type = version_type.lower() if version_type == 'major': major += 1 minor = 0 patch = 0 elif version_type == 'minor': minor += 1 patch = 0 elif version_type == 'patch': patch += 1 elif version_type in ['check', 'setup']: pass elif len(version_type.split('.')) == 3: major, minor, patch = version_type.split('.') else: raise Exception('Invalid option! Must provide version type: [major|minor|patch|MAJOR.MINOR.PATCH]') major = str(major) minor = str(minor) patch = str(patch) version = '.'.join([major, minor, patch]) if version_type not in ['check', 'setup']: cprint('\n\nReleasing version {}'.format(version), Colors.OKBLUE) print('\n\n') return version # Actual versions to use $NEW_VERSION = get_version($VERSION) $DEV_VERSION = $NEW_VERSION + '.dev0' def activate(env_name): """ Activate a conda environment. """ if not os.path.isfile($CONDA_ACTIVATE_SCRIPT): with open('activate.xsh', 'w') as fh: fh.write($(conda shell.xonsh hook)) # Activate environment source activate.xsh conda activate @(env_name) $[conda info] def update_version(version): """ Update version patterns. """ for fpath, pattern, new_pattern in $VERSION_BUMP_PATTERNS: new_pattern = new_pattern.replace('$VERSION', version) replace_in_file(pattern, new_pattern, fpath) # --- Activities # ---------------------------------------------------------------------------- @activity def checkout(branch='master'): """ Checkout master branch. """ git stash git checkout @(branch) # Check that origin and remote exist remotes = $(git remote -v) if not ('origin' in remotes and 'upstream' in remotes): raise Exception('Must have git remotes origin (pointing to fork) and upstream (pointing to repo)') @activity def clean_repo(): """ Clean the repo from build/dist and other files. """ import pathlib # Remove python files for p in pathlib.Path('.').rglob('*.py[co]'): p.unlink() for p in pathlib.Path('.').rglob('*.orig'): p.unlink() for p in pathlib.Path('.').rglob('__pycache__'): p.rmdir() rm -rf CHANGELOG.temp rm -rf .pytest_cache/ rm -rf build/ rm -rf dist/ rm -rf activate.xsh rm -rf $MODULE.egg-info # Delete files not tracked by git? # git clean -xfd @activity def update_repo(branch='master'): """ Stash any current changes and ensure you have the latest version from origin. """ git stash git pull upstream @(branch) git push origin @(branch) @activity def install_deps(): """ Install release and test dependencies. """ try: conda remove --name $TEMP_ENV --yes --quiet --all except: pass conda create --name $TEMP_ENV python=3.7 --yes --quiet activate($TEMP_ENV) pip install -r requirements/install.txt pip install -r requirements/dev.txt pip install -r requirements/release.txt @activity def format_code(): """ Format code. """ activate($TEMP_ENV) try: python run_checks_and_format.py except Exception: pass @activity def run_tests(): """ Run simple import tests before cleaning repository. """ pytest tests --cov=$MODULE @activity def update_release_version(): """ Update version in `__init__.py` (set release version, remove 'dev0'). and on the package.json file. """ update_version($NEW_VERSION) @activity def create_python_distributions(): """ Create distributions. """ activate($TEMP_ENV) python setup.py sdist bdist_wheel @activity def upload_test_distributions(): """ Upload test distributions. """ activate($TEMP_ENV) cprint("Yow will be asked to provide credentials", Colors.OKBLUE) print("\n\n") # The file might be already there try: python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* except Exception as err: print(err) @activity def install_test_distributions(): """ Upload test distributions. """ activate($TEMP_ENV) # Python package pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple $PROJECT==$NEW_VERSION @activity def run_install_tests(): """ Run simple import tests before cleaning repository. """ activate($TEMP_ENV) $MODULE --help @activity def create_changelog(): """ Create changelog using loghub. """ loghub $GITHUB_ORG/$GITHUB_REPO -zr @($PROJECT + ' v' + $NEW_VERSION) with open('CHANGELOG.temp', 'r') as fh: new_changelog_lines = fh.read().split('\n') with open('CHANGELOG.md', 'r') as fh: lines = fh.read().split('\n') new_lines = lines[:2] + new_changelog_lines + lines[2:] with open('CHANGELOG.md', 'w') as fh: fh.write('\n'.join(new_lines)) @activity def commit_release_version(): """ Commit release version. """ git add . git commit -m @('Set release version to ' + $NEW_VERSION + ' [ci skip]') @activity def add_tag(): """ Add release tag. """ # TODO: Add check to see if tag already exists? git tag -a @('v' + $NEW_VERSION) -m @('Tag version ' + $NEW_VERSION + ' [ci skip]') @activity def upload_python_distributions(): """ Upload the distributions to pypi production environment. """ activate($TEMP_ENV) cprint("Yow will be asked to provide credentials", Colors.OKBLUE) print("\n\n") # The file might be already there try: twine upload dist/* except Exception as err: print(err) @activity def update_dev_version(): """ Update `__init__.py` (add 'dev0'). """ update_version($DEV_VERSION) @activity def commit_dev_version(): """" Commit dev changes. """ git add . git commit -m "Restore dev version [ci skip]" --no-verify @activity def push(branch='master'): """ Push changes. """ # Push changes git push origin @(branch) git push upstream @(branch) # Push tags git push origin --tags git push upstream --tags qtsass-0.3.0+git20200324.06f1519/.gitignore0000644000175000017500000000706013636454105015432 0ustar jdgjdg################# ## Eclipse ################# *.orig *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ build/ [Bb]in/ [Oo]bj/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml *.publishproj # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac crap .DS_Store ############# ## Python ############# *.py[cod] # Packages *.egg *.egg-info dist/ build/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio *.iml ## Directory-based project format: .idea/ # if you remove the above rule, at least ignore the following: # User-specific stuff: # .idea/workspace.xml # .idea/tasks.xml # .idea/dictionaries # Sensitive or high-churn files: # .idea/dataSources.ids # .idea/dataSources.xml # .idea/sqlDataSources.xml # .idea/dynamic.xml # .idea/uiDesigner.xml # Gradle: # .idea/gradle.xml # .idea/libraries # Mongo Explorer plugin: # .idea/mongoSettings.xml ## File-based project format: *.ipr *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties # pytest .pytest_cache # Project virtualenv venv/ # Rever rever/ activate.xsh # Loghub CHANGELOG.temp qtsass-0.3.0+git20200324.06f1519/examples/0000755000175000017500000000000013636454105015255 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/examples/dummy.scss0000644000175000017500000000134613636454105017311 0ustar jdgjdg // This comment will not appear in generated css file /* This comment will appear in generated css file */ // !editable is not valid sass/css but is tolerated in qtsass since widespread in Qt stylesheets. QComboBox:!editable:on, QComboBox::drop-down:editable:on { color: blue; } // standard qss qlineargradient syntax works QListView::item:selected{ background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2:1, stop: 0.2 #3f3f3f, stop: 0.8 red ); } // You may also use QtSASS syntax directly QTreeView::item:selected{ $start: 0.2; $stops: $start #3f3f3f, $start + 0.6 red; background-color: qlineargradient(0, 0, 0, 1, $stops); color: rgba(255, 10, 10, 0.5); } qtsass-0.3.0+git20200324.06f1519/examples/complex/0000755000175000017500000000000013636454105016724 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/examples/complex/widgets/0000755000175000017500000000000013636454105020372 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/examples/complex/widgets/_qwidget.scss0000644000175000017500000000007613636454105023075 0ustar jdgjdgQWidget { background: $background; color: $primary; } qtsass-0.3.0+git20200324.06f1519/examples/complex/widgets/_qlineedit.scss0000644000175000017500000000016513636454105023406 0ustar jdgjdgQLineEdit { color: $primary; padding: $text-padding; border: 0; border-bottom: 1px solid $primary; } qtsass-0.3.0+git20200324.06f1519/examples/complex/widgets/_qpushbutton.scss0000644000175000017500000000035213636454105024022 0ustar jdgjdgQPushButton { background: $background; color: $accent; padding: $text-padding; border: 0; border-radius: $border-radius; } QPushButton:hover, QPushButton:focus { background: $accent; color: $background; } qtsass-0.3.0+git20200324.06f1519/examples/complex/light.scss0000644000175000017500000000002013636454105020720 0ustar jdgjdg@import 'base'; qtsass-0.3.0+git20200324.06f1519/examples/complex/_defaults.scss0000644000175000017500000000042513636454105021570 0ustar jdgjdg// Fonts $font-stack: Helvetica, sans-serif !default; // Colors $background: rgb(255, 255, 255) !default; $primary: rgb(35, 35, 35) !default; $accent: rgb(35, 75, 135) !default; // Paddings $text-padding: 16px 8px 16px 8px !default; // Borders $border-radius: 2px !default; qtsass-0.3.0+git20200324.06f1519/examples/complex/_base.scss0000644000175000017500000000015313636454105020671 0ustar jdgjdg@import 'defaults'; @import 'widgets/qwidget'; @import 'widgets/qpushbutton'; @import 'widgets/qlineedit'; qtsass-0.3.0+git20200324.06f1519/examples/complex/dark.scss0000644000175000017500000000017613636454105020546 0ustar jdgjdg// Change default values $background: rgb(35, 35, 35); $primary: rgb(255, 255, 255); // Import base style @import 'base'; qtsass-0.3.0+git20200324.06f1519/MANIFEST.in0000644000175000017500000000020513636454105015172 0ustar jdgjdg# Include the README include *.md # Include the license file include LICENSE.txt # Include the data files recursive-include data * qtsass-0.3.0+git20200324.06f1519/qtsass/0000755000175000017500000000000013636454105014755 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/qtsass/watchers/0000755000175000017500000000000013636454105016575 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/qtsass/watchers/api.py0000644000175000017500000001032213636454105017716 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """The filesystem watcher api.""" # yapf: disable from __future__ import absolute_import # Standard library imports import functools import logging import time _log = logging.getLogger(__name__) def retry(n, interval=0.1): """Retry a function or method n times before raising an exception. :param n: Number of times to retry :param interval: Time to sleep before attempts """ def decorate(fn): @functools.wraps(fn) def attempt(*args, **kwargs): attempts = 0 while True: try: return fn(*args, **kwargs) except Exception: attempts += 1 if n <= attempts: raise time.sleep(interval) return attempt return decorate # yapf: enable class Watcher(object): """Watcher base class. Watchers monitor a file or directory and call the on_change method when a change occurs. The on_change method should trigger the compiler function passed in during construction and dispatch the result to all connected callbacks. Watcher implementations must inherit from this base class. Subclasses should perform any setup required in the setup method, rather than overriding __init__. """ def __init__(self, watch_dir, compiler, args=None, kwargs=None): """Store initialization values and call Watcher.setup.""" self._watch_dir = watch_dir self._compiler = compiler self._args = args or () self._kwargs = kwargs or {} self._callbacks = set() self._log = _log self.setup() def setup(self): """Perform any setup required here. Rather than implement __init__, subclasses can perform any setup in this method. """ return NotImplemented def start(self): """Start this Watcher.""" return NotImplemented def stop(self): """Stop this Watcher.""" return NotImplemented def join(self): """Wait for this Watcher to finish.""" return NotImplemented @retry(5) def compile(self): """Call the Watcher's compiler.""" self._log.debug( 'Compiling sass...%s(*%s, **%s)', self._compiler, self._args, self._kwargs, ) return self._compiler(*self._args, **self._kwargs) def compile_and_dispatch(self): """Compile and dispatch the resulting css to connected callbacks.""" self._log.debug('Compiling and dispatching....') try: css = self.compile() except Exception: self._log.exception('Failed to compile...') return self.dispatch(css) def dispatch(self, css): """Dispatch css to connected callbacks.""" self._log.debug('Dispatching callbacks...') for callback in self._callbacks: callback(css) def on_change(self): """Call when a change is detected. Subclasses must call this method when they detect a change. Subclasses may also override this method in order to manually compile and dispatch callbacks. For example, a Qt implementation may use signals and slots to ensure that compiling and executing callbacks happens in the main GUI thread. """ self._log.debug('Change detected...') self.compile_and_dispatch() def connect(self, fn): """Connect a callback to this Watcher. All callbacks are called when a change is detected. Callbacks are passed the compiled css. """ self._log.debug('Connecting callback: %s', fn) self._callbacks.add(fn) def disconnect(self, fn): """Disconnect a callback from this Watcher.""" self._log.debug('Disconnecting callback: %s', fn) self._callbacks.discard(fn) qtsass-0.3.0+git20200324.06f1519/qtsass/watchers/snapshots.py0000644000175000017500000000357313636454105021201 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Contains the fallback implementation of the Watcher api.""" # yapf: disable from __future__ import absolute_import, print_function # Standard library imports import os # Local imports from qtsass.importers import norm_path # yapf: enable def take(dir_or_file, depth=3): """Return a dict mapping files and folders to their mtimes.""" if os.path.isfile(dir_or_file): path = norm_path(dir_or_file) return {path: os.path.getmtime(path)} if not os.path.isdir(dir_or_file): return {} snapshot = {} base_depth = len(norm_path(dir_or_file).split('/')) for root, subdirs, files in os.walk(dir_or_file): path = norm_path(root) if len(path.split('/')) - base_depth == depth: subdirs[:] = [] snapshot[path] = os.path.getmtime(path) for f in files: path = norm_path(root, f) snapshot[path] = os.path.getmtime(path) return snapshot def diff(prev_snapshot, next_snapshot): """Return a dict containing changes between two snapshots.""" changes = {} for path in set(prev_snapshot.keys()) | set(next_snapshot.keys()): if path in prev_snapshot and path not in next_snapshot: changes[path] = 'Deleted' elif path not in prev_snapshot and path in next_snapshot: changes[path] = 'Created' else: prev_mtime = prev_snapshot[path] next_mtime = next_snapshot[path] if next_mtime > prev_mtime: changes[path] = 'Changed' return changes qtsass-0.3.0+git20200324.06f1519/qtsass/watchers/qt.py0000644000175000017500000000570213636454105017577 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Contains the Qt implementation of the Watcher api.""" # yapf: disable from __future__ import absolute_import # Local imports from qtsass.watchers.polling import PollingWatcher # We cascade through Qt bindings here rather than relying on a comprehensive # Qt compatability library like qtpy or Qt.py. This prevents us from forcing a # specific compatability library on users. QT_BINDING = None if not QT_BINDING: try: from PySide2.QtWidgets import QApplication from PySide2.QtCore import QObject, Signal QT_BINDING = 'pyside2' except ImportError: pass if not QT_BINDING: try: from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QObject from PyQt5.QtCore import pyqtSignal as Signal QT_BINDING = 'pyqt5' except ImportError: pass if not QT_BINDING: try: from PySide.QtGui import QApplication from PySide2.QtCore import QObject, Signal QT_BINDING == 'pyside' except ImportError: pass if not QT_BINDING: from PyQt4.QtGui import QApplication from PyQt4.QtCore import QObject from PyQt4.QtCore import pyqtSignal as Signal QT_BINDING == 'pyqt4' # yapf: enable class QtDispatcher(QObject): """Used by QtWatcher to dispatch callbacks in the main ui thread.""" signal = Signal() class QtWatcher(PollingWatcher): """The Qt implementation of the Watcher api. Subclasses PollingWatcher but dispatches :meth:`compile_and_dispatch` using a Qt Signal to ensure that these calls are executed in the main ui thread. We aren't using a QFileSystemWatcher because it fails to report changes in certain circumstances. """ _qt_binding = QT_BINDING def setup(self): """Set up QtWatcher.""" super(QtWatcher, self).setup() self._qtdispatcher = None @property def qtdispatcher(self): """Get the QtDispatcher.""" if self._qtdispatcher is None: self._qtdispatcher = QtDispatcher() self._qtdispatcher.signal.connect(self.compile_and_dispatch) return self._qtdispatcher def on_change(self): """Call when a change is detected.""" self._log.debug('Change detected...') # If a QApplication event loop has not been started # call compile_and_dispatch in the current thread. if not QApplication.instance(): return super(PollingWatcher, self).compile_and_dispatch() # Create and use a QtDispatcher to ensure compile and any # connected callbacks get executed in the main gui thread. self.qtdispatcher.signal.emit() qtsass-0.3.0+git20200324.06f1519/qtsass/watchers/__init__.py0000644000175000017500000000143613636454105020712 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """The qtsass Watcher is responsible for watching and recompiling sass. The default Watcher is the QtWatcher. If Qt is unavailable we fallback to the PollingWatcher. """ # yapf: disable from __future__ import absolute_import # Local imports from qtsass.watchers.polling import PollingWatcher try: from qtsass.watchers.qt import QtWatcher except ImportError: QtWatcher = None # yapf: enable Watcher = QtWatcher or PollingWatcher qtsass-0.3.0+git20200324.06f1519/qtsass/watchers/polling.py0000644000175000017500000000716713636454105020626 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Contains the fallback implementation of the Watcher api.""" # yapf: disable from __future__ import absolute_import, print_function # Standard library imports import atexit import threading # Local imports from qtsass.watchers import snapshots from qtsass.watchers.api import Watcher # yapf: enable class PollingThread(threading.Thread): """A thread that fires a callback at an interval.""" def __init__(self, callback, interval): """Initialize the thread. :param callback: Callback function to repeat. :param interval: Number of seconds to sleep between calls. """ super(PollingThread, self).__init__() self.daemon = True self.callback = callback self.interval = interval self._shutdown = threading.Event() self._stopped = threading.Event() self._started = threading.Event() atexit.register(self.stop) @property def started(self): """Check if the thread has started.""" return self._started.is_set() @property def stopped(self): """Check if the thread has stopped.""" return self._stopped.is_set() @property def shutdown(self): """Check if the thread has shutdown.""" return self._shutdown.is_set() def stop(self): """Set the shutdown event for this thread and wait for it to stop.""" if not self.started and not self.shutdown: return self._shutdown.set() self._stopped.wait() def run(self): """Threads main loop.""" try: self._started.set() while True: self.callback() if self._shutdown.wait(self.interval): break finally: self._stopped.set() class PollingWatcher(Watcher): """Polls a directory recursively for changes. Detects file and directory changes, deletions, and creations. Recursion depth is limited to 2 levels. We use a limit because the scss file we're watching for changes could be sitting in the root of a project rather than a dedicated scss directory. That could lead to snapshots taking too long to build and diff. It's probably safe to assume that users aren't nesting scss deeper than a couple of levels. """ def setup(self): """Set up the PollingWatcher. A PollingThread is created but not started. """ self._snapshot_depth = 2 self._snapshot = snapshots.take(self._watch_dir, self._snapshot_depth) self._thread = PollingThread(self.run, interval=1) def start(self): """Start the PollingThread.""" self._thread.start() def stop(self): """Stop the PollingThread.""" self._thread.stop() def join(self): """Wait for the PollingThread to finish. You should always call stop before join. """ self._thread.join() def run(self): """Take a new snapshot and call on_change when a change is detected. Called repeatedly by the PollingThread. """ next_snapshot = snapshots.take(self._watch_dir, self._snapshot_depth) changes = snapshots.diff(self._snapshot, next_snapshot) if changes: self._snapshot = next_snapshot self.on_change() qtsass-0.3.0+git20200324.06f1519/qtsass/api.py0000644000175000017500000001725413636454105016111 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """qtsass - Compile SCSS files to valid Qt stylesheets.""" # yapf: disable from __future__ import absolute_import, print_function # Standard library imports import logging import os import sys # Third party imports import sass # Local imports from qtsass.conformers import qt_conform, scss_conform from qtsass.functions import qlineargradient, rgba from qtsass.importers import qss_importer if sys.version_info[0] == 3: from collections.abc import Mapping, Sequence else: from collections import Mapping, Sequence # yapf: enable # Constants DEFAULT_CUSTOM_FUNCTIONS = {'qlineargradient': qlineargradient, 'rgba': rgba} DEFAULT_SOURCE_COMMENTS = False # Logger setup _log = logging.getLogger(__name__) def compile(string, **kwargs): """ Conform and Compile QtSASS source code to CSS. This function conforms QtSASS to valid SCSS before passing it to sass.compile. Any keyword arguments you provide will be combined with qtsass's default keyword arguments and passed to sass.compile. .. code-block:: python >>> import qtsass >>> qtsass.compile("QWidget {background: rgb(0, 0, 0);}") QWidget {background:black;} :param string: QtSASS source code to conform and compile. :param kwargs: Keyword arguments to pass to sass.compile :returns: CSS string """ kwargs.setdefault('source_comments', DEFAULT_SOURCE_COMMENTS) kwargs.setdefault('custom_functions', []) kwargs.setdefault('importers', []) kwargs.setdefault('include_paths', []) # Add QtSass importers if isinstance(kwargs['importers'], Sequence): kwargs['importers'] = (list(kwargs['importers']) + [(0, qss_importer(*kwargs['include_paths']))]) else: raise ValueError('Expected Sequence for importers ' 'got {}'.format(type(kwargs['importers']))) # Add QtSass custom_functions if isinstance(kwargs['custom_functions'], Sequence): kwargs['custom_functions'] = dict( DEFAULT_CUSTOM_FUNCTIONS, **{fn.__name__: fn for fn in kwargs['custom_functions']}) elif isinstance(kwargs['custom_functions'], Mapping): kwargs['custom_functions'].update(DEFAULT_CUSTOM_FUNCTIONS) else: raise ValueError('Expected Sequence or Mapping for custom_functions ' 'got {}'.format(type(kwargs['custom_functions']))) # Conform QtSass source code try: kwargs['string'] = scss_conform(string) except Exception: _log.error('Failed to conform source code') raise if _log.isEnabledFor(logging.DEBUG): from pprint import pformat log_kwargs = dict(kwargs) log_kwargs['string'] = 'Conformed SCSS<...>' _log.debug('Calling sass.compile with:') _log.debug(pformat(log_kwargs)) _log.debug('Conformed scss:\n{}'.format(kwargs['string'])) # Compile QtSass source code try: return qt_conform(sass.compile(**kwargs)) except sass.CompileError: _log.error('Failed to compile source code') raise def compile_filename(input_file, output_file, **kwargs): """Compile and save QtSASS file as CSS. .. code-block:: python >>> import qtsass >>> qtsass.compile_filename("dummy.scss", "dummy.css") :param input_file: Path to QtSass file. :param output_file: Path to write Qt compliant CSS. :param kwargs: Keyword arguments to pass to sass.compile :returns: CSS string """ input_root = os.path.abspath(os.path.dirname(input_file)) kwargs.setdefault('include_paths', [input_root]) with open(input_file, 'r') as f: string = f.read() _log.info('Compiling {}...'.format(os.path.normpath(input_file))) css = compile(string, **kwargs) output_root = os.path.abspath(os.path.dirname(output_file)) if not os.path.isdir(output_root): os.makedirs(output_root) with open(output_file, 'w') as css_file: css_file.write(css) _log.info('Created CSS file {}'.format(os.path.normpath(output_file))) return css def compile_dirname(input_dir, output_dir, **kwargs): """Compiles QtSASS files in a directory including subdirectories. .. code-block:: python >>> import qtsass >>> qtsass.compile_dirname("./scss", "./css") :param input_dir: Directory containing QtSass files. :param output_dir: Directory to write compiled Qt compliant CSS files to. :param kwargs: Keyword arguments to pass to sass.compile """ kwargs.setdefault('include_paths', [input_dir]) def is_valid(file_name): return not file_name.startswith('_') and file_name.endswith('.scss') for root, _, files in os.walk(input_dir): relative_root = os.path.relpath(root, input_dir) output_root = os.path.join(output_dir, relative_root) fkwargs = dict(kwargs) fkwargs['include_paths'] = fkwargs['include_paths'] + [root] for file_name in [f for f in files if is_valid(f)]: scss_path = os.path.join(root, file_name) css_file = os.path.splitext(file_name)[0] + '.css' css_path = os.path.join(output_root, css_file) if not os.path.isdir(output_root): os.makedirs(output_root) compile_filename(scss_path, css_path, **fkwargs) def enable_logging(level=None, handler=None): """Enable logging for qtsass. Sets the qtsass logger's level to: 1. the provided logging level 2. logging.DEBUG if the QTSASS_DEBUG envvar is a True value 3. logging.WARNING .. code-block:: python >>> import logging >>> import qtsass >>> handler = logging.StreamHandler() >>> formatter = logging.Formatter('%(level)-8s: %(name)s> %(message)s') >>> handler.setFormatter(formatter) >>> qtsass.enable_logging(level=logging.DEBUG, handler=handler) :param level: Optional logging level :param handler: Optional handler to add """ if level is None: debug = os.environ.get('QTSASS_DEBUG', False) if debug in ('1', 'true', 'True', 'TRUE', 'on', 'On', 'ON'): level = logging.DEBUG else: level = logging.WARNING logger = logging.getLogger('qtsass') logger.setLevel(level) if handler: logger.addHandler(handler) _log.debug('logging level set to {}.'.format(level)) def watch(source, destination, compiler=None, Watcher=None): """ Watches a source file or directory, compiling QtSass files when modified. The compiler function defaults to compile_filename when source is a file and compile_dirname when source is a directory. :param source: Path to source QtSass file or directory. :param destination: Path to output css file or directory. :param compiler: Compile function (optional) :param Watcher: Defaults to qtsass.watchers.Watcher (optional) :returns: qtsass.watchers.Watcher instance """ if os.path.isfile(source): watch_dir = os.path.dirname(source) compiler = compiler or compile_filename elif os.path.isdir(source): watch_dir = source compiler = compiler or compile_dirname else: raise ValueError('source arg must be a dirname or filename...') if Watcher is None: from qtsass.watchers import Watcher watcher = Watcher(watch_dir, compiler, (source, destination)) return watcher qtsass-0.3.0+git20200324.06f1519/qtsass/functions.py0000644000175000017500000000434713636454105017347 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Libsass functions.""" # yapf: disable # Third party imports import sass # yapf: enable def rgba(r, g, b, a): """Convert r,g,b,a values to standard format. Where `a` is alpha! In CSS alpha can be given as: * float from 0.0 (fully transparent) to 1.0 (opaque) In Qt or qss that is: * int from 0 (fully transparent) to 255 (opaque) A percentage value 0% (fully transparent) to 100% (opaque) works in BOTH systems the same way! """ result = 'rgba({}, {}, {}, {}%)' if isinstance(r, sass.SassNumber): if a.unit == '%': alpha = a.value elif a.value > 1.0: # A value from 0 to 255 is coming in, convert to % alpha = a.value / 2.55 else: alpha = a.value * 100 return result.format( int(r.value), int(g.value), int(b.value), int(alpha), ) elif isinstance(r, float): return result.format(int(r), int(g), int(b), int(a * 100)) def rgba_from_color(color): """ Conform rgba. :type color: sass.SassColor """ # Inner rgba() call if not isinstance(color, sass.SassColor): return '{}'.format(color) return rgba(color.r, color.g, color.b, color.a) def qlineargradient(x1, y1, x2, y2, stops): """ Implement qss qlineargradient function for scss. :type x1: sass.SassNumber :type y1: sass.SassNumber :type x2: sass.SassNumber :type y2: sass.SassNumber :type stops: sass.SassList :return: """ stops_str = [] for stop in stops[0]: pos, color = stop[0] stops_str.append('stop: {} {}'.format( pos.value, rgba_from_color(color), )) template = 'qlineargradient(x1: {}, y1: {}, x2: {}, y2: {}, {})' return template.format(x1.value, y1.value, x2.value, y2.value, ', '.join(stops_str)) qtsass-0.3.0+git20200324.06f1519/qtsass/__main__.py0000644000175000017500000000107113636454105017046 0ustar jdgjdg#!/usr/bin/env python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """qtsass command line interface.""" # yapf: disable from __future__ import absolute_import # Local imports from qtsass import cli # yapf: enable if __name__ == '__main__': cli.main() qtsass-0.3.0+git20200324.06f1519/qtsass/__init__.py0000644000175000017500000000261713636454105017074 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """ The SASS language brings countless amazing features to CSS. Besides being used in web development, CSS is also the way to stylize Qt-based desktop applications. However, Qt's CSS has a few variations that prevent the direct use of SASS compiler. The purpose of qtsass is to fill the gap between SASS and Qt-CSS by handling those variations. """ # yapf: disable from __future__ import absolute_import # Standard library imports import logging # Local imports from qtsass.api import ( compile, compile_dirname, compile_filename, enable_logging, watch, ) # yapf: enable # Setup Logging logging.getLogger(__name__).addHandler(logging.NullHandler()) enable_logging() # Constants __version__ = '0.3.0.dev0' def _to_version_info(version): """Convert a version string to a number and string tuple.""" parts = [] for part in version.split('.'): try: part = int(part) except ValueError: pass parts.append(part) return tuple(parts) VERSION_INFO = _to_version_info(__version__) qtsass-0.3.0+git20200324.06f1519/qtsass/importers.py0000644000175000017500000000434013636454105017354 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Libsass importers.""" # yapf: disable from __future__ import absolute_import # Standard library imports import os # Local imports from qtsass.conformers import scss_conform # yapf: enable def norm_path(*parts): """Normalize path.""" return os.path.normpath(os.path.join(*parts)).replace('\\', '/') def qss_importer(*include_paths): """ Return function which conforms imported qss files to valid scss. This fucntion is to be used as an importer for sass.compile. :param include_paths: Directorys containing scss, css, and sass files. """ include_paths def find_file(import_file): # Create partial import filename dirname, basename = os.path.split(import_file) if dirname: import_partial_file = '/'.join([dirname, '_' + basename]) else: import_partial_file = '_' + basename # Build potential file paths for @import "import_file" potential_files = [] for ext in ['', '.scss', '.css', '.sass']: full_name = import_file + ext partial_name = import_partial_file + ext potential_files.append(full_name) potential_files.append(partial_name) for path in include_paths: potential_files.append(norm_path(path, full_name)) potential_files.append(norm_path(path, partial_name)) # Return first existing potential file for potential_file in potential_files: if os.path.isfile(potential_file): return potential_file return None def import_and_conform_file(import_file): """Return base file and conformed scss file.""" real_import_file = find_file(import_file) with open(real_import_file, 'r') as f: import_str = f.read() return [(import_file, scss_conform(import_str))] return import_and_conform_file qtsass-0.3.0+git20200324.06f1519/qtsass/conformers.py0000644000175000017500000001111713636454105017505 0ustar jdgjdg# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Conform qss to compliant scss and css to valid qss.""" # yapf: disable from __future__ import absolute_import, print_function # Standard library imports import re # yapf: enable _DEFAULT_COORDS = ('x1', 'y1', 'x2', 'y2') class Conformer(object): """Base class for all text transformations.""" def to_scss(self, qss): """Transform some qss to valid scss.""" return NotImplemented def to_qss(self, css): """Transform some css to valid qss.""" return NotImplemented class NotConformer(Conformer): """Conform QSS "!" in selectors.""" def to_scss(self, qss): """Replace "!" in selectors with "_qnot_".""" return qss.replace(':!', ':_qnot_') def to_qss(self, css): """Replace "_qnot_" in selectors with "!".""" return css.replace(':_qnot_', ':!') class QLinearGradientConformer(Conformer): """Conform QSS qlineargradient function.""" qss_pattern = re.compile( r'qlineargradient\(' r'((?:(?:\s+)?(?:x1|y1|x2|y2):(?:\s+)?[0-9A-Za-z$_\.-]+,?)+)' # coords r'((?:(?:\s+)?stop:.*,?)+(?:\s+)?)?' # stops r'\)', re.MULTILINE, ) def _conform_coords_to_scss(self, group): """ Take a qss str with xy coords and returns the values. 'x1: 0, y1: 0, x2: 0, y2: 0' => '0, 0, 0, 0' 'y1: 1' => '0, 1, 0, 0' """ values = ['0', '0', '0', '0'] for key_values in [part.split(':', 1) for part in group.split(',')]: try: key, value = key_values key = key.strip() if key in _DEFAULT_COORDS: pos = _DEFAULT_COORDS.index(key) if pos >= 0 and pos <= 3: values[pos] = value.strip() except ValueError: pass return ', '.join(values) def _conform_stops_to_scss(self, group): """ Take a qss str with stops and returns the values. 'stop: 0 red, stop: 1 blue' => '0 red, 1 blue' """ new_group = [] split = [""] bracket_level = 0 for char in group: if not bracket_level and char == ",": split.append("") continue elif char == "(": bracket_level += 1 elif char == ")": bracket_level -= 1 split[-1] += char for part in split: if part: _, value = part.split(':', 1) new_group.append(value.strip()) return ', '.join(new_group) def to_scss(self, qss): """ Conform qss qlineargradient to scss qlineargradient form. Normalize all whitespace including the removal of newline chars. qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0, stop: 0 red, stop: 1 blue) => qlineargradient(0, 0, 0, 0, (0 red, 1 blue)) """ conformed = qss for coords, stops in self.qss_pattern.findall(qss): new_coords = self._conform_coords_to_scss(coords) conformed = conformed.replace(coords, new_coords, 1) if not stops: continue new_stops = ', ({})'.format(self._conform_stops_to_scss(stops)) conformed = conformed.replace(stops, new_stops, 1) return conformed def to_qss(self, css): """Transform to qss from css.""" return css conformers = [c() for c in Conformer.__subclasses__() if c is not Conformer] def scss_conform(input_str): """ Conform qss to valid scss. Runs the to_scss method of all Conformer subclasses on the input_str. Conformers are run in order of definition. :param input_str: QSS string :returns: Valid SCSS string """ conformed = input_str for conformer in conformers: conformed = conformer.to_scss(conformed) return conformed def qt_conform(input_str): """ Conform css to valid qss. Runs the to_qss method of all Conformer subclasses on the input_str. Conformers are run in reverse order. :param input_str: CSS string :returns: Valid QSS string """ conformed = input_str for conformer in conformers[::-1]: conformed = conformer.to_qss(conformed) return conformed qtsass-0.3.0+git20200324.06f1519/qtsass/cli.py0000644000175000017500000000641613636454105016105 0ustar jdgjdg#!/usr/bin/env python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """qtsass command line interface.""" # yapf: disable from __future__ import absolute_import, print_function # Standard library imports import argparse import logging import os import sys import time # Local imports from qtsass.api import ( compile, compile_dirname, compile_filename, enable_logging, watch, ) # yapf: enable _log = logging.getLogger(__name__) def create_parser(): """Create qtsass's cli parser.""" parser = argparse.ArgumentParser( prog='QtSASS', description='Compile a Qt compliant CSS file from a SASS stylesheet.', ) parser.add_argument( 'input', type=str, help='The SASS stylesheet file.', ) parser.add_argument( '-o', '--output', type=str, help='The path of the generated Qt compliant CSS file.', ) parser.add_argument( '-w', '--watch', action='store_true', help='If set, recompile when the source file changes.', ) parser.add_argument( '-d', '--debug', action='store_true', help='Set the logging level to DEBUG.', ) return parser def main(): """CLI entry point.""" args = create_parser().parse_args() # Setup CLI logging debug = os.environ.get('QTSASS_DEBUG', args.debug) if debug in ('1', 'true', 'True', 'TRUE', 'on', 'On', 'ON', True): level = logging.DEBUG else: level = logging.INFO enable_logging(level) # Add a StreamHandler handler = logging.StreamHandler() if level == logging.DEBUG: fmt = '%(levelname)-8s: %(name)s> %(message)s' handler.setFormatter(logging.Formatter(fmt)) logging.root.addHandler(handler) logging.root.setLevel(level) file_mode = os.path.isfile(args.input) dir_mode = os.path.isdir(args.input) if file_mode and not args.output: with open(args.input, 'r') as f: string = f.read() css = compile( string, include_paths=os.path.abspath(os.path.dirname(args.input)), ) print(css) sys.exit(0) elif file_mode: _log.debug('compile_filename({}, {})'.format(args.input, args.output)) compile_filename(args.input, args.output) elif dir_mode and not args.output: print('Error: missing required option: -o/--output') sys.exit(1) elif dir_mode: _log.debug('compile_dirname({}, {})'.format(args.input, args.output)) compile_dirname(args.input, args.output) else: print('Error: input must be a file or a directory') sys.exit(1) if args.watch: _log.info('qtsass is watching {}...'.format(args.input)) watcher = watch(args.input, args.output) watcher.start() try: while True: time.sleep(0.5) except KeyboardInterrupt: watcher.stop() watcher.join() sys.exit(0) qtsass-0.3.0+git20200324.06f1519/requirements/0000755000175000017500000000000013636454105016162 5ustar jdgjdgqtsass-0.3.0+git20200324.06f1519/requirements/release.txt0000644000175000017500000000002313636454105020336 0ustar jdgjdgloghub twine wheel qtsass-0.3.0+git20200324.06f1519/requirements/install.txt0000644000175000017500000000001013636454105020360 0ustar jdgjdglibsass qtsass-0.3.0+git20200324.06f1519/requirements/dev.txt0000644000175000017500000000016713636454105017505 0ustar jdgjdgcodecov isort==4.3.15 pycodestyle==2.5.0 pydocstyle==3.0.0 PySide2; python_version=="3.6" pytest pytest-cov yapf==0.26 qtsass-0.3.0+git20200324.06f1519/RELEASE.md0000644000175000017500000000455413636454105015051 0ustar jdgjdg# Release process ## Using rever You need to have `conda` install since the process relies on conda environments. Make sure your current environment has [rever](https://regro.github.io/rever-docs/) installed. ```bash conda install rever -c conda-forge ``` Run checks before to make sure things are in order. ```bash rever check ``` Delete the `rever/` folder to start a clean release. ```bash rm -rf rever/ ``` Run rever with the type version (major|minor|patch|MAJOR.MINOR.PATCH) to update. ### Major release If the current version is `3.0.0.dev0`, running: ```bash rever major ``` Will produce version `4.0.0` and update the dev version to `4.0.0.dev0` ### Minor release If the current version is `3.0.0.dev0`, running: ```bash rever minor ``` Will produce version `3.1.0` and update the dev version to `3.1.0.dev0` ### Patch release If the current version is `3.0.0.dev0`, running: ```bash rever patch ``` Will produce version `3.0.1` and update the dev version to `3.0.1.dev0` ### MAJOR.MINOR.PATCH release If the current version is `3.0.0.dev0`, running: ```bash rever 5.0.1 ``` Will produce version `5.0.1` and update the dev version to `5.0.1.dev0` ### Important - In case some of the steps appear as completed, delete the `rever` folder. ```bash rm -rf rever/ ``` - Some of the intermediate steps may ask for feedback, like checking the changelog. ## Manual process - Ensure you have the latest version from upstream and update your fork ```bash git pull upstream master git push origin master ``` - Clean the repo ```bash git clean -xfdi ``` - Update CHANGELOG.md using loghub ```bash loghub spyder-ide/qtsass -zr ``` - Update version in `__init__.py` (set release version, remove 'dev0') - Commit changes ```bash git add . git commit -m "Release X.X.X" ``` - Create distributions ```bash python setup.py sdist bdist_wheel ``` - Upload distributions ```bash twine upload dist/* -u -p ``` - Add release tag ```bash git tag -a vX.X.X -m "Release X.X.X" ``` - Update `__init__.py` (add 'dev0' and increment minor) - Commint changes ```bash git add . git commit -m "Back to work" ``` - Push changes ```bash git push upstream master git push origin master git push --tags ``` ## To release a new version of **qtsass** on conda-forge - Update recipe on the [qtsass feedstock](https://github.com/conda-forge/qtsass-feedstock) qtsass-0.3.0+git20200324.06f1519/run_checks_and_format.py0000644000175000017500000000344613636454105020336 0ustar jdgjdg#!/usr/bin/env python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2015 Yann Lanthony # Copyright (c) 2017-2018 Spyder Project Contributors # # Licensed under the terms of the MIT License # (See LICENSE.txt for details) # ----------------------------------------------------------------------------- """Run checks and format code.""" # yapf: disable # Standard library imports from subprocess import PIPE, Popen import os import sys # yapf: enable # Constants PY3 = sys.version[0] == '3' COMMANDS = [ ['pydocstyle', 'qtsass'], ['pycodestyle', 'qtsass'], ['yapf', 'qtsass', '--in-place', '--recursive'], ['isort', '-y'], ] def run_process(cmd_list): """Run popen process.""" try: p = Popen(cmd_list, stdout=PIPE, stderr=PIPE) except OSError: raise OSError('Could not call command list: "%s"' % cmd_list) out, err = p.communicate() if PY3: out = out.decode() err = err.decode() return out, err def repo_changes(): """Check if repo files changed.""" out, _err = run_process(['git', 'status', '--short']) out_lines = [l for l in out.split('\n') if l.strip()] return out_lines def run(): """Run linters and formatters.""" for cmd_list in COMMANDS: cmd_str = ' '.join(cmd_list) print('\nRunning: ' + cmd_str) out, err = run_process(cmd_list) if out: print(out) if err: print(err) out_lines = repo_changes() if out_lines: print('\nPlease run the linter and formatter script!') print('\n'.join(out_lines)) code = 1 else: print('\nAll checks passed!') code = 0 print('\n') sys.exit(code) if __name__ == '__main__': run() qtsass-0.3.0+git20200324.06f1519/setup.cfg0000644000175000017500000000323313636454105015261 0ustar jdgjdg[metadata] # This includes the license file(s) in the wheel. # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file license_files = LICENSE.txt [bdist_wheel] # This flag says to generate wheels that support both Python 2 and Python # 3. If your code will not run unchanged on both Python 2 and 3, you will # need to generate separate wheels for each Python version that you # support. Removing this line (or setting universal to 0) will prevent # bdist_wheel from trying to make a universal wheel. For more see: # https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels universal=1 # pydocstyle # http://www.pydocstyle.org/en/latest/usage.html # [pydocstyle] # pycodestyle # http://pycodestyle.pycqa.org/en/latest/intro.html#configuration [pycodestyle] max-line-length = 79 statistics = True # yapf # https://github.com/google/yapf#formatting-style [yapf:style] based_on_style = pep8 column_limit = 79 spaces_before_comment = 2 allow_multiline_lambdas = true dedent_closing_brackets = true blank_line_before_nested_class_or_def = false # isort # https://github.com/timothycrosley/isort/wiki/isort-Settings [isort] from_first = true import_heading_stdlib = Standard library imports import_heading_firstparty = Local imports import_heading_localfolder = Local imports import_heading_thirdparty = Third party imports indent = ' ' known_first_party = qtsass known_third_party = libsass,pytest,setuptools,watchdog default_section = THIRDPARTY line_length = 79 sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER lines_after_imports = 2 skip = venv multi_line_output = 3 include_trailing_comma = true qtsass-0.3.0+git20200324.06f1519/AUTHORS.md0000644000175000017500000000077113636454105015113 0ustar jdgjdg# Authors The qtsass project has some great contributors! They are: - [Andrey Galkin](https://github.com/tmpfork) - [C.A.M. Gerlach](https://github.com/CAM-Gerlach) - [Carlos Cordoba](https://github.com/ccordoba12) - [Dan Bradham](https://github.com/danbradham) - [Eric Werner](https://github.com/ewerybody) - [Gonzalo Peña-Castellanos](https://github.com/goanpeca) - [Matthew Joyce](https://github.com/matsjoyce) - [Yann Lanthony](https://github.com/yann-lty) These have been sorted alphabetically.