Skip to content

Commit

Permalink
✨ NEW: Add attrs_image (experimental) extension (#620)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjsewell committed Sep 27, 2022
1 parent 4a30c24 commit ce52268
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 5 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Expand Up @@ -20,7 +20,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
rev: v2.38.2
hooks:
- id: pyupgrade
args: [--py37-plus]
Expand All @@ -31,7 +31,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 22.6.0
rev: 22.8.0
hooks:
- id: black

Expand All @@ -52,7 +52,7 @@ repos:
additional_dependencies:
- sphinx~=5.0
- markdown-it-py>=1.0.0,<3.0.0
- mdit-py-plugins~=0.3.0
- mdit-py-plugins~=0.3.1
files: >
(?x)^(
myst_parser/.*py|
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Expand Up @@ -90,6 +90,7 @@
"strikethrough",
"substitution",
"tasklist",
"attrs_image",
]
myst_number_code_blocks = ["typescript"]
myst_heading_anchors = 2
Expand Down
36 changes: 36 additions & 0 deletions docs/syntax/optional.md
Expand Up @@ -786,6 +786,42 @@ HTML image can also be used inline!

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

### Inline attributes

:::{warning}
This extension is currently experimental, and may change in future versions.
:::

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)),
you can enable parsing of inline attributes for images.

For example, the following Markdown:

```md
![image attrs](img/fun-fish.png){#imgattr .bg-primary width="100px" align=center}

{ref}`a reference to the image <imgattr>`
```

will be parsed as:

![image attrs](img/fun-fish.png){#imgattr .bg-primary width="100px" align=center}

{ref}`a reference to the image <imgattr>`

Inside the curly braces, the following syntax is possible:

- `.foo` specifies `foo` as a class.
Multiple classes may be given in this way; they will be combined.
- `#foo` specifies `foo` as an identifier.
An element may have only one identifier;
if multiple identifiers are given, the last one is used.
- `key="value"` or `key=value` specifies a key-value attribute.
Quotes are not needed when the value consists entirely of
ASCII alphanumeric characters or `_` or `:` or `-`.
Backslash escapes may be used inside quoted values.
- `%` begins a comment, which ends with the next `%` or the end of the attribute (`}`).

(syntax/figures)=

## Markdown Figures
Expand Down
1 change: 1 addition & 0 deletions myst_parser/config/main.py
Expand Up @@ -31,6 +31,7 @@ def check_extensions(_, __, value):
diff = set(value).difference(
[
"amsmath",
"attrs_image",
"colon_fence",
"deflist",
"dollarmath",
Expand Down
45 changes: 45 additions & 0 deletions myst_parser/mdit_to_docutils/base.py
Expand Up @@ -785,6 +785,51 @@ def render_image(self, token: SyntaxTreeNode) -> None:
title = token.attrGet("title")
if title:
img_node["title"] = token.attrGet("title")

# apply other attributes that can be set on the image
if "class" in token.attrs:
img_node["classes"].extend(str(token.attrs["class"]).split())
if "width" in token.attrs:
try:
width = directives.length_or_percentage_or_unitless(
str(token.attrs["width"])
)
except ValueError:
self.create_warning(
f"Invalid width value for image: {token.attrs['width']!r}",
line=token_line(token, default=0),
subtype="image",
append_to=self.current_node,
)
else:
img_node["width"] = width
if "height" in token.attrs:
try:
height = directives.length_or_unitless(str(token.attrs["height"]))
except ValueError:
self.create_warning(
f"Invalid height value for image: {token.attrs['height']!r}",
line=token_line(token, default=0),
subtype="image",
append_to=self.current_node,
)
else:
img_node["height"] = height
if "align" in token.attrs:
if token.attrs["align"] not in ("left", "center", "right"):
self.create_warning(
f"Invalid align value for image: {token.attrs['align']!r}",
line=token_line(token, default=0),
subtype="image",
append_to=self.current_node,
)
else:
img_node["align"] = token.attrs["align"]
if "id" in token.attrs:
name = nodes.fully_normalize_name(str(token.attrs["id"]))
img_node["names"].append(name)
self.document.note_explicit_target(img_node, img_node)

self.current_node.append(img_node)

# ### render methods for plugin tokens
Expand Down
3 changes: 3 additions & 0 deletions myst_parser/parsers/mdit.py
Expand Up @@ -9,6 +9,7 @@
from markdown_it.renderer import RendererProtocol
from mdit_py_plugins.amsmath import amsmath_plugin
from mdit_py_plugins.anchors import anchors_plugin
from mdit_py_plugins.attrs import attrs_plugin
from mdit_py_plugins.colon_fence import colon_fence_plugin
from mdit_py_plugins.deflist import deflist_plugin
from mdit_py_plugins.dollarmath import dollarmath_plugin
Expand Down Expand Up @@ -100,6 +101,8 @@ def create_md_parser(
md.use(tasklists_plugin)
if "substitution" in config.enable_extensions:
md.use(substitution_plugin, *config.sub_delimiters)
if "attrs_image" in config.enable_extensions:
md.use(attrs_plugin, after=("image",))
if config.heading_anchors is not None:
md.use(
anchors_plugin,
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Expand Up @@ -37,7 +37,7 @@ dependencies = [
"docutils>=0.15,<0.20",
"jinja2", # required for substitutions, but let sphinx choose version
"markdown-it-py>=1.0.0,<3.0.0",
"mdit-py-plugins~=0.3.0",
"mdit-py-plugins~=0.3.1",
"pyyaml",
"sphinx>=4,<6",
"typing-extensions",
Expand Down Expand Up @@ -68,6 +68,7 @@ testing = [
"pytest-regressions",
"pytest-param-files~=0.3.4",
"sphinx-pytest",
"sphinx<5.2", # TODO 5.2 changes the attributes of desc/desc_signature nodes
]

[project.scripts]
Expand Down
33 changes: 33 additions & 0 deletions tests/test_renderers/fixtures/myst-config.txt
Expand Up @@ -49,6 +49,8 @@ www.example.com
strike
<raw format="html" xml:space="preserve">
</s>

<string>:1: (WARNING/2) Strikethrough is currently only supported in HTML output [myst.strikethrough]
.

[gfm-disallowed-html] --myst-gfm-only="yes"
Expand Down Expand Up @@ -141,3 +143,34 @@ www.commonmark.org/he<lp
www.commonmark.org/he
<lp
.

[attrs_image] --myst-enable-extensions=attrs_image
.
![a](b){#id .a width="100%" align=center height=20px}{.b}
.
<document source="<string>">
<paragraph>
<image align="center" alt="a" classes="a b" height="20px" ids="id" names="id" uri="b" width="100%">
.

[attrs_image_warnings] --myst-enable-extensions=attrs_image
.
![a](b){width=1x height=2x align=other }
.
<document source="<string>">
<paragraph>
<system_message level="2" line="1" source="<string>" type="WARNING">
<paragraph>
Invalid width value for image: '1x' [myst.image]
<system_message level="2" line="1" source="<string>" type="WARNING">
<paragraph>
Invalid height value for image: '2x' [myst.image]
<system_message level="2" line="1" source="<string>" type="WARNING">
<paragraph>
Invalid align value for image: 'other' [myst.image]
<image alt="a" uri="b">

<string>:1: (WARNING/2) Invalid width value for image: '1x' [myst.image]
<string>:1: (WARNING/2) Invalid height value for image: '2x' [myst.image]
<string>:1: (WARNING/2) Invalid align value for image: 'other' [myst.image]
.
6 changes: 5 additions & 1 deletion tests/test_renderers/test_myst_config.py
Expand Up @@ -31,4 +31,8 @@ def test_cmdline(file_params):
parser=Parser(),
settings_overrides=settings,
)
file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
output = doctree.pformat()
warnings = report_stream.getvalue()
if warnings:
output += "\n" + warnings
file_params.assert_expected(output, rstrip_lines=True)

0 comments on commit ce52268

Please sign in to comment.