Skip to content

Commit ce52268

Browse files
authored
✨ NEW: Add attrs_image (experimental) extension (#620)
1 parent 4a30c24 commit ce52268

File tree

9 files changed

+129
-5
lines changed

9 files changed

+129
-5
lines changed

.pre-commit-config.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ repos:
2020
- id: trailing-whitespace
2121

2222
- repo: https://github.com/asottile/pyupgrade
23-
rev: v2.37.3
23+
rev: v2.38.2
2424
hooks:
2525
- id: pyupgrade
2626
args: [--py37-plus]
@@ -31,7 +31,7 @@ repos:
3131
- id: isort
3232

3333
- repo: https://github.com/psf/black
34-
rev: 22.6.0
34+
rev: 22.8.0
3535
hooks:
3636
- id: black
3737

@@ -52,7 +52,7 @@ repos:
5252
additional_dependencies:
5353
- sphinx~=5.0
5454
- markdown-it-py>=1.0.0,<3.0.0
55-
- mdit-py-plugins~=0.3.0
55+
- mdit-py-plugins~=0.3.1
5656
files: >
5757
(?x)^(
5858
myst_parser/.*py|

docs/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"strikethrough",
9191
"substitution",
9292
"tasklist",
93+
"attrs_image",
9394
]
9495
myst_number_code_blocks = ["typescript"]
9596
myst_heading_anchors = 2

docs/syntax/optional.md

+36
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,42 @@ HTML image can also be used inline!
786786

787787
I'm an inline image: <img src="img/fun-fish.png" height="20px">
788788

789+
### Inline attributes
790+
791+
:::{warning}
792+
This extension is currently experimental, and may change in future versions.
793+
:::
794+
795+
By adding `"attrs_image"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
796+
you can enable parsing of inline attributes for images.
797+
798+
For example, the following Markdown:
799+
800+
```md
801+
![image attrs](img/fun-fish.png){#imgattr .bg-primary width="100px" align=center}
802+
803+
{ref}`a reference to the image <imgattr>`
804+
```
805+
806+
will be parsed as:
807+
808+
![image attrs](img/fun-fish.png){#imgattr .bg-primary width="100px" align=center}
809+
810+
{ref}`a reference to the image <imgattr>`
811+
812+
Inside the curly braces, the following syntax is possible:
813+
814+
- `.foo` specifies `foo` as a class.
815+
Multiple classes may be given in this way; they will be combined.
816+
- `#foo` specifies `foo` as an identifier.
817+
An element may have only one identifier;
818+
if multiple identifiers are given, the last one is used.
819+
- `key="value"` or `key=value` specifies a key-value attribute.
820+
Quotes are not needed when the value consists entirely of
821+
ASCII alphanumeric characters or `_` or `:` or `-`.
822+
Backslash escapes may be used inside quoted values.
823+
- `%` begins a comment, which ends with the next `%` or the end of the attribute (`}`).
824+
789825
(syntax/figures)=
790826

791827
## Markdown Figures

myst_parser/config/main.py

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def check_extensions(_, __, value):
3131
diff = set(value).difference(
3232
[
3333
"amsmath",
34+
"attrs_image",
3435
"colon_fence",
3536
"deflist",
3637
"dollarmath",

myst_parser/mdit_to_docutils/base.py

+45
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,51 @@ def render_image(self, token: SyntaxTreeNode) -> None:
785785
title = token.attrGet("title")
786786
if title:
787787
img_node["title"] = token.attrGet("title")
788+
789+
# apply other attributes that can be set on the image
790+
if "class" in token.attrs:
791+
img_node["classes"].extend(str(token.attrs["class"]).split())
792+
if "width" in token.attrs:
793+
try:
794+
width = directives.length_or_percentage_or_unitless(
795+
str(token.attrs["width"])
796+
)
797+
except ValueError:
798+
self.create_warning(
799+
f"Invalid width value for image: {token.attrs['width']!r}",
800+
line=token_line(token, default=0),
801+
subtype="image",
802+
append_to=self.current_node,
803+
)
804+
else:
805+
img_node["width"] = width
806+
if "height" in token.attrs:
807+
try:
808+
height = directives.length_or_unitless(str(token.attrs["height"]))
809+
except ValueError:
810+
self.create_warning(
811+
f"Invalid height value for image: {token.attrs['height']!r}",
812+
line=token_line(token, default=0),
813+
subtype="image",
814+
append_to=self.current_node,
815+
)
816+
else:
817+
img_node["height"] = height
818+
if "align" in token.attrs:
819+
if token.attrs["align"] not in ("left", "center", "right"):
820+
self.create_warning(
821+
f"Invalid align value for image: {token.attrs['align']!r}",
822+
line=token_line(token, default=0),
823+
subtype="image",
824+
append_to=self.current_node,
825+
)
826+
else:
827+
img_node["align"] = token.attrs["align"]
828+
if "id" in token.attrs:
829+
name = nodes.fully_normalize_name(str(token.attrs["id"]))
830+
img_node["names"].append(name)
831+
self.document.note_explicit_target(img_node, img_node)
832+
788833
self.current_node.append(img_node)
789834

790835
# ### render methods for plugin tokens

myst_parser/parsers/mdit.py

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from markdown_it.renderer import RendererProtocol
1010
from mdit_py_plugins.amsmath import amsmath_plugin
1111
from mdit_py_plugins.anchors import anchors_plugin
12+
from mdit_py_plugins.attrs import attrs_plugin
1213
from mdit_py_plugins.colon_fence import colon_fence_plugin
1314
from mdit_py_plugins.deflist import deflist_plugin
1415
from mdit_py_plugins.dollarmath import dollarmath_plugin
@@ -100,6 +101,8 @@ def create_md_parser(
100101
md.use(tasklists_plugin)
101102
if "substitution" in config.enable_extensions:
102103
md.use(substitution_plugin, *config.sub_delimiters)
104+
if "attrs_image" in config.enable_extensions:
105+
md.use(attrs_plugin, after=("image",))
103106
if config.heading_anchors is not None:
104107
md.use(
105108
anchors_plugin,

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ dependencies = [
3737
"docutils>=0.15,<0.20",
3838
"jinja2", # required for substitutions, but let sphinx choose version
3939
"markdown-it-py>=1.0.0,<3.0.0",
40-
"mdit-py-plugins~=0.3.0",
40+
"mdit-py-plugins~=0.3.1",
4141
"pyyaml",
4242
"sphinx>=4,<6",
4343
"typing-extensions",
@@ -68,6 +68,7 @@ testing = [
6868
"pytest-regressions",
6969
"pytest-param-files~=0.3.4",
7070
"sphinx-pytest",
71+
"sphinx<5.2", # TODO 5.2 changes the attributes of desc/desc_signature nodes
7172
]
7273

7374
[project.scripts]

tests/test_renderers/fixtures/myst-config.txt

+33
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ www.example.com
4949
strike
5050
<raw format="html" xml:space="preserve">
5151
</s>
52+
53+
<string>:1: (WARNING/2) Strikethrough is currently only supported in HTML output [myst.strikethrough]
5254
.
5355

5456
[gfm-disallowed-html] --myst-gfm-only="yes"
@@ -141,3 +143,34 @@ www.commonmark.org/he<lp
141143
www.commonmark.org/he
142144
<lp
143145
.
146+
147+
[attrs_image] --myst-enable-extensions=attrs_image
148+
.
149+
![a](b){#id .a width="100%" align=center height=20px}{.b}
150+
.
151+
<document source="<string>">
152+
<paragraph>
153+
<image align="center" alt="a" classes="a b" height="20px" ids="id" names="id" uri="b" width="100%">
154+
.
155+
156+
[attrs_image_warnings] --myst-enable-extensions=attrs_image
157+
.
158+
![a](b){width=1x height=2x align=other }
159+
.
160+
<document source="<string>">
161+
<paragraph>
162+
<system_message level="2" line="1" source="<string>" type="WARNING">
163+
<paragraph>
164+
Invalid width value for image: '1x' [myst.image]
165+
<system_message level="2" line="1" source="<string>" type="WARNING">
166+
<paragraph>
167+
Invalid height value for image: '2x' [myst.image]
168+
<system_message level="2" line="1" source="<string>" type="WARNING">
169+
<paragraph>
170+
Invalid align value for image: 'other' [myst.image]
171+
<image alt="a" uri="b">
172+
173+
<string>:1: (WARNING/2) Invalid width value for image: '1x' [myst.image]
174+
<string>:1: (WARNING/2) Invalid height value for image: '2x' [myst.image]
175+
<string>:1: (WARNING/2) Invalid align value for image: 'other' [myst.image]
176+
.

tests/test_renderers/test_myst_config.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ def test_cmdline(file_params):
3131
parser=Parser(),
3232
settings_overrides=settings,
3333
)
34-
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
34+
output = doctree.pformat()
35+
warnings = report_stream.getvalue()
36+
if warnings:
37+
output += "\n" + warnings
38+
file_params.assert_expected(output, rstrip_lines=True)

0 commit comments

Comments
 (0)