mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-16 05:49:43 +02:00
Merge pull request #3946 from weedySeaDragon/chore/3922_doc-diagram-only
(chore) Docs: add tag to produce only a diagram, not code example
This commit is contained in:
@@ -48,14 +48,26 @@ const MERMAID_MAJOR_VERSION = (
|
|||||||
).split('.')[0];
|
).split('.')[0];
|
||||||
const CDN_URL = 'https://cdn.jsdelivr.net/npm'; // 'https://unpkg.com';
|
const CDN_URL = 'https://cdn.jsdelivr.net/npm'; // 'https://unpkg.com';
|
||||||
|
|
||||||
|
const MERMAID_KEYWORD = 'mermaid';
|
||||||
|
const MERMAID_CODE_ONLY_KEYWORD = 'mermaid-example';
|
||||||
|
|
||||||
|
// These keywords will produce both a mermaid diagram and a code block with the diagram source
|
||||||
|
const MERMAID_EXAMPLE_KEYWORDS = [MERMAID_KEYWORD, 'mmd', MERMAID_CODE_ONLY_KEYWORD]; // 'mmd' is an old keyword that used to be used
|
||||||
|
|
||||||
|
// This keyword will only produce a mermaid diagram
|
||||||
|
const MERMAID_DIAGRAM_ONLY = 'mermaid-nocode';
|
||||||
|
|
||||||
|
// These will be transformed into block quotes
|
||||||
|
const BLOCK_QUOTE_KEYWORDS = ['note', 'tip', 'warning', 'danger'];
|
||||||
|
|
||||||
|
// options for running the main command
|
||||||
const verifyOnly: boolean = process.argv.includes('--verify');
|
const verifyOnly: boolean = process.argv.includes('--verify');
|
||||||
const git: boolean = process.argv.includes('--git');
|
const git: boolean = process.argv.includes('--git');
|
||||||
const watch: boolean = process.argv.includes('--watch');
|
const watch: boolean = process.argv.includes('--watch');
|
||||||
const vitepress: boolean = process.argv.includes('--vitepress');
|
const vitepress: boolean = process.argv.includes('--vitepress');
|
||||||
const noHeader: boolean = process.argv.includes('--noHeader') || vitepress;
|
const noHeader: boolean = process.argv.includes('--noHeader') || vitepress;
|
||||||
|
|
||||||
// These paths are from the root of the mono-repo, not from the
|
// These paths are from the root of the mono-repo, not from the mermaid subdirectory
|
||||||
// mermaid sub-directory
|
|
||||||
const SOURCE_DOCS_DIR = 'src/docs';
|
const SOURCE_DOCS_DIR = 'src/docs';
|
||||||
const FINAL_DOCS_DIR = vitepress ? 'src/vitepress' : '../../docs';
|
const FINAL_DOCS_DIR = vitepress ? 'src/vitepress' : '../../docs';
|
||||||
|
|
||||||
@@ -153,7 +165,11 @@ const blockIcons: Record<string, string> = {
|
|||||||
|
|
||||||
const capitalize = (word: string) => word[0].toUpperCase() + word.slice(1);
|
const capitalize = (word: string) => word[0].toUpperCase() + word.slice(1);
|
||||||
|
|
||||||
const transformToBlockQuote = (content: string, type: string, customTitle?: string | null) => {
|
export const transformToBlockQuote = (
|
||||||
|
content: string,
|
||||||
|
type: string,
|
||||||
|
customTitle?: string | null
|
||||||
|
) => {
|
||||||
if (vitepress) {
|
if (vitepress) {
|
||||||
const vitepressType = type === 'note' ? 'info' : type;
|
const vitepressType = type === 'note' ? 'info' : type;
|
||||||
return `::: ${vitepressType} ${customTitle || ''}\n${content}\n:::`;
|
return `::: ${vitepressType} ${customTitle || ''}\n${content}\n:::`;
|
||||||
@@ -180,41 +196,67 @@ const transformIncludeStatements = (file: string, text: string): string => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform code blocks in a Markdown file.
|
||||||
|
* Use remark.parse() to turn the given content (a String) into an AST.
|
||||||
|
* For any AST node that is a code block: transform it as needed:
|
||||||
|
* - blocks marked as MERMAID_DIAGRAM_ONLY will be set to a 'mermaid' code block so it will be rendered as (only) a diagram
|
||||||
|
* - blocks marked as MERMAID_EXAMPLE_KEYWORDS will be copied and the original node will be a code only block and the copy with be rendered as the diagram
|
||||||
|
* - blocks marked as BLOCK_QUOTE_KEYWORDS will be transformed into block quotes
|
||||||
|
*
|
||||||
|
* Convert the AST back to a string and return it.
|
||||||
|
*
|
||||||
|
* @param content - the contents of a Markdown file
|
||||||
|
* @returns the contents with transformed code blocks
|
||||||
|
*/
|
||||||
|
export const transformBlocks = (content: string): string => {
|
||||||
|
const ast: Root = remark.parse(content);
|
||||||
|
const astWithTransformedBlocks = flatmap(ast, (node: Code) => {
|
||||||
|
if (node.type !== 'code' || !node.lang) {
|
||||||
|
return [node]; // no transformation if this is not a code block
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.lang === MERMAID_DIAGRAM_ONLY) {
|
||||||
|
// Set the lang to 'mermaid' so it will be rendered as a diagram.
|
||||||
|
node.lang = MERMAID_KEYWORD;
|
||||||
|
return [node];
|
||||||
|
} else if (MERMAID_EXAMPLE_KEYWORDS.includes(node.lang)) {
|
||||||
|
// Return 2 nodes:
|
||||||
|
// 1. the original node with the language now set to 'mermaid-example' (will be rendered as code), and
|
||||||
|
// 2. a copy of the original node with the language set to 'mermaid' (will be rendered as a diagram)
|
||||||
|
node.lang = MERMAID_CODE_ONLY_KEYWORD;
|
||||||
|
return [node, Object.assign({}, node, { lang: MERMAID_KEYWORD })];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform these blocks into block quotes.
|
||||||
|
if (BLOCK_QUOTE_KEYWORDS.includes(node.lang)) {
|
||||||
|
return [remark.parse(transformToBlockQuote(node.value, node.lang, node.meta))];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [node]; // default is to do nothing to the node
|
||||||
|
});
|
||||||
|
|
||||||
|
return remark.stringify(astWithTransformedBlocks);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform a markdown file and write the transformed file to the directory for published
|
* Transform a markdown file and write the transformed file to the directory for published
|
||||||
* documentation
|
* documentation
|
||||||
*
|
*
|
||||||
* 1. Add a `mermaid-example` block before every `mermaid` or `mmd` block On the docsify site (one
|
* 1. include any included files (copy and insert the source)
|
||||||
* place where the documentation is published), this will show the code used for the mermaid
|
* 2. Add a `mermaid-example` block before every `mermaid` or `mmd` block On the main documentation site (one
|
||||||
* diagram
|
* place where the documentation is published), this will show the code for the mermaid diagram
|
||||||
* 2. Add the text that says the file is automatically generated
|
* 3. Transform blocks to block quotes as needed
|
||||||
* 3. Use prettier to format the file Verify that the file has been changed and write out the changes
|
* 4. Add the text that says the file is automatically generated
|
||||||
|
* 5. Use prettier to format the file.
|
||||||
|
* 6. Verify that the file has been changed and write out the changes
|
||||||
*
|
*
|
||||||
* @param file {string} name of the file that will be verified
|
* @param file {string} name of the file that will be verified
|
||||||
*/
|
*/
|
||||||
const transformMarkdown = (file: string) => {
|
const transformMarkdown = (file: string) => {
|
||||||
const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file)));
|
const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file)));
|
||||||
const ast: Root = remark.parse(doc);
|
let transformed = transformBlocks(doc);
|
||||||
const out = flatmap(ast, (c: Code) => {
|
|
||||||
if (c.type !== 'code' || !c.lang) {
|
|
||||||
return [c];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert mermaid code blocks to mermaid-example blocks
|
|
||||||
if (['mermaid', 'mmd', 'mermaid-example'].includes(c.lang)) {
|
|
||||||
c.lang = 'mermaid-example';
|
|
||||||
return [c, Object.assign({}, c, { lang: 'mermaid' })];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform codeblocks into block quotes.
|
|
||||||
if (['note', 'tip', 'warning', 'danger'].includes(c.lang)) {
|
|
||||||
return [remark.parse(transformToBlockQuote(c.value, c.lang, c.meta))];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [c];
|
|
||||||
});
|
|
||||||
|
|
||||||
let transformed = remark.stringify(out);
|
|
||||||
if (!noHeader) {
|
if (!noHeader) {
|
||||||
// Add the header to the start of the file
|
// Add the header to the start of the file
|
||||||
transformed = `${generateHeader(file)}\n${transformed}`;
|
transformed = `${generateHeader(file)}\n${transformed}`;
|
||||||
|
115
packages/mermaid/src/docs.spec.ts
Normal file
115
packages/mermaid/src/docs.spec.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { transformBlocks, transformToBlockQuote } from './docs.mjs';
|
||||||
|
|
||||||
|
import { remark } from 'remark'; // import it this way so we can mock it
|
||||||
|
vi.mock('remark');
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('docs.mts', () => {
|
||||||
|
describe('transformBlocks', () => {
|
||||||
|
it('uses remark.parse to create the AST for the file ', () => {
|
||||||
|
const remarkParseSpy = vi
|
||||||
|
.spyOn(remark, 'parse')
|
||||||
|
.mockReturnValue({ type: 'root', children: [] });
|
||||||
|
const contents = 'Markdown file contents';
|
||||||
|
transformBlocks(contents);
|
||||||
|
expect(remarkParseSpy).toHaveBeenCalledWith(contents);
|
||||||
|
});
|
||||||
|
describe('checks each AST node', () => {
|
||||||
|
it('does no transformation if there are no code blocks', async () => {
|
||||||
|
const contents = 'Markdown file contents\n';
|
||||||
|
const result = transformBlocks(contents);
|
||||||
|
expect(result).toEqual(contents);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('is a code block', () => {
|
||||||
|
const beforeCodeLine = 'test\n';
|
||||||
|
const diagram_text = 'graph\n A --> B\n';
|
||||||
|
|
||||||
|
describe('language = "mermaid-nocode"', () => {
|
||||||
|
const lang_keyword = 'mermaid-nocode';
|
||||||
|
const contents = beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n';
|
||||||
|
|
||||||
|
it('changes the language to "mermaid"', () => {
|
||||||
|
const result = transformBlocks(contents);
|
||||||
|
expect(result).toEqual(
|
||||||
|
beforeCodeLine + '\n' + '```' + 'mermaid' + '\n' + diagram_text + '\n```\n'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('language = "mermaid" | "mmd" | "mermaid-example"', () => {
|
||||||
|
const mermaid_keywords = ['mermaid', 'mmd', 'mermaid-example'];
|
||||||
|
|
||||||
|
mermaid_keywords.forEach((lang_keyword) => {
|
||||||
|
const contents =
|
||||||
|
beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n';
|
||||||
|
|
||||||
|
it('changes the language to "mermaid-example" and adds a copy of the code block with language = "mermaid"', () => {
|
||||||
|
const result = transformBlocks(contents);
|
||||||
|
expect(result).toEqual(
|
||||||
|
beforeCodeLine +
|
||||||
|
'\n' +
|
||||||
|
'```mermaid-example\n' +
|
||||||
|
diagram_text +
|
||||||
|
'\n```\n' +
|
||||||
|
'\n```mermaid\n' +
|
||||||
|
diagram_text +
|
||||||
|
'\n```\n'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls transformToBlockQuote with the node information', () => {
|
||||||
|
const lang_keyword = 'note';
|
||||||
|
const contents =
|
||||||
|
beforeCodeLine + '```' + lang_keyword + '\n' + 'This is the text\n' + '```\n';
|
||||||
|
|
||||||
|
const result = transformBlocks(contents);
|
||||||
|
expect(result).toEqual(beforeCodeLine + '\n> **Note**\n' + '> This is the text\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('transformToBlockQuote', () => {
|
||||||
|
// TODO Is there a way to test this with --vitepress given as a process argument?
|
||||||
|
|
||||||
|
describe('vitepress is not given as an argument', () => {
|
||||||
|
it('everything starts with "> " (= block quote)', () => {
|
||||||
|
const result = transformToBlockQuote('first line\n\n\nfourth line', 'blorfType');
|
||||||
|
expect(result).toMatch(/> (.)*\n> first line(?:\n> ){3}fourth line/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes an icon if there is one for the type', () => {
|
||||||
|
const result = transformToBlockQuote(
|
||||||
|
'first line\n\n\nfourth line',
|
||||||
|
'danger',
|
||||||
|
'Custom Title'
|
||||||
|
);
|
||||||
|
expect(result).toMatch(/> \*\*‼️ Custom Title\*\* /);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('a custom title is given', () => {
|
||||||
|
it('custom title is surrounded in spaces, in bold', () => {
|
||||||
|
const result = transformToBlockQuote(
|
||||||
|
'first line\n\n\nfourth line',
|
||||||
|
'blorfType',
|
||||||
|
'Custom Title'
|
||||||
|
);
|
||||||
|
expect(result).toMatch(/> \*\*Custom Title\*\* /);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip('no custom title is given', () => {
|
||||||
|
it('title is the icon and the capitalized type, in bold', () => {
|
||||||
|
const result = transformToBlockQuote('first line\n\n\nfourth line', 'blorf type');
|
||||||
|
expect(result).toMatch(/> \*\*Blorf Type\*\* /);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user