diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2a818e0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Tests +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install and set up Poetry + run: | + curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python + source $HOME/.poetry/env + poetry config virtualenvs.in-project true + - name: Install dependencies + run: | + source $HOME/.poetry/env + poetry install + - name: Test + run: | + source $HOME/.poetry/env + poetry run tox -q --parallel=auto + - name: Post code coverage results + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + pip install 'coveralls<2' + coveralls diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d82fa7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,143 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# static files generated from Django application using `collectstatic` +media +static diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..00c269b --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,10 @@ +Changelog +========= + +All notable changes to this project will be documented here. This changelog +follows the conventions of `keepachangelog.com `_. + +Contributors +~~~~~~~~~~~~ + +- Tucker Siemens (`@reillysiemens `_) diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 0000000..48f5514 --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,140 @@ +Contributor Covenant Code of Conduct +==================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement via email. All +complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +~~~~~~~~~~~~~ + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +~~~~~~~~~~ + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +~~~~~~~~~~~~~~~~ + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +~~~~~~~~~~~~~~~~ + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.0, +available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by +`Mozilla's code of conduct enforcement ladder`_. + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + +.. _Contributor Covenant: https://www.contributor-covenant.org +.. _Mozilla's code of conduct enforcement ladder: https://github.com/mozilla/diversity diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..69c5441 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,151 @@ +Contributing Guidelines +======================= + +Please follow these guidelines for contributing to this project. + +Repository Management +--------------------- + +- Fork this project into your own repository. +- Follow the `version control`_ guidelines. +- No changes should reach the ``master`` branch except by way of a + `pull request`_. + +Submitting Issues +~~~~~~~~~~~~~~~~~ + +`Create an issue`_ to report bugs or request enhancements. Better yet, you're +encouraged to fix the problem yourself and submit a `pull request`_. + +Bug Reports ++++++++++++ + +When reporting a bug, please provide the steps to reproduce the problem and any +details that could be important such as whether this is the first time this has +happened or whether others are experiencing it. + +Pull Requests ++++++++++++++ + +Pull requests must remain focused on fixing or addressing one thing (see +`topic branch`_ model). Make sure your pull request contains a clear title and +description. Test coverage should not drop as a result. If you add code, you +add tests. + +Be sure to follow the guidelines on `writing code`_ if you want your work +considered for inclusion. + +Handling Pull Requests +~~~~~~~~~~~~~~~~~~~~~~ + +- Pull requests **must** include: + + - Title describing the change + - Description explaining the change in detail + - Tests + +- A maintainer will respond to Pull Requests with one of: + + - 'Ship It', 'LGTM', 🚢, or some other affirmation + - What must be changed + - Won't accept and why + +Nota Bene ++++++++++ + +- Submitting a `draft pull request`_ is a good way to get feedback from + maintainers if you are unsure of the changes you are making. +- A pull request that has been approved may not be merged immediately. +- You may be asked to rebase or squash your commits to keep an orderly version + control history. + +.. _version control: + +Using Version Control +~~~~~~~~~~~~~~~~~~~~~ + +- `Fork`_ the `central repository`_ and work from a clone of your own fork. +- Follow the `topic branch`_ model and submit pull requests from branches named + according to their purpose. +- Review the `GitHub Flow`_ documentation and, in general, try to stick to the + principles outlined there. + +.. _writing code: + +Writing Code +------------ + +Writing code is a creative process and there will always be exceptions to the +rules, but it's good to maintain certain standards. In general, please follow +these code conventions. + +Coding Style +~~~~~~~~~~~~ + +- Code in this project **must** be formatted with `black`_. +- Code in this project **must** be linted with `flake8`_. +- Try to follow :pep:`8` guidelines. +- Try to respect the style of existing code. + +Coding style checks are bundled into the static analysis automation in this +repository's `tox`_ configuration. To validate your coding style run + +.. code-block:: sh + + tox -e static + +Test Environment +~~~~~~~~~~~~~~~~ + +- Code **must** be tested. Write or update related unit tests so you don't have + to manually retest the same thing many times. +- Tests for this project are written using the `pytest`_ framework and executed + via `tox`_. While it isn't always achievable this project strives to maintain + 💯% test coverage. +- In addition to unit testing code in this project is statically type checked + using `mypy`_, formatted with `black`_, linted using `flake8`_, and security + checked with `bandit`_. + +Here are some example invocations for running unit tests/static analysis. + +.. code-block:: sh + + tox # Build and test the project + tox -e py39 # in a specific environment + tox -e py39 -- --pdb # with extra options, + # or + tox -e py39 --devenv venv # create a development environment + venv/bin/python -V # and call scripts/binaries in it. + +Documentation +~~~~~~~~~~~~~ + +- Public interfaces **must** be thoroughly documented. At a minimum this + includes inputs, return types, exceptions raised, and surprising behavior + like state changes. +- Documentation for this project is written in `reStructuredText`_ with the + `Google style`_ and generated with `Sphinx`_. + +To generate documentation run + +.. code-block:: sh + + tox -e docs + + +.. _Create an issue: https://help.github.com/articles/creating-an-issue +.. _pull request: https://help.github.com/articles/using-pull-requests/ +.. _draft pull request: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests +.. _topic branch: https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#Topic-Branches +.. _Fork: https://help.github.com/articles/fork-a-repo/ +.. _central repository: https://github.com/reillysiemens/ipython-style-gruvbox/ +.. _GitHub Flow: https://guides.github.com/introduction/flow/ +.. _pytest: https://docs.pytest.org/en/latest/ +.. _tox: https://tox.readthedocs.io/en/latest/ +.. _mypy: http://www.mypy-lang.org/ +.. _black: https://black.readthedocs.io/en/stable/ +.. _flake8: https://flake8.pycqa.org/en/latest/ +.. _bandit: https://bandit.readthedocs.io/en/latest/ +.. _reStructuredText: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html +.. _Google style: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html +.. _Sphinx: http://www.sphinx-doc.org/en/master/index.html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..da21233 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright © 2020, Reilly Tucker Siemens + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f19bf02 --- /dev/null +++ b/README.rst @@ -0,0 +1,19 @@ +IPython Style Gruvbox +===================== + +.. image:: https://img.shields.io/github/workflow/status/reillysiemens/ipython-style-gruvbox/Tests/master.svg?style=flat-square&label=tests + :target: https://github.com/reillysiemens/ipython-style-gruvbox/actions?query=workflow%3ATests + :alt: GitHub Actions test status + +.. image:: https://img.shields.io/coveralls/github/reillysiemens/ipython-style-gruvbox/master?style=flat-square + :target: https://coveralls.io/github/reillysiemens/ipython-style-gruvbox + :alt: Coveralls code coverage + +.. image:: https://img.shields.io/badge/code%20style-black-black?style=flat-square + :target: https://github.com/psf/black + :alt: Any color you like + +An opinionated terminal colorscheme for IPython using gruvbox colors. + +.. image:: docs/static/ipython-style-gruvbox.png + :alt: IPython Style Gruvbox diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..76888fe --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,3 @@ +.. _changelog: + +.. include:: ../CHANGELOG.rst diff --git a/docs/code-of-conduct.rst b/docs/code-of-conduct.rst new file mode 100644 index 0000000..bb240d1 --- /dev/null +++ b/docs/code-of-conduct.rst @@ -0,0 +1,3 @@ +.. _code-of-conduct: + +.. include:: ../CODE_OF_CONDUCT.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..ddd8ed7 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,72 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import datetime as dt +from pathlib import Path +import sys + +sys.path.insert(0, str(Path("..").resolve())) + +from gruvbox import __author__, __version__ # noqa: E402 + + +# -- Project information ----------------------------------------------------- + +changelog_mtime = Path("../CHANGELOG.rst").stat().st_mtime +copyright_year = dt.datetime.utcfromtimestamp(changelog_mtime) +homepage = "https://tuckersiemens.com" + +project = "ipython-style-gruvbox" +author = __author__ +release = __version__ +version = ".".join(release.split(".", 2)[:2]) +copyright = f'{copyright_year:%Y}, {author}' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.intersphinx", + "sphinx.ext.githubpages", + "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "alabaster" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..4628182 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,3 @@ +.. _contributing: + +.. include:: ../CONTRIBUTING.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..ce0bfe0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,33 @@ +.. ipython-style-gruvbox documentation master file + +IPython Style Gruvbox +===================== + +Release v\ |release|. (:ref:`Changelog `) + +.. image:: https://img.shields.io/github/workflow/status/reillysiemens/ipython-style-gruvbox/Tests/master.svg?style=flat-square&label=tests + :target: https://github.com/reillysiemens/ipython-style-gruvbox/actions?query=workflow%3ATests + :alt: GitHub Actions test status + +.. image:: https://img.shields.io/coveralls/github/reillysiemens/ipython-style-gruvbox/master?style=flat-square + :target: https://coveralls.io/github/reillysiemens/ipython-style-gruvbox + :alt: Coveralls code coverage + +.. image:: https://img.shields.io/badge/code%20style-black-black?style=flat-square + :target: https://github.com/psf/black + :alt: Any color you like + +An opinionated terminal colorscheme for IPython using gruvbox colors. + +.. image:: static/ipython-style-gruvbox.png + :alt: IPython Style Gruvbox + +Project Info +------------ + +.. toctree:: + :maxdepth: 1 + + Code of Conduct + changelog + contributing diff --git a/docs/static/ipython-style-gruvbox.png b/docs/static/ipython-style-gruvbox.png new file mode 100644 index 0000000..c7114e8 Binary files /dev/null and b/docs/static/ipython-style-gruvbox.png differ diff --git a/gruvbox.py b/gruvbox.py new file mode 100644 index 0000000..24c519a --- /dev/null +++ b/gruvbox.py @@ -0,0 +1,108 @@ +"""An opinionated terminal colorscheme for IPython using gruvbox colors. + +Inspired or informed by the following: + - Pavel Pertsev's gruvbox colorscheme: https://git.io/JvV4I + - Dave Yarwood's gruvbox Pygments style: https://git.io/JvV4k + - python-mode's syntax highlighting: https://git.io/JvV4t + - The Pygments Python lexer: https://git.io/Jviis +""" + +from dataclasses import dataclass +from typing import Any, Dict + +from pygments.style import Style # type: ignore +from pygments.token import ( # type: ignore + Comment, + Error, + Keyword, + Name, + Number, + Operator, + String, + Text, + Token, +) + +__author__ = "Reilly Tucker Siemens" +__email__ = "reilly@tuckersiemens.com" +__version__ = "1.0.0" + + +@dataclass(frozen=True) +class Color: + """Absolute colors as defined by gruvbox: https://git.io/JvV8i.""" + + dark0_hard: str = "#1d2021" + dark0: str = "#282828" + dark0_soft: str = "#32302f" + dark1: str = "#3c3836" + dark2: str = "#504945" + dark3: str = "#665c54" + dark4: str = "#7c6f64" + dark4_256: str = "#7c6f64" + + gray_245: str = "#928374" + gray_244: str = "#928374" + + light0_hard: str = "#f9f5d7" + light0: str = "#fbf1c7" + light0_soft: str = "#f2e5bc" + light1: str = "#ebdbb2" + light2: str = "#d5c4a1" + light3: str = "#bdae93" + light4: str = "#a89984" + light4_256: str = "#a89984" + + bright_red: str = "#fb4934" + bright_green: str = "#b8bb26" + bright_yellow: str = "#fabd2f" + bright_blue: str = "#83a598" + bright_purple: str = "#d3869b" + bright_aqua: str = "#8ec07c" + bright_orange: str = "#fe8019" + + neutral_red: str = "#cc241d" + neutral_green: str = "#98971a" + neutral_yellow: str = "#d79921" + neutral_blue: str = "#458588" + neutral_purple: str = "#b16286" + neutral_aqua: str = "#689d6a" + neutral_orange: str = "#d65d0e" + + faded_red: str = "#9d0006" + faded_green: str = "#79740e" + faded_yellow: str = "#b57614" + faded_blue: str = "#076678" + faded_purple: str = "#8f3f71" + faded_aqua: str = "#427b58" + faded_orange: str = "#af3a03" + + +class GruvboxStyle(Style): + """An opinionated terminal colorscheme for IPython using gruvbox colors.""" + + styles: Dict[Any, str] = { + Comment: Color.gray_245, + Error: Color.bright_red, + Keyword.Namespace: Color.bright_blue, + Keyword.Constant: Color.bright_orange, + Keyword.Type: Color.bright_yellow, + Keyword: Color.bright_red, + Name.Builtin.Pseudo: Color.bright_blue, + Name.Builtin: Color.bright_yellow, + Name.Class: Color.bright_yellow, + Name.Decorator: f"{Color.bright_green} bold", + Name.Exception: Color.bright_red, + Name.Function: Color.bright_aqua, + Name.Variable.Magic: Color.bright_orange, + Name: Color.light1, + Number: Color.bright_purple, + Operator.Word: Color.bright_red, + Operator: Color.light1, + String.Affix: Color.light1, + String.Escape: Color.bright_orange, + String.Interpol: Color.bright_orange, + String: Color.bright_green, + Text: Color.light1, + Token.Punctuation: Color.light1, + } diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..9640edc --- /dev/null +++ b/poetry.lock @@ -0,0 +1,211 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.3" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "platform_system == \"Windows\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "dev" +description = "Distribution utilities" +name = "distlib" +optional = false +python-versions = "*" +version = "0.3.0" + +[[package]] +category = "dev" +description = "A platform independent file lock." +name = "filelock" +optional = false +python-versions = "*" +version = "3.0.12" + +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.1" + +[[package]] +category = "main" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=3.5" +version = "2.6.1" + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.6" + +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.14.0" + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.0" + +[[package]] +category = "dev" +description = "tox is a generic virtualenv management and test command line tool" +name = "tox" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "3.14.5" + +[package.dependencies] +colorama = ">=0.4.1" +filelock = ">=3.0.0,<4" +packaging = ">=14" +pluggy = ">=0.12.0,<1" +py = ">=1.4.17,<2" +six = ">=1.14.0,<2" +toml = ">=0.9.4" +virtualenv = ">=16.0.0" + +[package.extras] +docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] +testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] + +[[package]] +category = "dev" +description = "Seamless integration of tox into GitHub Actions" +name = "tox-gh-actions" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.1.0" + +[package.dependencies] +tox = ">=3.12.2" + +[package.extras] +testing = ["flake8 (>=3,<4)", "pytest (>=4.0.0,<6)", "pytest-mock (>=2,<3)", "pytest-randomly (>=3)"] + +[[package]] +category = "dev" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.10" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.0,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] +testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.13,<1)"] + +[metadata] +content-hash = "094b037700ec57d935981cb3673f973bc24c229ec01be6fb485623670c160196" +python-versions = "^3.8" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +distlib = [ + {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +packaging = [ + {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, + {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, + {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, +] +pygments = [ + {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, + {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, +] +pyparsing = [ + {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, + {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, +] +six = [ + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, +] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +tox = [ + {file = "tox-3.14.5-py2.py3-none-any.whl", hash = "sha256:0cbe98369081fa16bd6f1163d3d0b2a62afa29d402ccfad2bd09fb2668be0956"}, + {file = "tox-3.14.5.tar.gz", hash = "sha256:676f1e3e7de245ad870f956436b84ea226210587d1f72c8dfb8cd5ac7b6f0e70"}, +] +tox-gh-actions = [ + {file = "tox-gh-actions-1.1.0.tar.gz", hash = "sha256:fbce2a435cab4619e5fe77ce08b941bf8f1569f67cc43ac26b0249b2c7b029f4"}, + {file = "tox_gh_actions-1.1.0-py2.py3-none-any.whl", hash = "sha256:1c491a6e73b13c72829b95e50d63060f920bd02e5e2ba3c5a79bfc312598f966"}, +] +virtualenv = [ + {file = "virtualenv-20.0.10-py2.py3-none-any.whl", hash = "sha256:10750cac3b5a9e6eed54d0f1f8222c550dc47f84609c95cbc504d44a58a048b8"}, + {file = "virtualenv-20.0.10.tar.gz", hash = "sha256:8512e83f1d90f8e481024d58512ac9c053bf16f54d9138520a0929396820dd78"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a54ffd7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,87 @@ +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" + +[tool.poetry] +name = "ipython-style-gruvbox" +version = "1.0.0" +description = "An opinionated terminal colorscheme for IPython using gruvbox colors." +authors = ["Reilly Tucker Siemens "] +license = "ISC" +packages = [ + { include = "gruvbox.py" }, +] + +[tool.poetry.dependencies] +python = "^3.8" +pygments = "^2.6.1" + +[tool.poetry.dev-dependencies] +tox = "^3.14.5" +tox-gh-actions = "^1.1.0" + +[tool.poetry.plugins."pygments.styles"] +gruvbox = "gruvbox:GruvboxStyle" + +[tool.tox] +legacy_tox_ini = """ +[tox] +minversion = 3.14.0 +envlist = + py38 + static + docs + +[testenv] +whitelist_externals = poetry +skip_install = true +deps = + pytest ~= 5.3.0 + pytest-cov ~= 2.8.0 + pytest-randomly ~= 3.2.0 +# XXX: (2020-03-15) This will install tox again, but it only happens once. +commands_pre = poetry install +commands = + pytest \ + --verbose \ + --cov=gruvbox \ + --cov-report=term-missing \ + --cov-fail-under=100 \ + {posargs} + +[testenv:static] +basepython = python3.8 +deps = + bandit ~= 1.6.0 + black ~= 19.10b0 + flake8 ~= 3.7.0 + flake8-bugbear ~= 20.1.0 + flake8-commas ~= 2.0.0 + flake8-docstrings ~= 1.5.0 + pep8-naming ~= 0.9.0 + mypy ~= 0.760 +commands_pre = +commands = + black --check gruvbox.py + flake8 --docstring-convention=google gruvbox.py + mypy \ + --disallow-untyped-calls \ + --disallow-untyped-defs \ + --disallow-incomplete-defs \ + --disallow-untyped-decorators \ + gruvbox.py + bandit --recursive -ll gruvbox.py + +[testenv:docs] +basepython = python3.8 +deps = + sphinx ~= 2.3.0 + sphinx-autodoc-typehints ~= 1.10.0 +commands_pre = +commands = + sphinx-build -b html docs {posargs:docs/_build/html} + +[gh-actions] +python = + 3.8: py38, static, docs +""" diff --git a/tests/test_gruvbox.py b/tests/test_gruvbox.py new file mode 100644 index 0000000..2c00fc3 --- /dev/null +++ b/tests/test_gruvbox.py @@ -0,0 +1,140 @@ +import pygments +from pygments.lexers import PythonLexer +from pygments.formatters import TerminalTrueColorFormatter +import pytest +from pytest import param + +from gruvbox import GruvboxStyle + + +def highlight(code: str) -> str: + """Highlight code in true color (24-bit) with the GruvboxStyle.""" + return pygments.highlight( + code=code, + lexer=PythonLexer(), + formatter=TerminalTrueColorFormatter(style=GruvboxStyle), + ) + + +@pytest.mark.parametrize( + "code,expected", + [ + param( + "# This is a comment.", + "\x1b[38;2;146;131;116m# This is a comment.\x1b[39m\n", + id="Comment", + ), + param( + "err?", + "\x1b[38;2;235;219;178merr\x1b[39m\x1b[38;2;251;73;52m?\x1b[39m\n", + id="Error", + ), + param( + "from gruvbox import GruvboxStyle", + "\x1b[38;2;131;165;152mfrom\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mgruvbox\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;131;165;152mimport\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mGruvboxStyle\x1b[39m\n", # noqa: E501 + id="Keyword.Namespace", + ), + param("None", "\x1b[38;2;254;128;25mNone\x1b[39m\n", id="Keyword.Constant",), + param("int", "\x1b[38;2;250;189;47mint\x1b[39m\n", id="Keyword.Type",), + param( + "try:\n pass\nexcept:\n raise", + "\x1b[38;2;251;73;52mtry\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\n\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mpass\x1b[39m\n\x1b[38;2;251;73;52mexcept\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\n\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mraise\x1b[39m\n", # noqa: E501 + id="Keyword", + ), + param( + "def method(self, other): ...", + "\x1b[38;2;251;73;52mdef\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;142;192;124mmethod\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;131;165;152mself\x1b[39m\x1b[38;2;235;219;178m,\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mother\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\n", # noqa: E501 + id="Name.Builtin.Pseudo", + ), + param( + "list(map(lambda n: n + 1, range(5)))", + "\x1b[38;2;250;189;47mlist\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;250;189;47mmap\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;251;73;52mlambda\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mn\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mn\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m+\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m1\x1b[39m\x1b[38;2;235;219;178m,\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;250;189;47mrange\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;211;134;155m5\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\n", # noqa: E501 + id="Name.Builtin", + ), + param( + "class IsDismissed: ...", + "\x1b[38;2;251;73;52mclass\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;250;189;47mIsDismissed\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\n", # noqa: E501 + id="Name.Class", + ), + param( + "@decorator\ndef function(): ...", + "\x1b[38;2;184;187;38;01m@decorator\x1b[39;00m\n\x1b[38;2;251;73;52mdef\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;142;192;124mfunction\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\n", # noqa: E501 + id="Name.Decorator", + ), + param( + 'raise KeyError("Wrong lock.")', + '\x1b[38;2;251;73;52mraise\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mKeyError\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;184;187;38mWrong lock.\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\n', # noqa: E501 + id="Name.Exception", + ), + param( + "def foo(bar: int, baz: Optional[str] = None) -> None: ...", + "\x1b[38;2;251;73;52mdef\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;142;192;124mfoo\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;235;219;178mbar\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;250;189;47mint\x1b[39m\x1b[38;2;235;219;178m,\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mbaz\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mOptional\x1b[39m\x1b[38;2;235;219;178m[\x1b[39m\x1b[38;2;250;189;47mstr\x1b[39m\x1b[38;2;235;219;178m]\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m=\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;254;128;25mNone\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m-\x1b[39m\x1b[38;2;235;219;178m>\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;254;128;25mNone\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\n", # noqa: E501 + id="Name.Function", + ), + param( + 'class IsDismissed:\n __slots__ = ("attr",)', + '\x1b[38;2;251;73;52mclass\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;250;189;47mIsDismissed\x1b[39m\x1b[38;2;235;219;178m:\x1b[39m\n\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;254;128;25m__slots__\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m=\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;184;187;38mattr\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;235;219;178m,\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\n', # noqa: E501 + id="Name.Variable.Magic", + ), + param( + "life = 42", + "\x1b[38;2;235;219;178mlife\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m=\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m42\x1b[39m\n", # noqa: E501 + id="Name", + ), + param( + "0b1101001", "\x1b[38;2;211;134;155m0b1101001\x1b[39m\n", id="Number.Bin", + ), + param("105.0", "\x1b[38;2;211;134;155m105.0\x1b[39m\n", id="Number.Float",), + param("0x69", "\x1b[38;2;211;134;155m0x69\x1b[39m\n", id="Number.Hex",), + param("105", "\x1b[38;2;211;134;155m105\x1b[39m\n", id="Number.Integer",), + param("0o151", "\x1b[38;2;211;134;155m0o151\x1b[39m\n", id="Number.Oct",), + param( + "x is not y and w is (y or z)", + "\x1b[38;2;235;219;178mx\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mis\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mnot\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178my\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mand\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mw\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mis\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;235;219;178my\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;251;73;52mor\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178mz\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\n", # noqa: E501 + id="Operator.Word", + ), + param( + "3 != 4, 5 == 5, 0b001 << 3", + "\x1b[38;2;211;134;155m3\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m!=\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m4\x1b[39m\x1b[38;2;235;219;178m,\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m5\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m==\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m5\x1b[39m\x1b[38;2;235;219;178m,\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m0b001\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m<<\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;211;134;155m3\x1b[39m\n", # noqa: E501 + id="Operator", + ), + param( + 'f"f-strings {rule}"', + '\x1b[38;2;235;219;178mf\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;184;187;38mf-strings \x1b[39m\x1b[38;2;254;128;25m{\x1b[39m\x1b[38;2;235;219;178mrule\x1b[39m\x1b[38;2;254;128;25m}\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\n', # noqa: E501 + id="String.Affix", + ), + param( + '"""This is a docstring."""', + '\x1b[38;2;184;187;38m"""This is a docstring."""\x1b[39m\n', + id="String.Doc", + ), + param( + '"\\n\\t"', + '\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;254;128;25m\\n\x1b[39m\x1b[38;2;254;128;25m\\t\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\n', # noqa: E501 + id="String.Escape", + ), + param( + '"%s" % "{xyzzy}".format(xyzzy="plugh")', + '\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;254;128;25m%s\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;235;219;178m%\x1b[39m\x1b[38;2;235;219;178m \x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;254;128;25m{xyzzy}\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178mformat\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;235;219;178mxyzzy\x1b[39m\x1b[38;2;235;219;178m=\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;184;187;38mplugh\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\n', # noqa: E501 + id="String.Interpol", + ), + param( + '"This is a string."', + '\x1b[38;2;184;187;38m"\x1b[39m\x1b[38;2;184;187;38mThis is a string.\x1b[39m\x1b[38;2;184;187;38m"\x1b[39m\n', # noqa: E501 + id="String", + ), + param( + " \n ", + "\x1b[38;2;235;219;178m \x1b[39m\n\x1b[38;2;235;219;178m \x1b[39m\n", + id="Text", + ), + param( + "[].reverse()", + "\x1b[38;2;235;219;178m[\x1b[39m\x1b[38;2;235;219;178m]\x1b[39m\x1b[38;2;235;219;178m.\x1b[39m\x1b[38;2;235;219;178mreverse\x1b[39m\x1b[38;2;235;219;178m(\x1b[39m\x1b[38;2;235;219;178m)\x1b[39m\n", # noqa: E501 + id="Token.Punctuation", + ), + ], +) +def test_highlighting(code: str, expected: str) -> None: + """The given code should highlight as expected.""" + assert highlight(code) == expected