Skip to content

Commit 00c5fb1

Browse files
committed
Added emoji extension
1 parent 916e30c commit 00c5fb1

File tree

5 files changed

+232
-2
lines changed

5 files changed

+232
-2
lines changed

material/extensions/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) 2016-2023 Martin Donath <[email protected]>
2+
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to
5+
# deal in the Software without restriction, including without limitation the
6+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
# sell copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
# IN THE SOFTWARE.

material/extensions/emoji.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) 2023 Martin Donath <[email protected]>
2+
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to
5+
# deal in the Software without restriction, including without limitation the
6+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
# sell copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
# IN THE SOFTWARE.
20+
21+
import codecs
22+
import functools
23+
import material
24+
import os
25+
26+
from glob import iglob
27+
from inspect import getfile
28+
from markdown import Markdown
29+
from pymdownx import emoji, twemoji_db
30+
from xml.etree.ElementTree import Element
31+
32+
# -----------------------------------------------------------------------------
33+
# Functions
34+
# -----------------------------------------------------------------------------
35+
36+
# Create twemoji index
37+
def twemoji(options: object, md: Markdown):
38+
paths = options.get("custom_icons", [])[:]
39+
return _load_twemoji_index(tuple(paths))
40+
41+
# Create emoji or icon
42+
def to_svg(
43+
index: str, shortname: str, alias: str, uc: str | None, alt: str,
44+
title: str, category: str, options: object, md: Markdown
45+
):
46+
if not uc:
47+
icons = md.inlinePatterns["emoji"].emoji_index["emoji"]
48+
49+
# Create and return element to host icon
50+
el = Element("span", { "class": options.get("classes", index) })
51+
el.text = md.htmlStash.store(_load(icons[shortname]["path"]))
52+
return el
53+
54+
# Delegate to `pymdownx.emoji` extension
55+
return emoji.to_svg(
56+
index, shortname, alias, uc, alt, title, category, options, md
57+
)
58+
59+
# -----------------------------------------------------------------------------
60+
# Helper functions
61+
# -----------------------------------------------------------------------------
62+
63+
# Load icon
64+
@functools.lru_cache(maxsize = None)
65+
def _load(file: str):
66+
with codecs.open(file, encoding = "utf-8") as f:
67+
return f.read()
68+
69+
# Load twemoji index and add icons
70+
@functools.lru_cache(maxsize = None)
71+
def _load_twemoji_index(paths):
72+
index = {
73+
"name": "twemoji",
74+
"emoji": twemoji_db.emoji,
75+
"aliases": twemoji_db.aliases
76+
}
77+
78+
# Compute path to theme root and traverse all icon directories
79+
root = os.path.dirname(getfile(material))
80+
root = os.path.join(root, "templates", ".icons")
81+
for path in [*paths, root]:
82+
base = os.path.normpath(path)
83+
84+
# Index icons provided by the theme and via custom icons
85+
glob = os.path.join(base, "**", "*.svg")
86+
glob = iglob(os.path.normpath(glob), recursive = True)
87+
for file in glob:
88+
icon = file[len(base) + 1:-4].replace(os.path.sep, "-")
89+
90+
# Add icon to index
91+
name = f":{icon}:"
92+
if not any(name in index[key] for key in ["emoji", "aliases"]):
93+
index["emoji"][name] = { "name": name, "path": file }
94+
95+
# Return index
96+
return index

mkdocs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ markdown_extensions:
133133
- pymdownx.caret
134134
- pymdownx.details
135135
- pymdownx.emoji:
136-
emoji_generator: !!python/name:materialx.emoji.to_svg
137-
emoji_index: !!python/name:materialx.emoji.twemoji
136+
emoji_generator: !!python/name:material.extensions.emoji.to_svg
137+
emoji_index: !!python/name:material.extensions.emoji.twemoji
138138
- pymdownx.highlight:
139139
anchor_linenums: true
140140
line_spans: __span

src/extensions/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) 2016-2023 Martin Donath <[email protected]>
2+
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to
5+
# deal in the Software without restriction, including without limitation the
6+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
# sell copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
# IN THE SOFTWARE.

src/extensions/emoji.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) 2023 Martin Donath <[email protected]>
2+
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to
5+
# deal in the Software without restriction, including without limitation the
6+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
# sell copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
# IN THE SOFTWARE.
20+
21+
import codecs
22+
import functools
23+
import material
24+
import os
25+
26+
from glob import iglob
27+
from inspect import getfile
28+
from markdown import Markdown
29+
from pymdownx import emoji, twemoji_db
30+
from xml.etree.ElementTree import Element
31+
32+
# -----------------------------------------------------------------------------
33+
# Functions
34+
# -----------------------------------------------------------------------------
35+
36+
# Create twemoji index
37+
def twemoji(options: object, md: Markdown):
38+
paths = options.get("custom_icons", [])[:]
39+
return _load_twemoji_index(tuple(paths))
40+
41+
# Create emoji or icon
42+
def to_svg(
43+
index: str, shortname: str, alias: str, uc: str | None, alt: str,
44+
title: str, category: str, options: object, md: Markdown
45+
):
46+
if not uc:
47+
icons = md.inlinePatterns["emoji"].emoji_index["emoji"]
48+
49+
# Create and return element to host icon
50+
el = Element("span", { "class": options.get("classes", index) })
51+
el.text = md.htmlStash.store(_load(icons[shortname]["path"]))
52+
return el
53+
54+
# Delegate to `pymdownx.emoji` extension
55+
return emoji.to_svg(
56+
index, shortname, alias, uc, alt, title, category, options, md
57+
)
58+
59+
# -----------------------------------------------------------------------------
60+
# Helper functions
61+
# -----------------------------------------------------------------------------
62+
63+
# Load icon
64+
@functools.lru_cache(maxsize = None)
65+
def _load(file: str):
66+
with codecs.open(file, encoding = "utf-8") as f:
67+
return f.read()
68+
69+
# Load twemoji index and add icons
70+
@functools.lru_cache(maxsize = None)
71+
def _load_twemoji_index(paths):
72+
index = {
73+
"name": "twemoji",
74+
"emoji": twemoji_db.emoji,
75+
"aliases": twemoji_db.aliases
76+
}
77+
78+
# Compute path to theme root and traverse all icon directories
79+
root = os.path.dirname(getfile(material))
80+
root = os.path.join(root, "templates", ".icons")
81+
for path in [*paths, root]:
82+
base = os.path.normpath(path)
83+
84+
# Index icons provided by the theme and via custom icons
85+
glob = os.path.join(base, "**", "*.svg")
86+
glob = iglob(os.path.normpath(glob), recursive = True)
87+
for file in glob:
88+
icon = file[len(base) + 1:-4].replace(os.path.sep, "-")
89+
90+
# Add icon to index
91+
name = f":{icon}:"
92+
if not any(name in index[key] for key in ["emoji", "aliases"]):
93+
index["emoji"][name] = { "name": name, "path": file }
94+
95+
# Return index
96+
return index

0 commit comments

Comments
 (0)