Skip to content
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

marked.use #1646

Merged
merged 6 commits into from Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 33 additions & 28 deletions docs/USING_PRO.md
Expand Up @@ -12,24 +12,25 @@ The renderer defines the output of the parser.
// Create reference instance
const marked = require('marked');

// Get reference
const renderer = new marked.Renderer();

// Override function
renderer.heading = function(text, level) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');

return `
<h${level}>
<a name="${escapedText}" class="anchor" href="#${escapedText}">
<span class="header-link"></span>
</a>
${text}
</h${level}>`;
const renderer = {
heading(text, level) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');

return `
<h${level}>
<a name="${escapedText}" class="anchor" href="#${escapedText}">
<span class="header-link"></span>
</a>
${text}
</h${level}>`;
}
};

marked.use({ renderer });

// Run marked
console.log(marked('# heading+', { renderer }));
console.log(marked('# heading+'));
```

**Output:**
Expand Down Expand Up @@ -99,30 +100,34 @@ The tokenizer defines how to turn markdown text into tokens.
// Create reference instance
const marked = require('marked');

// Get reference
const tokenizer = new marked.Tokenizer();
const originalCodespan = tokenizer.codespan;
// Override function
tokenizer.codespan = function(src) {
const match = src.match(/\$+([^\$\n]+?)\$+/);
if (match) {
return {
type: 'codespan',
raw: match[0],
text: match[1].trim()
};
const tokenizer = {
codespan(src) {
const match = src.match(/\$+([^\$\n]+?)\$+/);
if (match) {
return {
type: 'codespan',
raw: match[0],
text: match[1].trim()
};
}

// return false to use original codespan tokenizer
return false;
styfle marked this conversation as resolved.
Show resolved Hide resolved
}
return originalCodespan.apply(this, arguments);
};

marked.use({ tokenizer });

// Run marked
console.log(marked('$ latex code $', { tokenizer }));
console.log(marked('$ latex code $\n\n` other code `'));
```

**Output:**

```html
<p><code>latext code</code></p>
<p><code>latex code</code></p>
<p><code>other code</code></p>
```

### Block level tokenizer methods
Expand Down
37 changes: 37 additions & 0 deletions src/marked.js
Expand Up @@ -127,6 +127,43 @@ marked.getDefaults = getDefaults;

marked.defaults = defaults;

/**
* Use Extension
*/

marked.use = function(extension) {
const opts = merge({}, extension);
if (extension.renderer) {
const renderer = marked.defaults.renderer || new Renderer();
for (const prop in extension.renderer) {
const prevRenderer = renderer[prop];
renderer[prop] = (...args) => {
styfle marked this conversation as resolved.
Show resolved Hide resolved
let ret = extension.renderer[prop].apply(renderer, args);
if (ret === false) {
ret = prevRenderer.apply(renderer, args);
}
return ret;
};
}
opts.renderer = renderer;
}
if (extension.tokenizer) {
const tokenizer = marked.defaults.tokenizer || new Tokenizer();
for (const prop in extension.tokenizer) {
const prevTokenizer = tokenizer[prop];
tokenizer[prop] = (...args) => {
let ret = extension.tokenizer[prop].apply(tokenizer, args);
if (ret === false) {
ret = prevTokenizer.apply(tokenizer, args);
}
return ret;
};
}
opts.tokenizer = tokenizer;
}
marked.setOptions(opts);
};

/**
* Expose
*/
Expand Down
133 changes: 133 additions & 0 deletions test/unit/marked-spec.js
Expand Up @@ -96,3 +96,136 @@ describe('inlineLexer', () => {
expect(renderer.html).toHaveBeenCalledWith('<img alt="MY IMAGE" src="example.png" />');
});
});

describe('use extension', () => {
it('should use renderer', () => {
const extension = {
renderer: {
paragraph(text) {
return 'extension';
}
}
};
spyOn(extension.renderer, 'paragraph').and.callThrough();
marked.use(extension);
const html = marked('text');
expect(extension.renderer.paragraph).toHaveBeenCalledWith('text');
expect(html).toBe('extension');
});

it('should use tokenizer', () => {
const extension = {
tokenizer: {
paragraph(text) {
return {
type: 'paragraph',
raw: text,
text: 'extension'
};
}
}
};
spyOn(extension.tokenizer, 'paragraph').and.callThrough();
marked.use(extension);
const html = marked('text');
expect(extension.tokenizer.paragraph).toHaveBeenCalledWith('text');
expect(html).toBe('<p>extension</p>\n');
});

it('should use options from extension', () => {
const extension = {
headerIds: false
};
marked.use(extension);
const html = marked('# heading');
expect(html).toBe('<h1>heading</h1>\n');
});

it('should use last extension function and not override others', () => {
const extension1 = {
renderer: {
paragraph(text) {
return 'extension1 paragraph\n';
},
html(html) {
return 'extension1 html\n';
}
}
};
const extension2 = {
renderer: {
paragraph(text) {
return 'extension2 paragraph\n';
}
}
};
marked.use(extension1);
marked.use(extension2);
const html = marked(`
paragraph

<html />

# heading
`);
expect(html).toBe('extension2 paragraph\nextension1 html\n<h1 id="heading">heading</h1>\n');
});

it('should use previous extension when returning false', () => {
const extension1 = {
renderer: {
paragraph(text) {
if (text !== 'original') {
return 'extension1 paragraph\n';
}
return false;
}
}
};
const extension2 = {
renderer: {
paragraph(text) {
if (text !== 'extension1' && text !== 'original') {
return 'extension2 paragraph\n';
}
return false;
}
}
};
marked.use(extension1);
marked.use(extension2);
const html = marked(`
paragraph

extension1

original
`);
expect(html).toBe('extension2 paragraph\nextension1 paragraph\n<p>original</p>\n');
});

it('should get options with this.options', () => {
const extension = {
renderer: {
heading: () => {
return this.options ? 'arrow options\n' : 'arrow no options\n';
},
html: function() {
return this.options ? 'function options\n' : 'function no options\n';
},
paragraph() {
return this.options ? 'shorthand options\n' : 'shorthand no options\n';
}
}
};
marked.use(extension);
const html = marked(`
# heading

<html />

paragraph
`);
expect(html).toBe('arrow no options\nfunction options\nshorthand options\n');
});
});