Hi!
We would like to do these things:
MAX_ATX_COUNT private module variable.**, not __.<https://thisSyntaxFor.links>.autoLink now works, not sure why it didn't beforeThe current tokenizers don't allow to do this AFAIK.
We could easily take the current tokenizers as base and write our own, but that would not be very maintainable.
Would it make sense to have all these things changeable via options?
We could probably stich-up a pull request to have this upstream.
Thanks.
Ah, I see the options are parser options (not tokenizer options) and one cannot add arbitrary keys to it. So this seems like a no-go. Am I correct?
Why these “arbitrary” rules of what is and isn’t valid Markdown?
@wooorm It's not about considering stuff invalid Markdown.
The use-case is content managed by a group of people and even though you can set rules, you want to enforce it by the app & ignore things you don't support by your app, even though it is a valid Markdown.
This is already possible, but it's a little tricky. I did something similar when I wanted to repurpose __ for underlined text rather than strong text.
Basically you can add custom tokenizer options as a plugin to the unified processor. It works something like this:
import unified from 'unified';
import parse from 'remark-parse';
// this function is use()-ed by the unified processor to add custom parser behavior.
// it needs a normal function so you can access the parser via the context (this).
function customParserExtensions({/* custom parser options */}) {
const { Parser: { prototype: parserProto } } = this;
const {
blockMethods,
blockTokenizers,
inlineMethods,
inlineTokenizers,
} = parserProto;
// ADDING A CUSTOM INLINE TOKENIZER
// 1. define a tokenizer function for the custom key
inlineTokenizers.underline = tokenizeUnderline;
// 2. insert the custom key into the list of tokenizers.
// they are evaluated in order, so we splice "underline" in before "strong", which
// ensures that __ is processed as "underline" before the normal behavior processes it as "strong"
inlineMethods.splice(inlineMethods.indexOf('strong'), 0, 'underline');
// REMOVE A DEFAULT INLINE TOKENIZER
// just filter it out and it will never run, even though the tokenizer will still exist in inlineTokenizers
parserProto.inlineMethods = inlineMethods.filter(type => (type === 'autoLink' !== false));
// REMOVE A DEFAULT BLOCK TOKENIZER
// filter it out of the method list
parserProto.blockMethods = blockMethods.filter(type => (type === 'list' !== false));
// block methods can be nested, so there can be other references to the tokenizer (i think?)
// replacing the tokenizer with a no-op helps to avoid unexpected behavior that filtering the
// method list alone doesn't catch
blockTokenizers.list = passthruTokenize;
// REPLACING A BLOCK TOKENIZER
blockTokenizers.atxHeading = customAtxHeadingTokenize;
}
// NO-OP TOKENIZER
// it looks like a tokenizer, but does absolutely nothing.
function passthruTokenize() {
return undefined;
}
passthruTokenize.locator = () => -1;
// CUSTOM UNDERLINE TOKENIZER EXAMPLE
// for the sake of simplicity, this isn't the most robust implementation.
// it's not handling escape characters, for example.
function tokenizeUnderline(eat, value, silent) {
const [match, text] = value.match(/^__[^_\s]__/) || [undefined, undefined];
if (match) {
if (silent) {
return true;
}
const now = eat.now();
now.column += 2;
now.offset += 2;
return eat(match)({
type: 'underline',
children: this.tokenizeInline(text, now),
});
}
return undefined;
}
tokenizeUnderline.locator = (value, fromIndex) => value.indexOf('__', fromIndex);
// CUSTOM ATX HEADER
function customAtxHeadingTokenize(eat, value, silent) {
// copy from default implementation:
// https://github.com/remarkjs/remark/blob/master/packages/remark-parse/lib/tokenize/heading-atx.js
// and modify as needed
}
// TELL REMARK PROCESSOR TO USE CUSTOM PARSER EXTENSIONS
const processor = unified()
.use(remarkParse, {/* remark options */})
.use(customParserExtensions, {/* custom parser options */});
Using the code above as a base, here's how I'd implement what you're trying to do:
blockTokenizers.atxHeading with a custom implementation. For example, you could customize the default atxHeading tokenizer with your own max heading number.inlineTokenizers.strong with a custom implementation. Easiest thing is probably to copy the default strong tokenizer and remove the stuff about underscores.inlineMethods and/or no-op the inlineTokenizers.autoLink.I think the ways Keith outlined are fine. You can already do what you want.
When micromark comes, we’ll have another opportunity to look at how to turn on (and off) constructs, but I think this is fine for now.
Most helpful comment
This is already possible, but it's a little tricky. I did something similar when I wanted to repurpose
__for underlined text rather than strong text.Basically you can add custom tokenizer options as a plugin to the unified processor. It works something like this:
Using the code above as a base, here's how I'd implement what you're trying to do:
blockTokenizers.atxHeadingwith a custom implementation. For example, you could customize the default atxHeading tokenizer with your own max heading number.inlineTokenizers.strongwith a custom implementation. Easiest thing is probably to copy the default strong tokenizer and remove the stuff about underscores.inlineMethodsand/or no-op theinlineTokenizers.autoLink.