From 2500c9b6c899203ad4c02cdc41abd2b25c94ce26 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 25 Sep 2024 16:14:21 -0500 Subject: [PATCH] Release 0.2.0: Modernization and bug fixes * Dropped support for Sopel<7.1 + Python<3.8 * `sopel.module` -> `sopel.plugin` * Removed shim around `sopel.formatting.plain()` function * Updated `__future__` imports * Removed UTF-8 coding comment * Reorganized to match current Sopel standard (mainly putting "the actual plugin" in a `plugin.py` file, not `__init__.py`) * Updated packaging to use `pyproject.toml` metadata (closes #3) * Added release automation using PyPI Trusted Publishing * Fixed `random_start` setting (broken by changes in #1) * Fixed errors in `sopel-plugins configure rainbow` * Wrong setting name (`rainbow` -> `order`) * Wrong default value type (`list[int]` -> `list[str]`) * Use `unicodedata2` if it's installed --- .github/workflows/trusted-publishing.yml | 29 +++++++++ COPYING | 2 +- NEWS | 31 ++++++++++ README.md | 30 ++++++---- pyproject.toml | 52 ++++++++++++++++ setup.cfg | 25 -------- setup.py | 25 -------- sopel_rainbow/__init__.py | 72 ----------------------- sopel_rainbow/plugin.py | 75 ++++++++++++++++++++++++ 9 files changed, 208 insertions(+), 133 deletions(-) create mode 100644 .github/workflows/trusted-publishing.yml create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 sopel_rainbow/plugin.py diff --git a/.github/workflows/trusted-publishing.yml b/.github/workflows/trusted-publishing.yml new file mode 100644 index 0000000..cbe644f --- /dev/null +++ b/.github/workflows/trusted-publishing.yml @@ -0,0 +1,29 @@ +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + upload: + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 diff --git a/COPYING b/COPYING index 7d1cd07..11140e8 100644 --- a/COPYING +++ b/COPYING @@ -5,7 +5,7 @@ distribute this package, provided that: * copyright notices are retained unchanged, * any distribution of this package, whether modified or not, - includes this license text. + includes this license text. 2. Permission is hereby also granted to distribute binary programs which depend on this package. If the binary program depends on a modified version of this package, you are encouraged to publicly diff --git a/NEWS b/NEWS index c11c876..a367817 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,37 @@ spawned from [a tongue-in-cheek upstream issue](https://github.com/sopel-irc/sopel/issues/1962). +### sopel-rainbow 0.2.0 + +Changed: + +* Dropped support for Sopel<7.1 + Python<3.8 + * `sopel.module` -> `sopel.plugin` + * Removed shim around `sopel.formatting.plain()` function + * Updated `__future__` imports + * Removed UTF-8 coding comment + +Added: + +* Use `unicodedata2` if it's installed + +Fixed: + +* `random_start` setting (broken by changes in [#1][]) +* Errors in `sopel-plugins configure rainbow` + * Wrong setting name (`rainbow` -> `order`) + * Wrong default value type (`list[int]` -> `list[str]`) + +Meta: + +* Reorganized to match current Sopel standard (mainly putting "the + actual plugin" in a `plugin.py` file, not `__init__.py`) +* Updated packaging to use `pyproject.toml` metadata +* Added release automation using PyPI Trusted Publishing + +[#1]: https://github.com/sopel-irc/sopel-rainbow/pull/1 + + ### sopel-rainbow 0.1.1 Fixed: diff --git a/README.md b/README.md index 26decd9..102f86c 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,21 @@ A Sopel plugin to make things RAINBOW COLORED. +## Installing -## Configuration +Releases are hosted on PyPI, so after installing Sopel, all you need is `pip`: + +```shell +$ pip install sopel-rainbow +``` + +## Configuring + +The easiest way to configure `sopel-rainbow` is via Sopel's configuration +wizard—simply run `sopel-plugins configure rainbow` and enter the values for +which it prompts you. + +### `order` setting By default, `sopel-rainbow` outputs colors in the "standard" rainbow `order`, ROYGBIV, subject to receiving clients' use of the customary meanings for IRC @@ -34,6 +47,8 @@ order = # Americans and French can fight over this one 2 ``` +### `random_start` setting + Starting the rainbow at the beginning of the `order` every time is also default behavior. If you want the rainbow to start at a random place every time instead, set the Boolean option `random_start` to `yes` or `on`: @@ -43,15 +58,10 @@ time instead, set the Boolean option `random_start` to `yes` or `on`: random_start = on ``` - ## Dependencies -Only Sopel itself, version 7.0 or higher. - -If installed on a bot using Sopel 7.1+, `sopel-rainbow` will strip control -codes from the input text before applying the rainbow colors. (Sopel 7.0.x -does not offer this feature, so feeding formatted text into the `.rainbow` -command might yield unexpected results.) +* Sopel version 7.1 or higher +* Python 3.8 or higher -This version of `sopel-rainbow` will not work with Sopel 9.0+. A future -release will correct this, sometime before Sopel 9 becomes stable. +Sopel 7.x should still run on Python 2.7 or older Python 3 releases, but it's +not maintained any more; and neither is this plugin tested on anything older. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4b9e527 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["setuptools>=63.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +platforms = ["Linux x86, x86-64"] + +[tool.setuptools.packages.find] +include = ["sopel_rainbow", "sopel_rainbow.*"] +namespaces = false + +[tool.setuptools.dynamic] +readme = { file=["README.md", "NEWS"], content-type="text/markdown" } + +[project] +name = "sopel-rainbow" +version = "0.2.0" +description = "A Sopel plugin to make things RAINBOW COLORED." + +authors = [ + { name="dgw", email="dgw@technobabbl.es" }, +] + +license = { text="EFL-2.0" } +dynamic = ["readme"] + +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: Eiffel Forum License (EFL)", + "License :: OSI Approved :: Eiffel Forum License", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Communications :: Chat :: Internet Relay Chat", +] +keywords = [ + "sopel", + "plugin", + "bot", + "irc", +] + +requires-python = ">=3.8, <4" +dependencies = [ + "sopel>=7.1", +] + +[project.urls] +"Homepage" = "https://github.com/sopel-irc/sopel-rainbow" +"Bug Tracker" = "https://github.com/sopel-irc/sopel-rainbow/issues" + +[project.entry-points."sopel.plugins"] +"rainbow" = "sopel_rainbow.plugin" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8a58d5f..0000000 --- a/setup.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[metadata] -name = sopel-rainbow -version = 0.1.1 -description = A Sopel plugin to make things RAINBOW COLORED. -author = dgw -author_email = dgw@technobabbl.es -url = https://github.com/dgw/sopel-rainbow -license = Eiffel Forum License, version 2 -classifiers = - Intended Audience :: Developers - Intended Audience :: System Administrators - License :: Eiffel Forum License (EFL) - License :: OSI Approved :: Eiffel Forum License - Topic :: Communications :: Chat :: Internet Relay Chat - -[options] -packages = find: -zip_safe = false -include_package_data = true -install_requires = - sopel>=7.0,<9 - -[options.entry_points] -sopel.plugins = - rainbow = sopel_rainbow diff --git a/setup.py b/setup.py deleted file mode 100644 index 7b4acb9..0000000 --- a/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function -import os -import sys -from setuptools import setup, find_packages - - -if __name__ == '__main__': - print('Sopel does not correctly load plugins installed with setup.py ' - 'directly. Please use "pip install .", or add ' - '{}/sopel_rainbow to core.extra in your config.' - .format(os.path.dirname(os.path.abspath(__file__))), - file=sys.stderr) - -with open('README.md') as readme_file: - readme = readme_file.read() - -with open('NEWS') as history_file: - history = history_file.read() - - -setup( - long_description=readme + '\n\n' + history, - long_description_content_type='text/markdown', -) diff --git a/sopel_rainbow/__init__.py b/sopel_rainbow/__init__.py index c4ebb9e..e69de29 100644 --- a/sopel_rainbow/__init__.py +++ b/sopel_rainbow/__init__.py @@ -1,72 +0,0 @@ -# coding=utf8 -"""sopel-rainbow - -A Sopel plugin to make things RAINBOW COLORED. -""" -from __future__ import unicode_literals, absolute_import, division, print_function - -import itertools -import random -import unicodedata - -from sopel import formatting, module -from sopel.config import types - - -# Remove when dropping support for Sopel < 7.1 -if hasattr(formatting, 'plain'): - clean = formatting.plain -else: - clean = lambda t: t - - -class RainbowSection(types.StaticSection): - order = types.ListAttribute('order', default=[4, 7, 8, 3, 12, 2, 6]) - """The order of color codes to use. - - Defaults to a standard ROYGBIV rainbow (assuming readers' clients use - typical IRC color code mappings). - """ - random_start = types.ValidatedAttribute('random_start', bool, default=False) - """Whether to randomize the start color.""" - - -def configure(config): - config.define_section('rainbow', RainbowSection) - config.rainbow.configure_setting( - 'rainbow', - 'Specify the order of IRC color codes to use in the "rainbow":' - ) - config.rainbow.configure_setting( - 'random_start', - 'Randomize start position in the rainbow?' - ) - - -def setup(bot): - bot.config.define_section('rainbow', RainbowSection) - - -@module.commands('rainbow') -def rainbow_cmd(bot, trigger): - """Make text into a rainbow.""" - text = clean(trigger.group(2) or '').strip() - - if not text: - bot.reply("I can't make a rainbow out of nothing!") - return module.NOLIMIT - - colors = bot.config.rainbow.order - color_cycle = itertools.cycle(colors) - - if bot.config.rainbow.random_start: - for _ in range(len(colors)): - next(color_cycle) - - bot.say( - ''.join( - char if unicodedata.category(char) == 'Zs' - else formatting.color(char, next(color_cycle)) - for char in text - ) - ) diff --git a/sopel_rainbow/plugin.py b/sopel_rainbow/plugin.py new file mode 100644 index 0000000..bba9424 --- /dev/null +++ b/sopel_rainbow/plugin.py @@ -0,0 +1,75 @@ +"""sopel-rainbow + +A Sopel plugin to make things RAINBOW COLORED. +""" +from __future__ import annotations + +import itertools +import random + +from sopel import formatting, plugin +from sopel.config import types + +# TODO: Consider making ud2 an install extra +# or just require it outright; in absolute terms, it isn't a heavy library +# (though relative to just this plugin, it's on the order of 100x the size) +try: + import unicodedata2 as unicodedata +except ImportError: + import unicodedata + + +class RainbowSection(types.StaticSection): + order = types.ListAttribute( + 'order', default=[str(v) for v in (4, 7, 8, 3, 12, 2, 6)]) + """The order of color codes to use. + + Defaults to a standard ROYGBIV rainbow (assuming readers' clients use + typical IRC color code mappings). + """ + random_start = types.BooleanAttribute('random_start', default=False) + """Whether to randomize the start color.""" + + +def configure(config): + config.define_section('rainbow', RainbowSection) + config.rainbow.configure_setting( + 'order', + 'Specify the order of IRC color codes to use in the "rainbow":' + ) + config.rainbow.configure_setting( + 'random_start', + 'Randomize start position in the rainbow?' + ) + + +def setup(bot): + bot.config.define_section('rainbow', RainbowSection) + + +@plugin.commands('rainbow') +def rainbow_cmd(bot, trigger): + """Make text into a rainbow.""" + text = formatting.plain(trigger.group(2) or '').strip() + + if not text: + bot.reply("I can't make a rainbow out of nothing!") + return plugin.NOLIMIT + + colors = bot.config.rainbow.order + color_cycle = itertools.cycle(colors) + + if bot.config.rainbow.random_start: + color_cycle = itertools.islice( + # passing all of (iter, start, stop) is important; + # passing only (iter, stop) will return a non-infinite iterator + color_cycle, random.randrange(len(colors)), None, + ) + + bot.say( + ''.join( + char if unicodedata.category(char) == 'Zs' + else formatting.color(char, next(color_cycle)) + for char in text + ) + )