-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inline Options for Roles and Directives #28
Open
agoose77
wants to merge
17
commits into
main
Choose a base branch
from
agoose77/mep-inline-attributes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
4c19816
wip: MEP 3!
agoose77 4b5b761
feat: add angus as author
agoose77 4d09095
fix: future syntax example
agoose77 d8c9a57
fix: typos
agoose77 a266d25
fix: spacing
agoose77 0e7e939
fix: more details on grammar
agoose77 606bd4e
fix: add note about composing syntax types
agoose77 23ec849
fix: add note about id
agoose77 074cde1
feat: add note about .class coercion
agoose77 f7cc07a
Update meps/mep-0003.md
agoose77 398d8ba
chore: set status to active
agoose77 d3465a6
wip: improve language about LITERAL
agoose77 f4ece1f
name --> label
rowanc1 53e8ef6
wip: drop mention of syntax
agoose77 265ec40
Update mep-0003.md
agoose77 d7a306b
feat: add EBNF grammar
agoose77 7d3528c
fix: replace fenced code with directive
agoose77 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,3 +49,8 @@ project: | |
orcid: 0000-0002-0760-5497 | ||
affiliation: Curvenote Inc. | ||
email: [email protected] | ||
- id: agoose77 | ||
name: Angus Hollands | ||
github: agoose77 | ||
orcid: 0000-0003-0788-3814 | ||
affiliation: 2i2c |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
--- | ||
label: MEP0003 | ||
tags: | ||
- Active | ||
title: Inline Options for Roles and Directives | ||
date: 2025/01/27 | ||
authors: | ||
- rowanc1 | ||
- agoose77 | ||
short_title: Inline Options | ||
description: An inline syntax for setting options on roles and directives. | ||
data: | ||
discussion: "https://github.com/jupyter-book/myst-enhancement-proposals/pull/28" | ||
abbreviations: | ||
DSL: Domain Specific Language | ||
--- | ||
|
||
## Summary | ||
|
||
We propose an extension to the MyST Markdown syntax for roles and directives that facilitates inline definition of configuration options. | ||
|
||
## Context | ||
|
||
There is currently no syntax support for parametrising role definitions[^def] in MyST Markdown. This limitation has led to workarounds such as postfix role name conventions that conceptually group together related roles (e.g. `cite:ps`, `cite:t`), or the use of DSLs in the role body for parsing multiple values (e.g. `` {doc}`title <document.md>` ``). Given that these approaches are both unstandardized and limited in their extensibility, the lack of proper syntax support imposes strong contraints on the utility of roles. | ||
|
||
[^def]: Where a role _definition_ is the appearance of the role in a MyST document, rather than the role _declaration_ which specifies its behaviour and interface. | ||
|
||
## Proposal | ||
|
||
We propose extending the MyST Markdown syntax to support parametrisation of roles and directives. The existing syntax for defining a role and directive of the form | ||
|
||
``` | ||
{ROLE-NAME}`ROLE-BODY` | ||
``` | ||
|
||
and | ||
|
||
```` | ||
```{DIRECTIVE-NAME} | ||
DIRECTIVE-BODY | ||
``` | ||
```` | ||
|
||
will be extended by replacing the `{NAME}` syntax of specifying role/directive names with a more general _named inline attribute_ syntax of the form `{NAME .CLASS #ID KEY=VALUE}`. With this new syntax, it will be possible to directly define directive options in an inline manner, e.g. the following are equivalent: | ||
|
||
```` | ||
```{tip} | ||
:label: my-tip | ||
:class: dropdown | ||
|
||
Content of the tip directive. | ||
``` | ||
```` | ||
|
||
````{code} | ||
:label: new-syntax-example | ||
|
||
```{tip #my-tip .dropdown} | ||
Content of the tip directive. | ||
``` | ||
```` | ||
|
||
where [](#new-syntax-example) is the new inline attribute syntax. | ||
|
||
### Inline Attribute Syntax | ||
|
||
The internal inline syntax for specifying content takes inspiration from the prior art in [Pandoc](https://pandoc.org/chunkedhtml-demo/8.18-divs-and-spans.html)/[djot](https://htmlpreview.github.io/?https://github.com/jgm/djot/blob/master/doc/syntax.html#inline-attributes). | ||
|
||
In pseudo-grammar, the new _named_ inline attribute syntax may be expressed as `named-attribute-set` below | ||
|
||
```{code} ebnf | ||
:name: ebnf-grammar | ||
|
||
named-attribute-set = "{" { whitespace } name [ var-whitespace attribute { var-whitespace attribute } ] { whitespace } "}"; | ||
|
||
attribute = identifier | class | key-value; | ||
|
||
identifier = "#" attr-name; | ||
class = "." attr-name; | ||
key-value = key "=" value; | ||
|
||
name = name-token { name-token }; | ||
name-token = letter | "-" | "_" | ":" | "+" ; | ||
|
||
attr-name = attr-name-token { attr-name-token }; | ||
attr-name-token = letter | digit | "-" | "_" | ":"; | ||
|
||
key = key-token { key-token }; | ||
key-token = letter | digit | ':' | '-' | '_'; | ||
|
||
value = safe-value | quoted-value; | ||
|
||
safe-value = safe-value-token { safe-value-token }; | ||
safe-value-token = key; | ||
|
||
quoted-value = '"' quoted-value-token { quoted-value-token } '"'; | ||
quoted-value-token = letter | digit | quote-safe-punctuation | escaped-quote | non-escaping-slash; | ||
escaped-quote = "\\" '"'; | ||
non-escaping-slash = "\\" ( "\\" | digit | letter | quote-safe-punctuation); | ||
|
||
var-whitespace = whitespace { whitespace }; | ||
whitespace = " "; | ||
|
||
letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"; | ||
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; | ||
quote-safe-punctuation = "!" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" | "+" | "," | "-" | "." | ":" | ";" | "<" | "=" | ">" | "?" | "@" | "[" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "~"; | ||
|
||
``` | ||
|
||
in the following example of a named attribute set | ||
|
||
``` | ||
{NAME .CLASS #ID KEY=VALUE} | ||
``` | ||
|
||
the intepretations of `NAME`, `.CLASS`, `#ID`, and `KEY=VALUE` are thus: | ||
|
||
`NAME` | ||
: The name of the role or directive. This **must** be defined **once** at the beginning of the attributes definition. | ||
|
||
`.CLASS` | ||
: The name of a class to annotate the AST with. This **may** be (repeatedly) defined. Each defined class will be combined into a single whitespace-separated string and passed to the role or directive as the `class` option | ||
|
||
`#ID` | ||
: A unique label which will be passed to the role or directive as the `label` option. This **may** be defined **once**. If it is repeated, only the final label will be used and a warning will be raised. Users wishing to specify complex identifiers should pass in the `label` option explicitly as a [key-value pair](#key-value). | ||
|
||
(key-value)= | ||
`KEY=LITERAL-VALUE` or `KEY="RAW VALUE"` | ||
: A key-value pair to set as named options. A `KEY` **must** be defined **once** if the role definition marks the it as required, otherwise these **may** be defined **once**. Quotes are not needed when a literal is given (see [](#ebnf-grammar)). | ||
Backslash escapes may be used inside quoted values. These values are parsed according to the directive options (e.g. string, markup, number, etc.) | ||
|
||
Below is an example showing syntax that includes all extensions proposed here (this could apply to either a role or a directive): | ||
|
||
``` | ||
{name .classname #uniqueid key="value"}`some body markup` | ||
``` | ||
|
||
### Directive Parsing Rules | ||
|
||
Directives already support two mutually exclusive mechanisms of defining directive options. The syntax proposed by this PR is intended to complement this mechanisms for providing options, i.e. | ||
|
||
```` | ||
```{figure #fig-1} https://foo.com/image | ||
--- | ||
alt: | | ||
A long inline alt-text, | ||
in a figure far, far away ... | ||
--- | ||
|
||
I'm a caption! | ||
``` | ||
```` | ||
|
||
## Examples | ||
|
||
### Roles | ||
|
||
The new inline attribute syntax is helpful in specifying classes/IDs on elements:\ | ||
`` {highlight .red #important-point}`Inline _content_` ``\ | ||
or to give additional attributes to complex roles like reasons for a citation (e.g. [CiTO](https://sparontologies.github.io/cito/current/cito.html)):\ | ||
`` {cite cito="disputes"}`controversial-ref` ``.\ | ||
Other applications include buttons (primary, secondary classes, etc.), inline widgets (sliders, text-boxes, etc.), and evaluation controls (setting number format). | ||
|
||
### Directives | ||
|
||
Directives with only class and label options can be defined more concisely. For example, an example of a `tip` directive with an identifier and a `dropdown` class is written as: | ||
|
||
```` | ||
```{tip #my-tip .dropdown} | ||
Content of the tip directive. | ||
``` | ||
```` | ||
|
||
This is a much more concise version of the same syntax for specifying the directive options across multiple lines using the `:key: value` pairs: | ||
|
||
```` | ||
```{tip} | ||
:class: dropdown | ||
:label: my-tip | ||
Content of the tip directive. | ||
``` | ||
```` | ||
|
||
or using the `---` syntax to write the options in YAML: | ||
|
||
```` | ||
```{tip} | ||
--- | ||
class: dropdown | ||
label: my-tip | ||
--- | ||
Content of the tip directive. | ||
``` | ||
```` | ||
|
||
Each of the above is useful and will continue to be supported; the new inline attribute syntax will complement the existing block-level option specification. For example, the multi-line syntax is helpful for long descriptions such as a caption. The YAML is useful when making those descriptions run over multiple lines themselves and take advantage of other YAML syntax for lists, etc. The single line syntax is much more concise for specifying identifiers and classes. | ||
|
||
## UX implications & migration | ||
|
||
The proposed syntax is an addition to existing role and directive patterns. It is a stricly non-breaking enhancement; existing MyST documents that use this syntax are currently considered invalid and should not successfully parse. It does not invalidate or change the behavior of any existing workflows. | ||
|
||
The inline attribute syntax adopts the same inline attribute syntax used by Pandoc, Quarto, and djot, with the exception that the inline attribute must start with the role/directive name. This change follows from adapting the reference syntax to MyST Markdown, which already implements extensibilty through nominal types (directives and roles) rather than compositional types (i.e. class names). | ||
|
||
## Alternatives Considered | ||
|
||
We considered other approaches in this proposal and in discussion over several years. Including: | ||
|
||
1. extending the patterns used in Sphinx internal to the role content (`` {name}`content <argument .class1 key="value">` ``) | ||
2. attributes specified _after_ a role in addition to the name (``{name}`content`{.class1 key="value"}``); and | ||
|
||
We chose the current approach to (a) separate the options and content clearly (i.e. not 1), and (b) to keep role names and options close together (i.e. not 2). | ||
|
||
### Future Syntax Possibilities | ||
|
||
#### Anonymous Roles and Directives | ||
|
||
We could provide a short-hand for the div and span roles, where | ||
|
||
```` | ||
```{div .red} | ||
Some markup | ||
``` | ||
```` | ||
|
||
becomes | ||
|
||
```` | ||
```{.red} | ||
Some markup | ||
``` | ||
```` | ||
|
||
(bool-attrs)= | ||
|
||
#### Short-hand Boolean Attibutes | ||
|
||
In certain markup languages, such as React JSX, it is possible to pass boolean `true` values simply by name, e.g. | ||
|
||
``` | ||
<FooComponent myProp /> | ||
``` | ||
|
||
where `props.myProp` will be `true`. We might consider extending this syntax such that: | ||
|
||
```` | ||
```{code linenos} | ||
Numbered code directive | ||
|
||
Another line | ||
``` | ||
```` | ||
|
||
sets the `linenos` option to `true`. | ||
rowanc1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### Class Coercion | ||
|
||
Similarly to [](#bool-attrs), it may be helpful to use the `.CLASS` attribute to set boolean options. Consider the `tip` admonition: | ||
|
||
```` | ||
```{tip .dropdown .open} | ||
``` | ||
```` | ||
|
||
Where `dropdown` is a valid directive boolean option, setting `.dropdown` could preferentially set `dropdown=true`, and leave the `class` option untouched. This is not a syntax-level enhancement, but rather a proposal that would modify the consumer of the markup e.g. `mystmd`. | ||
|
||
## Questions or Objections Considered | ||
|
||
Adopting the proposed syntax opens up a currently impossible way to specify options for roles. The same syntax can also be applied to directives, however, it introduces a third way to specify options for directives. We find that acceptable, as this is for short-hand properties like classes and IDs, which ideally should not take up an extra line. | ||
|
||
## References | ||
|
||
We drew on prior art for inspiration and alignment, including: | ||
|
||
- djot [inline-attributes](https://htmlpreview.github.io/?https://github.com/jgm/djot/blob/master/doc/syntax.html#inline-attributes) | ||
- Sphinx allows you to set role data for an entire page using the [`role` directive](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#inline-code-highlighting). | ||
- Pandoc [bracketed spans](https://pandoc.org/MANUAL.html#extension-bracketed_spans) | ||
- MyST Python implementation [myst-parser](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#attributes) | ||
- https://michelf.ca/projects/php-markdown/extra/ |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flag values are not part of pandoc/djot: jgm/djot#257