From 00c5fb17662fcf29fe4393ba4df5272bf5b2079d Mon Sep 17 00:00:00 2001 From: squidfunk Date: Wed, 20 Sep 2023 17:33:29 +0200 Subject: [PATCH] Added emoji extension --- material/extensions/__init__.py | 19 +++++++ material/extensions/emoji.py | 96 +++++++++++++++++++++++++++++++++ mkdocs.yml | 4 +- src/extensions/__init__.py | 19 +++++++ src/extensions/emoji.py | 96 +++++++++++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 material/extensions/__init__.py create mode 100644 material/extensions/emoji.py create mode 100644 src/extensions/__init__.py create mode 100644 src/extensions/emoji.py diff --git a/material/extensions/__init__.py b/material/extensions/__init__.py new file mode 100644 index 000000000..d18993785 --- /dev/null +++ b/material/extensions/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2016-2023 Martin Donath + +# 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 NON-INFRINGEMENT. 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. diff --git a/material/extensions/emoji.py b/material/extensions/emoji.py new file mode 100644 index 000000000..aa2e5b778 --- /dev/null +++ b/material/extensions/emoji.py @@ -0,0 +1,96 @@ +# Copyright (c) 2023 Martin Donath + +# 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 NON-INFRINGEMENT. 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. + +import codecs +import functools +import material +import os + +from glob import iglob +from inspect import getfile +from markdown import Markdown +from pymdownx import emoji, twemoji_db +from xml.etree.ElementTree import Element + +# ----------------------------------------------------------------------------- +# Functions +# ----------------------------------------------------------------------------- + +# Create twemoji index +def twemoji(options: object, md: Markdown): + paths = options.get("custom_icons", [])[:] + return _load_twemoji_index(tuple(paths)) + +# Create emoji or icon +def to_svg( + index: str, shortname: str, alias: str, uc: str | None, alt: str, + title: str, category: str, options: object, md: Markdown +): + if not uc: + icons = md.inlinePatterns["emoji"].emoji_index["emoji"] + + # Create and return element to host icon + el = Element("span", { "class": options.get("classes", index) }) + el.text = md.htmlStash.store(_load(icons[shortname]["path"])) + return el + + # Delegate to `pymdownx.emoji` extension + return emoji.to_svg( + index, shortname, alias, uc, alt, title, category, options, md + ) + +# ----------------------------------------------------------------------------- +# Helper functions +# ----------------------------------------------------------------------------- + +# Load icon +@functools.lru_cache(maxsize = None) +def _load(file: str): + with codecs.open(file, encoding = "utf-8") as f: + return f.read() + +# Load twemoji index and add icons +@functools.lru_cache(maxsize = None) +def _load_twemoji_index(paths): + index = { + "name": "twemoji", + "emoji": twemoji_db.emoji, + "aliases": twemoji_db.aliases + } + + # Compute path to theme root and traverse all icon directories + root = os.path.dirname(getfile(material)) + root = os.path.join(root, "templates", ".icons") + for path in [*paths, root]: + base = os.path.normpath(path) + + # Index icons provided by the theme and via custom icons + glob = os.path.join(base, "**", "*.svg") + glob = iglob(os.path.normpath(glob), recursive = True) + for file in glob: + icon = file[len(base) + 1:-4].replace(os.path.sep, "-") + + # Add icon to index + name = f":{icon}:" + if not any(name in index[key] for key in ["emoji", "aliases"]): + index["emoji"][name] = { "name": name, "path": file } + + # Return index + return index diff --git a/mkdocs.yml b/mkdocs.yml index 4e398d0ef..d20a51a2a 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -133,8 +133,8 @@ markdown_extensions: - pymdownx.caret - pymdownx.details - pymdownx.emoji: - emoji_generator: !!python/name:materialx.emoji.to_svg - emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + emoji_index: !!python/name:material.extensions.emoji.twemoji - pymdownx.highlight: anchor_linenums: true line_spans: __span diff --git a/src/extensions/__init__.py b/src/extensions/__init__.py new file mode 100644 index 000000000..d18993785 --- /dev/null +++ b/src/extensions/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2016-2023 Martin Donath + +# 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 NON-INFRINGEMENT. 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. diff --git a/src/extensions/emoji.py b/src/extensions/emoji.py new file mode 100644 index 000000000..aa2e5b778 --- /dev/null +++ b/src/extensions/emoji.py @@ -0,0 +1,96 @@ +# Copyright (c) 2023 Martin Donath + +# 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 NON-INFRINGEMENT. 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. + +import codecs +import functools +import material +import os + +from glob import iglob +from inspect import getfile +from markdown import Markdown +from pymdownx import emoji, twemoji_db +from xml.etree.ElementTree import Element + +# ----------------------------------------------------------------------------- +# Functions +# ----------------------------------------------------------------------------- + +# Create twemoji index +def twemoji(options: object, md: Markdown): + paths = options.get("custom_icons", [])[:] + return _load_twemoji_index(tuple(paths)) + +# Create emoji or icon +def to_svg( + index: str, shortname: str, alias: str, uc: str | None, alt: str, + title: str, category: str, options: object, md: Markdown +): + if not uc: + icons = md.inlinePatterns["emoji"].emoji_index["emoji"] + + # Create and return element to host icon + el = Element("span", { "class": options.get("classes", index) }) + el.text = md.htmlStash.store(_load(icons[shortname]["path"])) + return el + + # Delegate to `pymdownx.emoji` extension + return emoji.to_svg( + index, shortname, alias, uc, alt, title, category, options, md + ) + +# ----------------------------------------------------------------------------- +# Helper functions +# ----------------------------------------------------------------------------- + +# Load icon +@functools.lru_cache(maxsize = None) +def _load(file: str): + with codecs.open(file, encoding = "utf-8") as f: + return f.read() + +# Load twemoji index and add icons +@functools.lru_cache(maxsize = None) +def _load_twemoji_index(paths): + index = { + "name": "twemoji", + "emoji": twemoji_db.emoji, + "aliases": twemoji_db.aliases + } + + # Compute path to theme root and traverse all icon directories + root = os.path.dirname(getfile(material)) + root = os.path.join(root, "templates", ".icons") + for path in [*paths, root]: + base = os.path.normpath(path) + + # Index icons provided by the theme and via custom icons + glob = os.path.join(base, "**", "*.svg") + glob = iglob(os.path.normpath(glob), recursive = True) + for file in glob: + icon = file[len(base) + 1:-4].replace(os.path.sep, "-") + + # Add icon to index + name = f":{icon}:" + if not any(name in index[key] for key in ["emoji", "aliases"]): + index["emoji"][name] = { "name": name, "path": file } + + # Return index + return index