First commit

This commit is contained in:
2025-05-03 23:49:48 +02:00
commit d1548b6e8a
17 changed files with 1153 additions and 0 deletions

17
.copywrite.hcl Normal file
View File

@@ -0,0 +1,17 @@
schema_version = 1
project {
license = "MIT"
copyright_year = 2025
copyright_holder = "Paillat-dev"
header_ignore = [
".venv/**",
"logs/**",
".idea/**",
".git/**",
".vscode/**",
"__pycache__/**",
"*.pyc",
"src/dismoji/raw/**",
]
}

25
.github/workflows/CI.yaml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: CI
on:
push:
branches: [ "master", "dev" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: ["master", "dev"]
release:
types: [created]
jobs:
quality:
uses: ./.github/workflows/quality.yaml
permissions:
contents: read
publish:
needs: quality
if: github.event_name == 'release'
uses: ./.github/workflows/publish.yaml
permissions:
id-token: write
contents: read

31
.github/workflows/publish.yaml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Quality Checks
on:
workflow_call:
jobs:
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
environment: pypi
steps:
- uses: actions/checkout@v4
- name: "Install uv"
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install dependencies
run: uv sync
- name: Build
run: uv build
- name: Publish
run: uv publish

87
.github/workflows/quality.yaml vendored Normal file
View File

@@ -0,0 +1,87 @@
name: Quality Checks
on:
workflow_call:
jobs:
check-license-header:
name: License Header Check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Copywrite
uses: hashicorp/setup-copywrite@5e3e8a26d7b9f8a508848ad0a069dfd2f7aa5339
- name: Check Header Compliance
run: copywrite headers --plan --config .copywrite.hcl
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [3.9, 3.10, 3.11, 3.12, 3.13]
include:
- python-version: 3.9
name: "Python 3.9"
- python-version: 3.10
name: "Python 3.10"
- python-version: 3.11
name: "Python 3.11"
- python-version: 3.12
name: "Python 3.12"
- python-version: 3.13
name: "Python 3.13"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --no-managed-python --no-python-downloads
- name: Run tests
run: uv run pytest ./tests
quality:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
check: [format, lint, basedpyright]
include:
- check: format
name: "Format Check"
command: "uv run ruff format --check ."
- check: lint
name: "Lint Check"
command: "uv run ruff check ."
- check: basedpyright
name: "Type Check"
command: "uv run basedpyright ."
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v4
- name: "Install uv"
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install dependencies
run: uv sync
- name: ${{ matrix.name }}
run: ${{ matrix.command }}

175
.gitignore vendored Normal file
View File

@@ -0,0 +1,175 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
./build/ # so hatch includes discord-emojis' build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
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
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__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/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# PyPI configuration file
.pypirc
_version.py
node_modules/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "src/dismoji/raw"]
path = src/dismoji/raw
url = git@github.com:Paillat-dev/discord-emojis.git

34
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,34 @@
# Copyright (c) NiceBots
# SPDX-License-Identifier: MIT
ci:
autoupdate_commit_msg: ":construction_worker: pre-commit autoupdate"
autofix_commit_msg: ":art: auto fixes from pre-commit.com hooks"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: \.(po|pot|yml|yaml)$
- id: end-of-file-fixer
exclude: \.(po|pot|yml|yaml)$
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
args: [--prose-wrap=always, --print-width=88]
exclude: \.(po|pot|yml|yaml)$
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.9.10
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/bhundven/copywrite # waiting for https://github.com/hashicorp/copywrite/pull/120 to be merged
rev: 937f17f09c46992447dfa8977bb96eda512588c4
hooks:
- id: add-headers

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Paillat-dev
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.

343
README.md Normal file
View File

@@ -0,0 +1,343 @@
<div align="center">
<h1>Discord Progress Bar</h1>
<!-- badges -->
![PyPI - Version](https://img.shields.io/pypi/v/dismoji)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dismoji)
![PyPI - Types](https://img.shields.io/pypi/types/dismoji)
![PyPI - License](https://img.shields.io/pypi/l/dismoji)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Paillat-dev/dismoji/CI.yaml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/Paillat-dev/dismoji/main.svg)](https://results.pre-commit.ci/latest/github/Paillat-dev/dismoji/main)
<!-- end badges -->
A Python library for creating customizable progress bars with Discord emojis in your
Discord bot messages.
</div>
## Table of Contents
- [Overview](#overview)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [How It Works](#how-it-works)
- [Key Components](#key-components)
- [Features](#features)
- [Type Safety](#type-safety)
- [Usage Examples](#usage-examples)
- [Basic Progress Bar](#basic-progress-bar)
- [Different Progress Bar States](#different-progress-bar-states)
- [Custom Progress Bar Length](#custom-progress-bar-length)
- [Configuration](#configuration)
- [Bot Configuration](#bot-configuration)
- [Progress Bar Manager Initialization](#progress-bar-manager-initialization)
- [Custom Progress Bar Styles](#custom-progress-bar-styles)
- [Limitations](#limitations)
- [Getting Help](#getting-help)
- [Development](#development)
- [Local Testing](#local-testing)
- [Contributing](#contributing)
- [License](#license)
## Overview
Discord Progress Bar is a Python library that allows you to create visually appealing
progress bars in your Discord bot messages using custom emojis. It provides a simple API
to generate progress bars of different styles and lengths, making it easy to display
progress, loading states, or completion percentages in your Discord applications.
Built on:
- [py-cord](https://github.com/Pycord-Development/pycord) - A modern, easy-to-use,
feature-rich, and async-ready API wrapper for Discord
- Custom Discord emojis - Used to create visually consistent progress bar segments
## Installation
```bash
pip install discord-progress-bar --pre
```
<!-- prettier-ignore -->
> [!NOTE]
> The package is currently in pre-release.
## Quick Start
<!-- quick-start -->
<!-- prettier-ignore -->
> [!TIP]
> Create beautiful progress bars in your Discord bot with just a few lines of code!
```python
import discord
from discord_progress_bar import ProgressBarManager
# Create a Discord bot with emoji caching enabled
bot = discord.Bot(cache_app_emojis=True)
progress_bar_manager = None
progress_bar = None
@bot.event
async def on_ready():
"""Initialize the ProgressBarManager when the bot is ready."""
global progress_bar_manager, progress_bar
# Initialize the progress bar manager
progress_bar_manager = await ProgressBarManager(bot)
# Get a progress bar with the "green" style
progress_bar = await progress_bar_manager.progress_bar("green")
@bot.slash_command()
async def show_progress(ctx, percent: float = 0.5):
"""Display a progress bar with the specified percentage."""
await ctx.respond(f"Progress: {progress_bar.partial(percent)}")
# Run your bot
bot.run("YOUR_TOKEN")
```
<!-- prettier-ignore -->
> [!NOTE]
> For a complete example, check the [examples directory](/examples).
## Core Concepts
### How It Works
Discord Progress Bar works by using custom Discord emojis to create visually appealing
progress bars in your bot messages. Here's how it works:
1. **Progress Bar Structure**: Each progress bar consists of multiple emoji segments:
- Left edge (filled or empty)
- Middle sections (filled or empty)
- Right edge (filled or empty)
2. **Emoji Management**: The `ProgressBarManager` class handles:
- Loading existing emojis from your bot
- Creating new emojis from URLs or files if needed
- Providing the appropriate emojis to the `ProgressBar` class
3. **Progress Bar Rendering**: The `ProgressBar` class handles:
- Rendering full progress bars (100%)
- Rendering empty progress bars (0%)
- Rendering partial progress bars (any percentage)
4. **Default Styles**: The library comes with a default "green" style, but you can
create custom styles by providing your own emoji images.
### Key Components
- **ProgressBarManager**: Initializes with your Discord bot and manages emoji resources
- **ProgressBar**: Renders progress bars with different percentages
- **ProgressBarPart**: Enum defining the different parts of a progress bar (LEFT_EMPTY,
LEFT_FILLED, etc.)
- **Default Bars**: Pre-configured progress bar styles that can be used out of the box
## Features
- **Easy Integration**: Seamlessly integrates with Discord bots built using py-cord
- **Customizable Progress Bars**: Create progress bars with different styles and lengths
- **Default Styles**: Comes with a pre-configured "green" style ready to use
- **Custom Styles**: Create your own progress bar styles using custom emoji images
- **Flexible Rendering**: Render progress bars at any percentage (0-100%)
- **Async Support**: Fully supports asynchronous operations for Discord bots
- **Emoji Management**: Automatically handles emoji creation and management
### Type Safety
Discord Progress Bar is fully type-annotated and type-safe. It uses `basedpyright` for
type checking.
<!-- prettier-ignore -->
> [!NOTE]
> While Discord Progress Bar itself is fully typed, the underlying py-cord library has limited type annotations, which may affect type checking in some areas.
## Usage Examples
### Basic Progress Bar
```python
@discord.slash_command()
async def show_progress(self, ctx: discord.ApplicationContext, percent: float = 0.5) -> None:
"""Display a progress bar with the specified percentage."""
# Get a progress bar with the default "green" style
progress_bar = await self.progress_bar_manager.progress_bar("green", length=10)
# Render the progress bar at the specified percentage
await ctx.respond(f"Progress: {progress_bar.partial(percent)}")
```
### Different Progress Bar States
```python
@discord.slash_command()
async def show_progress_states(self, ctx: discord.ApplicationContext) -> None:
"""Display different progress bar states."""
progress_bar = await self.progress_bar_manager.progress_bar("green", length=10)
# Empty progress bar (0%)
empty = progress_bar.empty()
# Partial progress bar (50%)
half = progress_bar.partial(0.5)
# Full progress bar (100%)
full = progress_bar.full()
await ctx.respond(f"Empty: {empty}\nHalf: {half}\nFull: {full}")
```
### Custom Progress Bar Length
```python
@discord.slash_command()
async def show_different_lengths(self, ctx: discord.ApplicationContext) -> None:
"""Display progress bars with different lengths."""
# Short progress bar (5 segments)
short_bar = await self.progress_bar_manager.progress_bar("green", length=5)
# Medium progress bar (10 segments)
medium_bar = await self.progress_bar_manager.progress_bar("green", length=10)
# Long progress bar (20 segments)
long_bar = await self.progress_bar_manager.progress_bar("green", length=20)
await ctx.respond(
f"Short (5): {short_bar.partial(0.7)}\n"
f"Medium (10): {medium_bar.partial(0.7)}\n"
f"Long (20): {long_bar.partial(0.7)}"
)
```
For more examples, check the [examples directory](/examples) in the repository.
## Configuration
### Bot Configuration
When creating your Discord bot, make sure to enable emoji caching:
```python
bot = discord.Bot(cache_app_emojis=True)
```
This is required for the `ProgressBarManager` to properly load and manage emojis.
### Progress Bar Manager Initialization
Initialize the `ProgressBarManager` after your bot is ready:
```python
@discord.Cog.listener()
async def on_ready(self) -> None:
self.progress_bar_manager = await ProgressBarManager(self.bot)
```
### Custom Progress Bar Styles
You can create custom progress bar styles by providing your own emoji images:
#### From URLs
```python
from discord_progress_bar import ProgressBarPart
# Define URLs for each part of the progress bar
custom_style = {
ProgressBarPart.LEFT_EMPTY: "https://example.com/left_empty.png",
ProgressBarPart.LEFT_FILLED: "https://example.com/left_filled.png",
ProgressBarPart.MIDDLE_EMPTY: "https://example.com/middle_empty.png",
ProgressBarPart.MIDDLE_FILLED: "https://example.com/middle_filled.png",
ProgressBarPart.RIGHT_EMPTY: "https://example.com/right_empty.png",
ProgressBarPart.RIGHT_FILLED: "https://example.com/right_filled.png",
}
# Create emojis from URLs
await self.progress_bar_manager.create_emojis_from_urls("custom_style", custom_style)
```
#### From Files
```python
import pathlib
from discord_progress_bar import ProgressBarPart
# Define file paths for each part of the progress bar
custom_style = {
ProgressBarPart.LEFT_EMPTY: pathlib.Path("path/to/left_empty.png"),
ProgressBarPart.LEFT_FILLED: pathlib.Path("path/to/left_filled.png"),
ProgressBarPart.MIDDLE_EMPTY: pathlib.Path("path/to/middle_empty.png"),
ProgressBarPart.MIDDLE_FILLED: pathlib.Path("path/to/middle_filled.png"),
ProgressBarPart.RIGHT_EMPTY: pathlib.Path("path/to/right_empty.png"),
ProgressBarPart.RIGHT_FILLED: pathlib.Path("path/to/right_filled.png"),
}
# Create emojis from files
await self.progress_bar_manager.create_emojis_from_files("custom_style", custom_style)
```
## Limitations
<!-- prettier-ignore -->
> [!WARNING]
> Please be aware of the following limitations:
>
> - **Python Version**: Supports Python 3.12 only
> - **Discord Bot Framework**: Currently only supports py-cord, not discord.py or other Discord API wrappers
> - **Emoji Limits**: Subject to Discord's app emoji limits (2'000 emojis per app - should be plenty for most use cases)
> - **Pre-release Status**: This package is currently in alpha stage and may have unexpected behaviors or breaking changes in future versions
> - **Custom Styles**: Creating custom styles requires providing all six emoji parts (LEFT_EMPTY, LEFT_FILLED, MIDDLE_EMPTY, MIDDLE_FILLED, RIGHT_EMPTY, RIGHT_FILLED)
## Getting Help
If you encounter issues or have questions about discord-progress-bar:
- **GitHub Issues**:
[Submit a bug report or feature request](https://github.com/Paillat-dev/discord-progress-bar/issues)
- **Discord Support**:
- For py-cord related questions: Join the
[Pycord Official Server](https://discord.gg/pycord)
- For discord-progress-bar specific help: Join the
[Pycord Official Server](https://discord.gg/pycord) and mention `@paillat`
<!-- prettier-ignore -->
> [!TIP]
> Before asking for help, check if your question is already answered in the [examples directory](/examples) or existing GitHub issues.
## Development
### Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Run linter, formatter and type checker: `ruff check .`,`ruff format .`,
`basedpyright .`
5. Submit a pull request
**Development Tools**:
- **uv**: For dependency management
- **Ruff**: For linting and formatting
- **HashiCorp Copywrite**: For managing license headers
- **basedpyright**: For type checking
<!-- prettier-ignore -->
> [!CAUTION]
> This is an early-stage project and may have unexpected behaviors or bugs. Please report any issues you encounter.
## License
MIT License - Copyright (c) 2025 Paillat-dev
---
Made with ❤ by Paillat-dev

158
pyproject.toml Normal file
View File

@@ -0,0 +1,158 @@
[build-system]
requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[project]
name = "dismoji"
dynamic = ["version", "urls", "readme"]
description = "***"
authors = [
{ name = "Paillat-dev", email = "me@paillat.dev" }
]
license = "MIT"
requires-python = "==3.12.*"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.9",
"Typing :: Typed",
"Operating System :: OS Independent",
]
keywords = ["discord", "bot", "emojis", "emoji"]
dependencies = [
"json5>=0.12.0",
]
[dependency-groups]
dev = [
"basedpyright>=1.28.1",
"pytest>=8.3.5",
"python-dotenv>=1.0.1",
"ruff>=0.9.9",
]
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "src/dismoji/_version.py"
[tool.hatch.metadata.hooks.vcs.urls]
Homepage = "https://github.com/Paillat-dev/dismoji"
source_archive = "https://github.com/Paillat-dev/dismoji/archive/{commit_hash}.zip"
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "<!-- badges -->\n"
end-before = "\n<!-- end badges -->"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
text = "\n\n---\n"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "## Overview\n"
end-before = "\n## Installation"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "<!-- quick-start -->"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
replacement = '[\1](https://github.com/Paillat-dev/dismoji/tree/main\g<2>)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]'
replacement = '**\1**:'
[tool.hatchling]
name = "dismoji"
[tool.hatch.build]
include = [
"src/dismoji/",
"src/dismoji/raw/build/emojis.json",
]
exclude = [
".copywrite.hcl",
".github",
".python-version",
"uv.lock",
"src/dismoji/raw/*.*",
"src/dismoji/raw/.*/",
"src/dismoji/raw/src/"
]
[tool.hatch.build.targets.wheel]
packages = ["src/dismoji"]
[tool.pyright]
pythonVersion = "3.9"
typeCheckingMode = "all"
reportUnusedCallResult = false
reportAny = false
executionEnvironments = [
{ root = "src/dismoji/_version.py", reportDeprecated = false },
{ root = "examples", reportExplicitAny = false, reportUnknownMemberType = false, reportUnusedParameter = false, reportImplicitOverride = false }
]
[tool.ruff]
target-version = "py39"
line-length = 120
indent-width = 4
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = false
docstring-code-line-length = "dynamic"
exclude = [
"src/dismoji/_version.py"
]
[tool.ruff.lint]
select = ["ALL"]
per-file-ignores = { "examples/**/*" = ["INP001", "ARG002", "T201"], "tests/**/*" = ["S101"], "src/dismoji/_version.py" = ["I001", "Q000", "UP005", "UP006", "UP035"] }
extend-ignore = [
"N999",
"D104",
"D100",
"D103",
"D102",
"D101",
"D107",
"D105",
"D106",
"ANN401",
"TRY003",
"EM101",
"EM102",
"G004",
"PTH",
"D211",
"D213",
"COM812",
"ISC001",
"D203",
"FBT001",
"FBT002",
"PLR2004",
"PLR0913",
"C901",
"ISC003" # conflicts with basedpyright reportImplicitStringConcatenation
]
[tool.uv.sources]
py-cord = { git = "https://github.com/Pycord-Development/pycord", rev = "c0c0b7c58f7b489983a159f5e0eea2c0dab0b0c8" }

25
renovate.json Normal file
View File

@@ -0,0 +1,25 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"baseBranches": ["master"],
"labels": ["deps"],
"ignorePaths": ["requirements.txt"],
"commitMessagePrefix": "⬆️",
"commitMessageAction": "Upgrade",
"packageRules": [
{
"updateTypes": ["pin"],
"commitMessagePrefix": "📌",
"commitMessageAction": "Pin"
},
{
"updateTypes": ["rollback"],
"commitMessagePrefix": "⬇️",
"commitMessageAction": "Downgrade"
},
{
"matchDatasources": ["pypi"],
"addLabels": ["pypi"]
}
]
}

37
src/dismoji/__init__.py Normal file
View File

@@ -0,0 +1,37 @@
# Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT
from __future__ import annotations
import json
import re
from pathlib import Path
EMOJIS_PATH = Path(__file__).parent / "raw" / "build" / "emojis.json"
with EMOJIS_PATH.open("r", encoding="utf-8") as f:
EMOJIS = json.load(f)
EMOJI_MAPPING: dict[str, str] = {k: EMOJIS["emojis"][v]["surrogates"] for k, v in EMOJIS["nameToEmoji"].items()}
EMOJI_PATTERN = re.compile(r"(?<!\w):([a-zA-Z0-9_-]+):(?!\w)")
def emojize(s: str) -> str:
"""Convert a string with emoji names to a string with emoji characters.
Args:
s (str): The input string containing emoji names.
Returns:
str: The input string with emoji names replaced by emoji characters.
"""
def replace(match: re.Match[str]) -> str:
emoji_name = match.group(1)
if emoji_name in EMOJI_MAPPING:
return EMOJI_MAPPING[emoji_name]
return match.group(0)
return EMOJI_PATTERN.sub(replace, s)

0
src/dismoji/py.typed Normal file
View File

1
src/dismoji/raw Submodule

Submodule src/dismoji/raw added at f4c3abf844

2
tests/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
# Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT

43
tests/emoji_test.py Normal file
View File

@@ -0,0 +1,43 @@
# Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT
from dismoji import emojize
def test_basic() -> None:
"""Test basic functionality of emojize function."""
assert emojize("Hello :smile:") == "Hello 😄"
def test_no_match() -> None:
"""Test emojize function with no matches."""
assert emojize("Hello world") == "Hello world"
def test_not_emoji() -> None:
"""Test emojize function with non-emoji input."""
assert emojize("Hello :not_an_emoji:") == "Hello :not_an_emoji:"
def test_surrogate() -> None:
"""Test emojize function with surrogate pairs."""
surrogate_pairs = [
(":handshake_light_skin_tone_dark_skin_tone:", "🫱🏻‍🫲🏿"),
(":handshake_dark_skin_tone_light_skin_tone:", "🫱🏿‍🫲🏻"),
(":handshake_medium_skin_tone_light_skin_tone:", "🫱🏽‍🫲🏻"),
(":handshake_medium_light_skin_tone_dark_skin_tone:", "🫱🏼‍🫲🏿"),
(":handshake_medium_dark_skin_tone_light_skin_tone:", "🫱🏾‍🫲🏻"),
]
for emoji_name, surrogate in surrogate_pairs:
assert emojize(emoji_name) == surrogate
def test_multiple_emojis() -> None:
"""Test emojize function with multiple emojis."""
assert emojize(":smile: :wave:") == "😄 👋"
def test_complex_sentence() -> None:
"""Test emojize function with a complex sentence."""
assert emojize("Hello :wave:, what's up? :smile: :white_check_mark: :smile:") == "Hello 👋, what's up? 😄 ✅ 😄"

151
uv.lock generated Normal file
View File

@@ -0,0 +1,151 @@
version = 1
revision = 1
requires-python = "==3.12.*"
[[package]]
name = "basedpyright"
version = "1.29.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodejs-wheel-binaries" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/18/f5e488eac4960ad9a2e71b95f0d91cf93a982c7f68aa90e4e0554f0bc37e/basedpyright-1.29.1.tar.gz", hash = "sha256:06bbe6c3b50ab4af20f80e154049477a50d8b81d2522eadbc9f472f2f92cd44b", size = 21773469 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/1b/1bb837bbb7e259928f33d3c105dfef4f5349ef08b3ef45576801256e3234/basedpyright-1.29.1-py3-none-any.whl", hash = "sha256:b7eb65b9d4aaeeea29a349ac494252032a75a364942d0ac466d7f07ddeacc786", size = 11397959 },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
]
[[package]]
name = "dismoji"
source = { editable = "." }
dependencies = [
{ name = "json5" },
]
[package.dev-dependencies]
dev = [
{ name = "basedpyright" },
{ name = "pytest" },
{ name = "python-dotenv" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [{ name = "json5", specifier = ">=0.12.0" }]
[package.metadata.requires-dev]
dev = [
{ name = "basedpyright", specifier = ">=1.28.1" },
{ name = "pytest", specifier = ">=8.3.5" },
{ name = "python-dotenv", specifier = ">=1.0.1" },
{ name = "ruff", specifier = ">=0.9.9" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
]
[[package]]
name = "json5"
version = "0.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079 },
]
[[package]]
name = "nodejs-wheel-binaries"
version = "22.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/c7/4fd3871d2b7fd5122216245e273201ab98eda92bbd6fe9ad04846b758c56/nodejs_wheel_binaries-22.14.0.tar.gz", hash = "sha256:c1dc43713598c7310d53795c764beead861b8c5021fe4b1366cb912ce1a4c8bf", size = 8055 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/b6/66ef4ef75ea7389ea788f2d5505bf9a8e5c3806d56c7a90cf46a6942f1cf/nodejs_wheel_binaries-22.14.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:d8ab8690516a3e98458041286e3f0d6458de176d15c14f205c3ea2972131420d", size = 50326597 },
{ url = "https://files.pythonhosted.org/packages/7d/78/023d91a293ba73572a643bc89d11620d189f35f205a309dd8296aa45e69a/nodejs_wheel_binaries-22.14.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:b2f200f23b3610bdbee01cf136279e005ffdf8ee74557aa46c0940a7867956f6", size = 51158258 },
{ url = "https://files.pythonhosted.org/packages/af/86/324f6342c79e5034a13319b02ba9ed1f4ac8813af567d223c9a9e56cd338/nodejs_wheel_binaries-22.14.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0877832abd7a9c75c8c5caafa37f986c9341ee025043c2771213d70c4c1defa", size = 57180264 },
{ url = "https://files.pythonhosted.org/packages/6d/9f/42bdaab26137e31732bff00147b9aca2185d475b5752b57a443e6c7ba93f/nodejs_wheel_binaries-22.14.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fded5a70a8a55c2135e67bd580d8b7f2e94fcbafcc679b6a2d5b92f88373d69", size = 57693251 },
{ url = "https://files.pythonhosted.org/packages/ab/d7/94f8f269aa86cf35f9ed2b70d09aca48dc971fb5656fdc4a3b69364b189f/nodejs_wheel_binaries-22.14.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c1ade6f3ece458b40c02e89c91d5103792a9f18aaad5026da533eb0dcb87090e", size = 58841717 },
{ url = "https://files.pythonhosted.org/packages/2d/a0/43b7316eaf22b4ee9bfb897ee36c724efceac7b89d7d1bedca28057b7be1/nodejs_wheel_binaries-22.14.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34fa5ed4cf3f65cbfbe9b45c407ffc2fc7d97a06cd8993e6162191ff81f29f48", size = 59808791 },
{ url = "https://files.pythonhosted.org/packages/10/0a/814491f751a25136e37de68a2728c9a9e3c1d20494aba5ff3c230d5f9c2d/nodejs_wheel_binaries-22.14.0-py2.py3-none-win_amd64.whl", hash = "sha256:ca7023276327455988b81390fa6bbfa5191c1da7fc45bc57c7abc281ba9967e9", size = 40478921 },
{ url = "https://files.pythonhosted.org/packages/f4/5c/cab444afaa387dceac8debb817b52fd00596efcd2d54506c27311c6fe6a8/nodejs_wheel_binaries-22.14.0-py2.py3-none-win_arm64.whl", hash = "sha256:fd59c8e9a202221e316febe1624a1ae3b42775b7fb27737bf12ec79565983eaf", size = 36206637 },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
]
[[package]]
name = "pluggy"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
]
[[package]]
name = "pytest"
version = "8.3.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
]
[[package]]
name = "python-dotenv"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
]
[[package]]
name = "ruff"
version = "0.11.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d9/11/bcef6784c7e5d200b8a1f5c2ddf53e5da0efec37e6e5a44d163fb97e04ba/ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79", size = 4010053 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/1f/8848b625100ebcc8740c8bac5b5dd8ba97dd4ee210970e98832092c1635b/ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1", size = 10248105 },
{ url = "https://files.pythonhosted.org/packages/e0/47/c44036e70c6cc11e6ee24399c2a1e1f1e99be5152bd7dff0190e4b325b76/ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de", size = 11001494 },
{ url = "https://files.pythonhosted.org/packages/ed/5b/170444061650202d84d316e8f112de02d092bff71fafe060d3542f5bc5df/ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a", size = 10352151 },
{ url = "https://files.pythonhosted.org/packages/ff/91/f02839fb3787c678e112c8865f2c3e87cfe1744dcc96ff9fc56cfb97dda2/ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193", size = 10541951 },
{ url = "https://files.pythonhosted.org/packages/9e/f3/c09933306096ff7a08abede3cc2534d6fcf5529ccd26504c16bf363989b5/ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e", size = 10079195 },
{ url = "https://files.pythonhosted.org/packages/e0/0d/a87f8933fccbc0d8c653cfbf44bedda69c9582ba09210a309c066794e2ee/ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308", size = 11698918 },
{ url = "https://files.pythonhosted.org/packages/52/7d/8eac0bd083ea8a0b55b7e4628428203441ca68cd55e0b67c135a4bc6e309/ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55", size = 12319426 },
{ url = "https://files.pythonhosted.org/packages/c2/dc/d0c17d875662d0c86fadcf4ca014ab2001f867621b793d5d7eef01b9dcce/ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc", size = 11791012 },
{ url = "https://files.pythonhosted.org/packages/f9/f3/81a1aea17f1065449a72509fc7ccc3659cf93148b136ff2a8291c4bc3ef1/ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2", size = 13949947 },
{ url = "https://files.pythonhosted.org/packages/61/9f/a3e34de425a668284e7024ee6fd41f452f6fa9d817f1f3495b46e5e3a407/ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6", size = 11471753 },
{ url = "https://files.pythonhosted.org/packages/df/c5/4a57a86d12542c0f6e2744f262257b2aa5a3783098ec14e40f3e4b3a354a/ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2", size = 10417121 },
{ url = "https://files.pythonhosted.org/packages/58/3f/a3b4346dff07ef5b862e2ba06d98fcbf71f66f04cf01d375e871382b5e4b/ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03", size = 10073829 },
{ url = "https://files.pythonhosted.org/packages/93/cc/7ed02e0b86a649216b845b3ac66ed55d8aa86f5898c5f1691797f408fcb9/ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b", size = 11076108 },
{ url = "https://files.pythonhosted.org/packages/39/5e/5b09840fef0eff1a6fa1dea6296c07d09c17cb6fb94ed5593aa591b50460/ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9", size = 11512366 },
{ url = "https://files.pythonhosted.org/packages/6f/4c/1cd5a84a412d3626335ae69f5f9de2bb554eea0faf46deb1f0cb48534042/ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287", size = 10485900 },
{ url = "https://files.pythonhosted.org/packages/42/46/8997872bc44d43df986491c18d4418f1caff03bc47b7f381261d62c23442/ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e", size = 11558592 },
{ url = "https://files.pythonhosted.org/packages/d7/6a/65fecd51a9ca19e1477c3879a7fda24f8904174d1275b419422ac00f6eee/ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79", size = 10682766 },
]