mirror of
https://github.com/Paillat-dev/dismoji.git
synced 2026-01-02 09:06:20 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18020f3bc8 | ||
| 7a42ead179 | |||
| 1d9d14f1ed | |||
| cc7d78a47a | |||
|
|
fe3bcffa24 | ||
|
|
b01368182f | ||
| 9fb0c776d7 | |||
| cfa8812081 | |||
| 34739a077c | |||
| 47aa6a3fc1 | |||
| ad98dd9a58 |
2
.github/workflows/publish.yaml
vendored
2
.github/workflows/publish.yaml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: pypi
|
environment: pypi
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/quality.yaml
vendored
8
.github/workflows/quality.yaml
vendored
@@ -9,9 +9,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Setup Copywrite
|
- name: Setup Copywrite
|
||||||
uses: hashicorp/setup-copywrite@5e3e8a26d7b9f8a508848ad0a069dfd2f7aa5339
|
uses: hashicorp/setup-copywrite@32f9f1c86f661b8a51100768976a06f1b281a035
|
||||||
- name: Check Header Compliance
|
- name: Check Header Compliance
|
||||||
run: copywrite headers --plan --config .copywrite.hcl
|
run: copywrite headers --plan --config .copywrite.hcl
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
name: "Python 3.13"
|
name: "Python 3.13"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@v6
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
<!-- end badges -->
|
<!-- end badges -->
|
||||||
|
|
||||||
A Python library for converting Discord emoji names to their Unicode equivalents.
|
A Python library for converting Discord emoji names to their Unicode equivalents and
|
||||||
|
vice versa.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -31,9 +32,9 @@ A Python library for converting Discord emoji names to their Unicode equivalents
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Dismoji is a lightweight Python library that provides a simple way to convert Discord
|
Dismoji is a lightweight Python library that provides a simple way to convert Discord
|
||||||
emoji names to their Unicode equivalents. With just a single function call, you can
|
emoji names to their Unicode equivalents and vice versa. With just two function calls,
|
||||||
transform text containing Discord-style emoji codes (like `:smile:`) into text with
|
you can transform text containing Discord-style emoji codes (like `:smile:`) into text
|
||||||
actual Unicode emoji characters (like "😄").
|
with actual Unicode emoji characters (like "😄") and back again.
|
||||||
|
|
||||||
This library uses
|
This library uses
|
||||||
[Paillat-dev/discord-emojis](https://github.com/Paillat-dev/discord-emojis) as the
|
[Paillat-dev/discord-emojis](https://github.com/Paillat-dev/discord-emojis) as the
|
||||||
@@ -56,16 +57,23 @@ import dismoji
|
|||||||
text = "Hello, :wave: I'm excited! :partying_face:"
|
text = "Hello, :wave: I'm excited! :partying_face:"
|
||||||
converted_text = dismoji.emojize(text)
|
converted_text = dismoji.emojize(text)
|
||||||
print(converted_text) # Output: "Hello, 👋 I'm excited! 🥳"
|
print(converted_text) # Output: "Hello, 👋 I'm excited! 🥳"
|
||||||
|
|
||||||
|
# Convert Unicode emojis back to Discord emoji names
|
||||||
|
emoji_text = "Hello, 👋 I'm excited! 🥳"
|
||||||
|
named_text = dismoji.demojize(emoji_text)
|
||||||
|
print(named_text) # Output: "Hello, :wave: I'm excited! :partying_face:"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Simple API**: Just one function to remember - `dismoji.emojize()`
|
- **Simple API**: Just two functions to remember - `dismoji.emojize()` and
|
||||||
|
`dismoji.demojize()`
|
||||||
- **Discord Compatible**: Supports Discord's emoji naming conventions
|
- **Discord Compatible**: Supports Discord's emoji naming conventions
|
||||||
- **Comprehensive**: Includes all standard emojis available on Discord
|
- **Comprehensive**: Includes all standard emojis available on Discord
|
||||||
- **Type Safe**: Fully type-annotated for better IDE integration
|
- **Type Safe**: Fully type-annotated for better IDE integration
|
||||||
- **Zero Dependencies**: Lightweight with no external dependencies
|
- **Zero Dependencies**: Lightweight with no external dependencies
|
||||||
- **Fast**: Optimized for quick emoji replacement
|
- **Fast**: Optimized for quick emoji replacement
|
||||||
|
- **Bidirectional**: Convert between emoji names and characters in both directions
|
||||||
|
|
||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
||||||
@@ -94,6 +102,10 @@ If you encounter issues or have questions about dismoji:
|
|||||||
- **HashiCorp Copywrite**: For managing license headers
|
- **HashiCorp Copywrite**: For managing license headers
|
||||||
- **basedpyright**: For type checking
|
- **basedpyright**: For type checking
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
- [`emoji`](https://pypi.org/project/emoji/) as inspiration for the API design
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License - Copyright (c) 2025 Paillat-dev
|
MIT License - Copyright (c) 2025 Paillat-dev
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:recommended"],
|
"extends": [
|
||||||
"baseBranches": ["master"],
|
"local>nicebots-xyz/renovate-config",
|
||||||
"labels": ["deps"],
|
":semanticPrefixFixDepsChoreOthers",
|
||||||
"ignorePaths": ["requirements.txt"],
|
":dependencyDashboard"
|
||||||
"commitMessagePrefix": "⬆️",
|
],
|
||||||
"commitMessageAction": "Upgrade",
|
"git-submodules": {
|
||||||
"packageRules": [
|
"enabled": true
|
||||||
{
|
|
||||||
"updateTypes": ["pin"],
|
|
||||||
"commitMessagePrefix": "📌",
|
|
||||||
"commitMessageAction": "Pin"
|
|
||||||
},
|
},
|
||||||
{
|
"forkProcessing": "enabled",
|
||||||
"updateTypes": ["rollback"],
|
"baseBranchPatterns": [
|
||||||
"commitMessagePrefix": "⬇️",
|
"master"
|
||||||
"commitMessageAction": "Downgrade"
|
],
|
||||||
},
|
"lockFileMaintenance": {
|
||||||
{
|
"enabled": true
|
||||||
"matchDatasources": ["pypi"],
|
|
||||||
"addLabels": ["pypi"]
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,32 @@ EMOJIS_PATH = Path(__file__).parent / "raw" / "build" / "emojis.json"
|
|||||||
with EMOJIS_PATH.open("r", encoding="utf-8") as f:
|
with EMOJIS_PATH.open("r", encoding="utf-8") as f:
|
||||||
EMOJIS = json.load(f)
|
EMOJIS = json.load(f)
|
||||||
|
|
||||||
EMOJI_MAPPING: dict[str, str] = {k: EMOJIS["emojis"][v]["surrogates"] for k, v in EMOJIS["nameToEmoji"].items()}
|
_VARIATION_SELECTOR = "\ufe0f" # We remove this as it is not needed by discord and causes issues with tests
|
||||||
|
EMOJI_MAPPING: dict[str, str] = {
|
||||||
|
k: EMOJIS["emojis"][v]["surrogates"].replace(_VARIATION_SELECTOR, "") for k, v in EMOJIS["nameToEmoji"].items()
|
||||||
|
}
|
||||||
|
|
||||||
del EMOJIS # Clean up to save memory
|
REVERSE_EMOJI_MAPPING: dict[str, str] = {}
|
||||||
|
|
||||||
EMOJI_PATTERN = re.compile(r":([a-zA-Z0-9_-]+):")
|
for emoji_index_str, emoji_index in sorted(EMOJIS["surrogateToEmoji"].items(), key=lambda x: len(x[0]), reverse=True):
|
||||||
|
# Get the first name in the list as the preferred name
|
||||||
|
e = EMOJIS["emojis"][emoji_index]
|
||||||
|
# If it has multiple diversity parents, use the last name because it is the most specific one
|
||||||
|
# e.g. :handshake_light_skin_tone_dark_skin_tone: vs :handshake_tone1_tone5:
|
||||||
|
REVERSE_EMOJI_MAPPING[emoji_index_str] = e["names"][-1 if e.get("hasMultiDiversityParent") else 0]
|
||||||
|
|
||||||
|
del EMOJIS, _VARIATION_SELECTOR # Clean up to save memory
|
||||||
|
|
||||||
|
EMOJI_PATTERN = re.compile(r":([\w+-]+):")
|
||||||
|
|
||||||
|
EMOJI_CHARS_PATTERN = re.compile("|".join(map(re.escape, REVERSE_EMOJI_MAPPING.keys())))
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def emojize(s: str) -> str:
|
def emojize(s: str) -> str:
|
||||||
@@ -29,11 +50,30 @@ def emojize(s: str) -> str:
|
|||||||
str: The input string with emoji names replaced by emoji characters.
|
str: The input string with emoji names replaced by emoji characters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
return EMOJI_PATTERN.sub(_replace, s)
|
||||||
|
|
||||||
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)
|
def _reverse_replace(match: re.Match[str]) -> str:
|
||||||
|
emoji = match.group(0)
|
||||||
|
return f":{REVERSE_EMOJI_MAPPING[emoji]}:"
|
||||||
|
|
||||||
|
|
||||||
|
def demojize(s: str) -> str:
|
||||||
|
"""Convert a string with emoji characters to a string with emoji names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): The input string containing emoji characters.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The input string with emoji characters replaced by emoji names.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return EMOJI_CHARS_PATTERN.sub(_reverse_replace, s)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"EMOJI_MAPPING",
|
||||||
|
"REVERSE_EMOJI_MAPPING",
|
||||||
|
"demojize",
|
||||||
|
"emojize",
|
||||||
|
)
|
||||||
|
|||||||
Submodule src/dismoji/raw updated: 079afc968f...78764682b8
@@ -1,7 +1,26 @@
|
|||||||
# Copyright (c) Paillat-dev
|
# Copyright (c) Paillat-dev
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
from dismoji import emojize
|
from dismoji import EMOJI_MAPPING, REVERSE_EMOJI_MAPPING, demojize, emojize
|
||||||
|
|
||||||
|
|
||||||
|
def are_equal(a: str, b: str) -> bool:
|
||||||
|
"""Check if two emojis are equal.
|
||||||
|
|
||||||
|
Allows for comparing emojis with modifiers even when they are in different orders.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
a (str): First emoji string.
|
||||||
|
b (str): Second emoji string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the emojis are equal, False otherwise.
|
||||||
|
"""
|
||||||
|
if len(a) != len(b):
|
||||||
|
return False
|
||||||
|
if len(a) == 1:
|
||||||
|
return a == b
|
||||||
|
return a[0] == b[0] and set(a[1:]) == set(b[1:])
|
||||||
|
|
||||||
|
|
||||||
def test_basic() -> None:
|
def test_basic() -> None:
|
||||||
@@ -68,3 +87,47 @@ def test_emoji_with_special_characters() -> None:
|
|||||||
]
|
]
|
||||||
for input_str, expected_output in special_char_tests:
|
for input_str, expected_output in special_char_tests:
|
||||||
assert emojize(input_str) == expected_output
|
assert emojize(input_str) == expected_output
|
||||||
|
|
||||||
|
|
||||||
|
def test_emojize_all() -> None:
|
||||||
|
for name, emoji in EMOJI_MAPPING.items():
|
||||||
|
assert are_equal(emojize(f":{name}:"), emoji)
|
||||||
|
|
||||||
|
|
||||||
|
def test_demojize_basic() -> None:
|
||||||
|
"""Test basic functionality of demojize function."""
|
||||||
|
assert demojize("Hello 😄") == "Hello :smile:"
|
||||||
|
|
||||||
|
|
||||||
|
def test_demojize_no_match() -> None:
|
||||||
|
"""Test demojize function with no matches."""
|
||||||
|
assert demojize("Hello world") == "Hello world"
|
||||||
|
|
||||||
|
|
||||||
|
def test_demojize_multiple_emojis() -> None:
|
||||||
|
"""Test demojize function with multiple emojis."""
|
||||||
|
assert demojize("😄 👋") == ":smile: :wave:"
|
||||||
|
|
||||||
|
|
||||||
|
def test_demojize_complex_sentence() -> None:
|
||||||
|
"""Test demojize function with a complex sentence."""
|
||||||
|
assert demojize("Hello 👋, what's up? 😄 ✅ 😄") == "Hello :wave:, what's up? :smile: :white_check_mark: :smile:"
|
||||||
|
|
||||||
|
|
||||||
|
def test_demojize_surrogate() -> None:
|
||||||
|
"""Test demojize 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 surrogate, emoji_name in surrogate_pairs:
|
||||||
|
assert demojize(surrogate) == emoji_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_demojize_all() -> None:
|
||||||
|
for emoji, name in REVERSE_EMOJI_MAPPING.items():
|
||||||
|
assert demojize(emoji) == f":{name}:"
|
||||||
|
|||||||
Reference in New Issue
Block a user