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

(chore) Docs: add tag to produce only a diagram, not code example #3946

Merged
merged 3 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 68 additions & 26 deletions packages/mermaid/src/docs.mts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,26 @@ const MERMAID_MAJOR_VERSION = (
).split('.')[0];
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 git: boolean = process.argv.includes('--git');
const watch: boolean = process.argv.includes('--watch');
const vitepress: boolean = process.argv.includes('--vitepress');
const noHeader: boolean = process.argv.includes('--noHeader') || vitepress;

// These paths are from the root of the mono-repo, not from the
// mermaid sub-directory
// These paths are from the root of the mono-repo, not from the mermaid subdirectory
const SOURCE_DOCS_DIR = 'src/docs';
const FINAL_DOCS_DIR = vitepress ? 'src/vitepress' : '../../docs';

Expand Down Expand Up @@ -153,7 +165,11 @@ const blockIcons: Record<string, string> = {

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) {
const vitepressType = type === 'note' ? 'info' : type;
return `::: ${vitepressType} ${customTitle || ''}\n${content}\n:::`;
Expand All @@ -180,41 +196,67 @@ const transformIncludeStatements = (file: string, text: string): string => {
}
});
};

/**
* Transform a markdown file and write the transformed file to the directory for published
* documentation
* 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
*
* 1. Add a `mermaid-example` block before every `mermaid` or `mmd` block On the docsify site (one
* place where the documentation is published), this will show the code used for the mermaid
* diagram
* 2. Add the text that says the file is automatically generated
* 3. Use prettier to format the file Verify that the file has been changed and write out the changes
* Convert the AST back to a string and return it.
*
* @param file {string} name of the file that will be verified
* @param content - the contents of a Markdown file
* @returns the contents with transformed code blocks
*/
const transformMarkdown = (file: string) => {
const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file)));
const ast: Root = remark.parse(doc);
const out = flatmap(ast, (c: Code) => {
if (c.type !== 'code' || !c.lang) {
return [c];
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
}

// 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' })];
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 codeblocks into block quotes.
if (['note', 'tip', 'warning', 'danger'].includes(c.lang)) {
return [remark.parse(transformToBlockQuote(c.value, c.lang, c.meta))];
// Transform these blocks into block quotes.
if (BLOCK_QUOTE_KEYWORDS.includes(node.lang)) {
return [remark.parse(transformToBlockQuote(node.value, node.lang, node.meta))];
}

return [c];
return [node]; // default is to do nothing to the node
});

let transformed = remark.stringify(out);
return remark.stringify(astWithTransformedBlocks);
};

/**
* Transform a markdown file and write the transformed file to the directory for published
* documentation
*
* 1. include any included files (copy and insert the source)
* 2. Add a `mermaid-example` block before every `mermaid` or `mmd` block On the main documentation site (one
* place where the documentation is published), this will show the code for the mermaid diagram
* 3. Transform blocks to block quotes as needed
* 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
*/
const transformMarkdown = (file: string) => {
const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file)));
let transformed = transformBlocks(doc);
if (!noHeader) {
// Add the header to the start of the file
transformed = `${generateHeader(file)}\n${transformed}`;
Expand Down
115 changes: 115 additions & 0 deletions packages/mermaid/src/docs.spec.ts
Original file line number Diff line number Diff line change
@@ -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\*\* /);
});
});
});
});
});