Skip to content

Commit

Permalink
merge master and update for svg output
Browse files Browse the repository at this point in the history
  • Loading branch information
raghur committed Dec 23, 2023
1 parent f029a15 commit 053b111
Show file tree
Hide file tree
Showing 11 changed files with 12,807 additions and 3,796 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
20 changes: 20 additions & 0 deletions .github/workflows/pr-checks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# github action to run checks on PRs and pushes to master
name: Checks

on:
push:
branches:
- "*" # all branches
pull_request:
branches:
- master

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test -- --coverage
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ img/
.vscode/
.mermaid*
test-output/
coverage/
179 changes: 13 additions & 166 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,170 +1,17 @@
// #! /usr/bin/env node
var pandoc = require('pandoc-filter');
var _ = require('lodash');
var tmp = require('tmp');
var fs = require('fs');
var path = require('path');
var exec = require('child_process').execSync;
var process = require('process')
var sanfile = require('sanitize-filename')
const pandoc = require('pandoc-filter')
const process = require('process')
const utils = require('./lib')
const tmp = require('tmp')
const fs = require('fs')

var prefix = "diagram";
var cmd = externalTool("mmdc");
var imgur = externalTool("imgur");
var counter = 0;
var folder = process.cwd()
// Create a writeable stream to redirect stderr to file - if it logs to stdout, then pandoc hangs due to improper json.
// errorLog is used in pandoc.toJSONFilter
var tmpObj = tmp.fileSync({ mode: 0o644, prefix: 'mermaid-filter-', postfix: '.err' });
var errorLog = fs.createWriteStream(tmpObj.name)

function mermaid(type, value, _format, _meta) {
if (type != "CodeBlock") return null;
var attrs = value[0],
content = value[1];
var id = attrs[0],
classes = attrs[1];
var options = {
width: process.env.MERMAID_FILTER_WIDTH || 800,
format: process.env.MERMAID_FILTER_FORMAT || 'png',
loc: process.env.MERMAID_FILTER_LOC || 'inline',
theme: process.env.MERMAID_FILTER_THEME || 'default',
background: process.env.MERMAID_FILTER_BACKGROUND || 'white',
caption: process.env.MERMAID_FILTER_CAPTION || '',
filename: process.env.MERMAID_FILTER_FILENAME || '',
scale: process.env.MERMAID_FILTER_SCALE || 1,
imageClass: process.env.MERMAID_FILTER_IMAGE_CLASS || ''
};
var configFile = process.env.MERMAID_FILTER_MERMAID_CONFIG || path.join(folder, ".mermaid-config.json");
var confFileOpts = ""
if (fs.existsSync(configFile)) {
confFileOpts += ` -c "${configFile}"`
}
var puppeteerConfig = process.env.MERMAID_FILTER_PUPPETEER_CONFIG || path.join(folder, ".puppeteer.json");
var puppeteerOpts = ""
if (fs.existsSync(puppeteerConfig)) {
puppeteerOpts += ` -p "${puppeteerConfig}"`
}
var cssFile = process.env.MERMAID_FILTER_MERMAID_CSS || path.join(folder, ".mermaid.css");
if (fs.existsSync(cssFile)) {
confFileOpts += ` -C "${cssFile}"`
}

// console.log(classes)
if (classes.indexOf('mermaid') < 0) return null;

// console.log(attrs, content);
attrs[2].map(item => {
if (item.length === 1) options[item[0]] = true;
else options[item[0]] = item[1];
});
// console.log(options);
// if (options.loc === 'inline') options.format = 'svg'
counter++;
//console.log(content);
var tmpfileObj = tmp.fileSync();
// console.log(tmpfileObj.name);
fs.writeFileSync(tmpfileObj.name, content);
var outdir = options.loc !== 'imgur' ? options.loc : path.dirname(tmpfileObj.name);
// console.log(outdir);

if (options.caption !== "" && options.filename === ""){
options.filename = sanfile(options.caption, {replacement: ''}).replace(/[#$~%+;()\[\]{}&=_\-\s]/g, '');
}

if (options.filename === ""){
options.filename = `${prefix}-${counter}`;
}

var savePath = tmpfileObj.name + "." + options.format
var newPath = path.join(outdir, `${options.filename}.${options.format}`);
var fullCmd = `${cmd} ${confFileOpts} ${puppeteerOpts} -w ${options.width} -s ${options.scale} -f -i "${tmpfileObj.name}" -t ${options.theme} -b ${options.background} -o "${savePath}"`
// console.log(fullCmd, savePath)
exec(fullCmd);
//console.log(oldPath, newPath);
if (options.loc == 'inline') {
if (options.format === 'svg') {
var data = fs.readFileSync(savePath, 'utf8')
// newPath = "data:image/svg+xml;base64," + Buffer.from(data).toString('base64');


// does not use default theme - picks the forest theme in the test.md
return pandoc.RawBlock('html', data);
} else if (options.format === 'pdf') {
newPath = savePath
} else {
var data = fs.readFileSync(savePath)
newPath = 'data:image/png;base64,' + Buffer.from(data).toString('base64');
}
} else if (options.loc === 'imgur')
newPath = exec(`${imgur} ${savePath}`)
.toString()
.trim()
.replace("http://", "https://");
else {
mv(savePath, newPath);
}

var fig = "";

if (options.caption != "") {
fig = "fig:";
}

var imageClasses = options.imageClass ? [options.imageClass] : []

if (options.loc == 'inline' && options.format === 'svg') {
}
return pandoc.Para(
[
pandoc.Image(
[id, imageClasses, []],
[pandoc.Str(options.caption)],
[newPath, fig]
)
]);
}

function externalTool(command) {
var paths = [
path.resolve(__dirname, "node_modules", ".bin", command),
path.resolve(__dirname, "..", ".bin", command)
];
// Ability to replace path of external tool by environment variable
// to replace `mmdc` use `MERMAID_FILTER_CMD_MMDC`
// to replace `imgur` use `MERMAID_FILTER_CMD_IMGUR`
var envCmdName = "MERMAID_FILTER_CMD_" + (command || "").toUpperCase().replace(/[^A-Z0-9-]/g, "_");
var envCmd = process.env[envCmdName];
if (envCmd) {
paths = [envCmd];
command = "env: " + envCmdName; // for error message
}
return firstExisting(paths,
function() {
console.error("External tool not found: " + command);
process.exit(1);
});
}

function mv(from, to) {
var readStream = fs.createReadStream(from)
var writeStream = fs.createWriteStream(to);

readStream.on('close', () => {
fs.unlinkSync(from);
});
readStream.pipe(writeStream);
}

function firstExisting(paths, error) {
for (var i = 0; i < paths.length; i++) {
if (fs.existsSync(paths[i])) return `"${paths[i]}"`;
}
error();
}

pandoc.toJSONFilter(function(type, value, format, meta) {
// Redirect stderr to a globally created writeable stream
process.stderr.write = errorLog.write.bind(errorLog);
return mermaid(type, value, format, meta);
});
const tmpObj = tmp.fileSync({ mode: 0o644, prefix: 'mermaid-filter-', postfix: '.err' })
const errorLog = fs.createWriteStream(tmpObj.name)

pandoc.toJSONFilter(function (type, value, format, meta) {
// Redirect stderr to a globally created writeable stream
process.stderr.write = errorLog.write.bind(errorLog)
return utils.mermaid(type, value, format, meta)
})
88 changes: 88 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* global test describe it expect fail */
const utils = require('./lib')
describe('external tool lookup', () => {
function findTool (name, env) {
return utils.externalTool(name, env, () => fail(`expected to find utility ${name}`))
}
it('should find mmdc tool', () => {
findTool('mmdc')
})
it('should find imgur tool', () => {
findTool('imgur')
})
describe('env overrides', () => {
it('should allow tool override from env', () => {
const path = findTool('mmdc', { MERMAID_FILTER_CMD_MMDC: '/usr/bin/ls' })
expect(path).toEqual('"/usr/bin/ls"')
})
it('should only override where env key matches', () => {
const path = findTool('imgur', { MERMAID_FILTER_CMD_MMDC: '/usr/bin/ls' })
expect(path).not.toEqual('"/usr/bin/ls"')
})
})
})

describe('mermaid', () => {
test('returns null for non code block', () => {
const type = 'Paragraph'
const value = []
const format = ''
const meta = {}

expect(utils.mermaid(type, value, format, meta)).toBeNull()
})

test('returns null if no mermaid class', () => {
const type = 'CodeBlock'
const value = [['id', ['other']], 'graph TD;\nA-->B;']
const format = ''
const meta = {}

expect(utils.mermaid(type, value, format, meta)).toBeNull()
})
})

describe('getOptions', () => {
test('sets default options', () => {
const options = utils.getOptions()

expect(options).toEqual(expect.objectContaining({
width: 800,
format: 'png'
}))
})

describe('env overrides', () => {
it.each([
['width', 600],
['format', 'svg'],
['loc', 'imgur'],
['theme', 'forest'],
['background', 'transparent'],
['caption', 'caption'],
['filename', 'filename'],
['scale', 2],
['imageClass', 'imageClass']
])('overrides options for %s from env', (key, value) => {
const options = utils.getOptions([], { [`MERMAID_FILTER_${key.toUpperCase()}`]: value })
expect(options[key]).toBe(value)
})
})

describe('attribute overrides', () => {
it.each([
['width', 600],
['format', 'svg'],
['loc', 'imgur'],
['theme', 'forest'],
['background', 'transparent'],
['caption', 'caption'],
['filename', 'filename'],
['scale', 2],
['imageClass', 'imageClass']
])('overrides options for %s from attributes', (key, value) => {
const options = utils.getOptions([[key, value]])
expect(options[key]).toBe(value)
})
})
})
Loading

0 comments on commit 053b111

Please sign in to comment.