-
Notifications
You must be signed in to change notification settings - Fork 66
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
♻️ REFACTOR: Directive plugins #31
Conversation
Codecov Report
@@ Coverage Diff @@
## main #31 +/- ##
==========================================
- Coverage 97.12% 96.05% -1.07%
==========================================
Files 24 28 +4
Lines 626 812 +186
Branches 135 181 +46
==========================================
+ Hits 608 780 +172
- Misses 18 32 +14
Continue to review full report at Codecov.
|
cc'ing @choldgraf @rowanc1 , @damianavila and @agoose77 for an initial look |
I have now also made the HTML tags for |
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.
Standardizing on the tokens coming out makes sense. As this comes in I will need to update downstream packages to compensate for these changes.
It looks like this is removing the reliance on markdown-it-container
, which may have been giving some problems with nesting of directives. Once this replaces, we should remove that library from dependencies.
This will also require some changes to the numbering (figure/equation/references) that I have done so far. Thinking that some of that may not be wanted/needed by consumers if they are only interested in the structure of the outputs (at the AST level).
Additionally the docs will need to change:
https://executablebooks.github.io/markdown-it-myst/overview.html#creating-a-new-directive
On a broader level, are you planning other large structural changes? Can we discuss those in an issue if you are?
src/directives2/base.ts
Outdated
if (logError) { | ||
console.error('unexpected "directive_base" token', token) | ||
} | ||
return `<pre><code>\n${token.info}: ${token.meta.arg}\n\n${token.content}\n</code></pre>\n` |
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.
Probably worth using escapeHtml
here?
import { escapeHtml } from 'markdown-it/lib/common/utils';
src/directives2/parseStructure.ts
Outdated
const totalArgs = | ||
(fullSpec.required_arguments || 0) + (fullSpec.optional_arguments || 0) | ||
if (args.length < (fullSpec.required_arguments || 0)) { | ||
throw new DirectiveParsingError( |
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.
Is there a strategy for collecting errors in the python implementation?
Would be nice for this to succeed in parsing, but show warnings to the user.
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.
yes they are logged as warnings, the parsing succeeds but the directive is not rendered. there is already a TODO in the admonition plugin, where the error is caught and the token discarded (presently without warning)
const tokens = mdit.parse(basicDirective, {}) | ||
// console.log(tokens.map(token => JSON.stringify(token))) | ||
expect(tokens.map(token => JSON.stringify(token))).toEqual([ | ||
'{"type":"open_admonition","tag":"aside","attrs":[["class","admonition' + |
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.
Makes sense to standardize at this level - hadn't been thinking about that.
Will each directive have its own type?
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.
yes indeed; the directives/roles in the first instance should be building up the token stream (i.e. AST), this allows for other plugins to potentially modify it "later".
src/directives2/admonition.ts
Outdated
|
||
const defaultTags = { | ||
main: 'aside', | ||
title: 'div', |
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.
I was using header
(semantic html) for this, and dropping the then unnecessary class name.
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.
yep I can change this to header
; I was having a look if there was any good tag for the body, but didn't see one? I'm not convinced though thats these can fully replace class names, for specificity
src/directives2/admonition.ts
Outdated
adToken.attrSet('class', classes.join(' ')) | ||
newTokens.push(adToken) | ||
const adTokenTitle = new Token('open_admonition_title', tags.title, 1) | ||
adTokenTitle.attrSet('class', 'admonition-title') |
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.
Are these classes part of the rendering or the tokens? It seems that the information about classes should be left to the renderer which can decide that an open_admonition_title
should have a specific css class?
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.
as mentioned there is no special renderer, its all handled by markdown-it's default one
src/directives2/parseStructure.ts
Outdated
required_arguments: 0, | ||
optional_arguments: 0, | ||
final_argument_whitespace: false, | ||
has_content: false, |
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.
Directive defaults have no content by default?
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.
correct
src/directives2/base.ts
Outdated
regex = DIRECTIVE_PATTERN, | ||
logError = true | ||
): void { | ||
// TODO also convert colon-fences |
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.
What is your strategy on this? Do the fences already come from markdown-it core identifying them?
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.
Thanks for the comments 👍, here some responses:
Yes indeed, and that will be covered by semantic versioning. If you haven't already, I would suggest pinning to 0.0.x
Indeed, this was removed a long time ago in the Python code
As mentioned in my initial comment, I will be rewriting all of them
👍
As mentioned I will be rewriting all the directives. I haven't had a look at the roles yet, but quite possibly aspects of that as well. Again the key point is to have them aligned with the sphinx/myst-parser code (plus make best use of markdown-it). If you haven't already I would strongly suggest you look through the myst-parser code and also sphinx, particularly: https://www.sphinx-doc.org/en/master/extdev/appapi.html, it will be necessary to understand that before understanding why certain decisions have been taken here |
Co-authored-by: Rowan Cockett <rowanc1@gmail.com>
@rowanc1 so obviously this PR has essentially been superseded by https://github.com/executablebooks/markdown-it-docutils. |
Closing this! See #34. |
Note for development I have put the code in the
src/directives2
folder, but eventually this will replacesrc/directives
. The admonition plugin is functionally complete (except option conversion/validation) and now I will start looking at the figure and math ones.So this PR essentially re-writes the whole of the directive plugin code/logic.
This is necessary since, among other things, for the current code:
The new logic is designed to closely mirror sphinx/myst-parser and follows as such:
src/directives/base.ts:pluginDirectiveBase
is defined.All specific directive plugins will rely on this being loaded first.
It simply walks through the token stream and converts all code fences, whose token.info matches the directive regex (i.e. looks like
{name} argument
), to adirective_base
type token, changes the token.info to just the name and stores the argument in token.meta.arg. Seetests/directiveBase.spec.ts
for an example.Having this as a separate plugin separates out the logic of identifying a directive, from how it is processed/rendered.
It means that it is possible, in the future, to change our directive syntax without affecting any of the other directive plugins. Or even people can use different plugins to generate these
directive_base
tokens.src/directives/parseStructure.ts:parseStructure
is defined.This is a function converts the first line and body of a
directive_base
token to the structure of a directive: args (list of strings), options (dict), body (string), dependent on the particular directive specification (e.g. how many arguments it requires and whether it has a body). The code is directly adapted frommyst_parser/parse_directives.py
.This code is generic to all directives, and will be called in the individual plugins.
src/directives2/admonition.ts:pluginDirectiveAdmonition
is defined.This acts on
directive_base
tokens of specific names (admonition, note, etc); first callingparseStructure
, performing a nested (block level only) parse on the body string of the directive, then replacing the original token with the following list of tokens:open_admonition
(tag = aside class = 'admonition' + type of admonition + any set in theclass
option of the directive)open_admonition_title
(tag = div, class = 'admonition-title')inline
(containing the title text)close_admonition_title
open_admonition_body
(tag = div, class = 'admonition-body')close_admonition_body
close_admonition
Outputting this "structure" of tokens means that you don't actually require a specific render method for admonitions, since the default markdown-it renderer will deal with it. Where possible this how plugins should be implemented; generating a token structure that directly mirrors the HTML structure and requires no/minimal custom rendering.
See for
tests/directiveAdmonition.spec.ts
for more examples, but this is what the parse looks like:is parsed to tokens:
which is rendered to: