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

Support using stdout #725

Merged
merged 4 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
20 changes: 20 additions & 0 deletions src-test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,26 @@ describe('mermaid-cli', () => {
expect(stderr).not.toContain('No input file specified, reading from stdin.')
}, timeout)

test('should warn when outputing to stdout with missing --outputFormat', async () => {
const execFilePromise = promisify(execFile)('node', ['src/cli.js', '--output', '-'])
await promisify(pipeline)(
createReadStream('test-positive/flowchart1.mmd'),
execFilePromise.child.stdin
)
const { stderr } = await execFilePromise
expect(stderr).toContain('No output format specified, using svg.')
}, timeout)

test('should not warn when outputing to stdout with --outputFormat', async () => {
const execFilePromise = promisify(execFile)('node', ['src/cli.js', '--output', '-', '--outputFormat', 'svg'])
await promisify(pipeline)(
createReadStream('test-positive/flowchart1.mmd'),
execFilePromise.child.stdin
)
const { stderr } = await execFilePromise
expect(stderr).not.toContain('No output format specified, using svg.')
}, timeout)

test('should error on mermaid syntax error', async () => {
await expect(
compileDiagram('test-negative', 'invalid.expect-error.mmd', 'svg')
Expand Down
28 changes: 23 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ async function cli () {
.addOption(new Option('-w, --width [width]', 'Width of the page').argParser(parseCommanderInt).default(800))
.addOption(new Option('-H, --height [height]', 'Height of the page').argParser(parseCommanderInt).default(600))
.option('-i, --input <input>', 'Input mermaid file. Files ending in .md will be treated as Markdown and all charts (e.g. ```mermaid (...)``` or :::mermaid (...):::) will be extracted and generated. Use `-` to read from stdin.')
.option('-o, --output [output]', 'Output file. It should be either md, svg, png or pdf. Optional. Default: input + ".svg"')
.option('-o, --output [output]', 'Output file. It should be either md, svg, png, pdf or use `-` to output to stdout. Optional. Default: input + ".svg"')
.addOption(new Option('-e, --outputFormat [format]', 'Output format for the generated image.').choices(['svg', 'png', 'pdf']).default(null, 'Loaded from the output file extension'))
.addOption(new Option('-b, --backgroundColor [backgroundColor]', 'Background color for pngs/svgs (not pdfs). Example: transparent, red, \'#F0F0F0\'.').default('white'))
.option('-c, --configFile [configFile]', 'JSON configuration file for mermaid.')
Expand Down Expand Up @@ -150,10 +150,22 @@ async function cli () {
} else {
output = input ? (`${input}.svg`) : 'out.svg'
}
}
if (!/\.(?:svg|png|pdf|md|markdown)$/.test(output)) {
} else if (output === '-') {
// `--output -` means write to stdout.
output = '/dev/stdout'
quiet = true

if (!outputFormat) {
outputFormat = 'svg'
warn('No output format specified, using svg. ' +
'If you want to specify an output format and supress this warning, ' +
'please use `-e <format>.` '
)
}
} else if (!/\.(?:svg|png|pdf|md|markdown)$/.test(output)) {
error('Output file must end with ".md"/".markdown", ".svg", ".png" or ".pdf"')
}

const outputDir = path.dirname(output)
if (!fs.existsSync(outputDir)) {
error(`Output directory "${outputDir}/" doesn't exist`)
Copy link
Contributor Author

@mzhubail mzhubail Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, maybe we shouldn't check the directory if we're writing to stdout.

Suggested change
const outputDir = path.dirname(output)
if (!fs.existsSync(outputDir)) {
error(`Output directory "${outputDir}/" doesn't exist`)
if (output !== '/dev/stdout') {
const outputDir = path.dirname(output)
if (!fs.existsSync(outputDir)) {
error(`Output directory "${outputDir}/" doesn't exist`)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea.

Non-Linux operating systems probably won't have a /dev folder.

If you want to avoid having an extra level of indentation, we could also change the code to the following:

Suggested change
const outputDir = path.dirname(output)
if (!fs.existsSync(outputDir)) {
error(`Output directory "${outputDir}/" doesn't exist`)
const outputDir = path.dirname(output)
if (output !== '/dev/stdout' && !fs.existsSync(outputDir)) {
error(`Output directory "${outputDir}/" doesn't exist`)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted.

Expand Down Expand Up @@ -390,7 +402,7 @@ function markdownImage ({ url, title, alt }) {
* path to a markdown file containing mermaid.
* If this is a string, loads the mermaid definition from the given file.
* If this is `undefined`, loads the mermaid definition from stdin.
* @param {`${string}.${"md" | "markdown" | "svg" | "png" | "pdf"}`} output - Path to the output file.
* @param {`${string}.${"md" | "markdown" | "svg" | "png" | "pdf"}` | "/dev/stdout"} output - Path to the output file.
* @param {Object} [opts] - Options
* @param {import("puppeteer").LaunchOptions} [opts.puppeteerConfig] - Puppeteer launch options.
* @param {boolean} [opts.quiet] - If set, suppress log output.
Expand Down Expand Up @@ -437,6 +449,10 @@ async function run (input, output, { puppeteerConfig = {}, quiet = false, output

const definition = await getInputData(input)
if (input && /\.(md|markdown)$/.test(input)) {
if (output === '/dev/stdout') {
throw new Error('Cannot use `stdout` with markdown input')
}

const imagePromises = []
for (const mermaidCodeblockMatch of definition.matchAll(mermaidChartsInMarkdownRegexGlobal)) {
if (browser === undefined) {
Expand Down Expand Up @@ -497,7 +513,9 @@ async function run (input, output, { puppeteerConfig = {}, quiet = false, output
info('Generating single mermaid chart')
browser = await puppeteer.launch(puppeteerConfig)
const data = await parseMMD(browser, definition, outputFormat, parseMMDOptions)
await fs.promises.writeFile(output, data)
await output !== '/dev/stdout'
? fs.promises.writeFile(output, data)
: process.stdout.write(data)
aloisklink marked this conversation as resolved.
Show resolved Hide resolved
}
} finally {
await browser?.close?.()
Expand Down