diff --git a/docs/userGuide/puml.md b/docs/userGuide/puml.md index d46a132c3f..6bd174ca3b 100644 --- a/docs/userGuide/puml.md +++ b/docs/userGuide/puml.md @@ -30,19 +30,27 @@ A PlantUML diagram file (.puml) can be inserted into a Markbind page using a `

` tag, listed below: ****Options**** -Name | Type | Default | Description ---- | --- | --- | --- -alt | `string` | | **This must be specified.**
The alternative text of the diagram. -height | `string` | | The height of the diagram in pixels. -src | `string` | | **This must be specified.**
The URL of the diagram.
The URL can be specified as absolute or relative references. More info in: _[Intra-Site Links]({{baseUrl}}/userGuide/formattingContents.html#intraSiteLinks)_ -width | `string` | | The width of the diagram in pixels.
If both width and height are specified, width takes priority over height. It is to maintain the diagram's aspect ratio. +Name | Type | Description +--- | --- | --- +alt | `string` | **This must be specified.**
The alternative text of the diagram. +height | `string` | The height of the diagram in pixels. +name | `string` | The name of the output file. +src | `string` | The URL of the diagram if your PUML input is in another file.
The URL can be specified as absolute or relative references. More info in: _[Intra-Site Links]({{baseUrl}}/userGuide/formattingContents.html#intraSiteLinks)_ +width | `string` | The width of the diagram in pixels.
If both width and height are specified, width takes priority over height. It is to maintain the diagram's aspect ratio. ### Example +You could have your PUML be written in a separate file or inline. + +_Markbind page separate file_: +``` + +``` + _diagrams/sequence.puml_: ``` @startuml @@ -57,9 +65,20 @@ return success @enduml ``` -_Markbind page_: +_Markbind page inline_: ``` - + +@startuml +alice -> bob ++ : hello +bob -> bob ++ : self call +bob -> bib ++ #005500 : hello +bob -> george ** : create +return done +return rc +bob -> george !! : delete +return success +@enduml + ``` diff --git a/package-lock.json b/package-lock.json index 27d5346e83..5007394897 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1941,6 +1941,11 @@ "which": "^1.2.9" } }, + "crypto-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", + "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -3226,8 +3231,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -3245,13 +3249,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3264,18 +3266,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3378,8 +3377,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3389,7 +3387,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3402,20 +3399,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3432,7 +3426,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3505,8 +3498,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -3516,7 +3508,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3592,8 +3583,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -3623,7 +3613,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3641,7 +3630,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3680,13 +3668,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/package.json b/package.json index 3df1a0b18a..926794d6cb 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "cheerio": "^0.22.0", "chokidar": "^3.3.0", "commander": "^3.0.2", + "crypto-js": "^4.0.0", "fastmatter": "^2.1.1", "figlet": "^1.2.4", "find-up": "^4.1.0", diff --git a/src/plugins/default/markbind-plugin-plantuml.js b/src/plugins/default/markbind-plugin-plantuml.js index a7d87b7a1f..4862a991e1 100644 --- a/src/plugins/default/markbind-plugin-plantuml.js +++ b/src/plugins/default/markbind-plugin-plantuml.js @@ -6,42 +6,35 @@ const cheerio = module.parent.require('cheerio'); const fs = require('fs'); const path = require('path'); + const { exec } = require('child_process'); const logger = require('../../util/logger'); +const pluginUtil = require('../../util/pluginUtil'); const JAR_PATH = path.resolve(__dirname, 'plantuml.jar'); const { ERR_PROCESSING, - ERR_READING, } = require('../../constants'); // Tracks diagrams that have already been processed const processedDiagrams = new Set(); /** - * Generates diagram and replaces src attribute of puml tag - * @param src src attribute of the puml tag - * @param cwf original file that contains the puml tag, we resolve relative src to this file + * Generates diagram and returns the file name of the diagram + * @param fileName name of the file to be generated + * @param content puml dsl used to generate the puml diagram * @param config sourcePath and resultPath from parser context - * @returns {string} resolved src attribute + * @returns {string} file name of diagram */ -function generateDiagram(src, cwf, config) { - const { sourcePath, resultPath } = config; - const _cwf = cwf || sourcePath; +function generateDiagram(fileName, content, config) { + const { resultPath } = config; - // For replacing img.src - const diagramSrc = src.replace('.puml', '.png'); + const outputDir = path.join(path.dirname(resultPath), path.dirname(fileName)); // Path of the .puml file - const rawDiagramPath = path.resolve(path.dirname(_cwf), src); - // Path of the .png to be generated - const outputFilePath = path.resolve(resultPath, path.relative(sourcePath, rawDiagramPath) - .replace('.puml', '.png')); - // Output dir for the png file - const outputDir = path.dirname(outputFilePath); - + const outputFilePath = path.join(outputDir, path.basename(fileName)); // Tracks built files to avoid accessing twice - if (processedDiagrams.has(outputFilePath)) { return diagramSrc; } + if (processedDiagrams.has(outputFilePath)) { return fileName; } processedDiagrams.add(outputFilePath); // Creates output dir if it doesn't exist @@ -49,49 +42,37 @@ function generateDiagram(src, cwf, config) { fs.mkdirSync(outputDir, { recursive: true }); } - // Read diagram file, launch PlantUML jar - fs.readFile(rawDiagramPath, (err, data) => { - if (err) { - logger.debug(err); - logger.error(`${ERR_READING} ${rawDiagramPath}`); - return; - } - - const umlCode = data.toString(); - - // Java command to launch PlantUML jar - const cmd = `java -jar "${JAR_PATH}" -pipe > "${outputFilePath}"`; - const childProcess = exec(cmd); - - let errorLog = ''; - - childProcess.stdin.write( - umlCode, - (e) => { - if (e) { - logger.debug(e); - logger.error(`${ERR_PROCESSING} ${rawDiagramPath}`); - } - childProcess.stdin.end(); - }, - ); - - childProcess.on('error', (error) => { - logger.debug(error); - logger.error(`${ERR_PROCESSING} ${rawDiagramPath}`); - }); + // Java command to launch PlantUML jar + const cmd = `java -jar "${JAR_PATH}" -pipe > "${outputFilePath}"`; + const childProcess = exec(cmd); + + let errorLog = ''; + childProcess.stdin.write( + content, + (e) => { + if (e) { + logger.debug(e); + logger.error(`${ERR_PROCESSING} ${fileName}`); + } + childProcess.stdin.end(); + }, + ); + + childProcess.on('error', (error) => { + logger.debug(error); + logger.error(`${ERR_PROCESSING} ${fileName}`); + }); - childProcess.stderr.on('data', (errorMsg) => { - errorLog += errorMsg; - }); + childProcess.stderr.on('data', (errorMsg) => { + errorLog += errorMsg; + }); - childProcess.on('exit', () => { - // This goes to the log file, but not shown on the console - logger.debug(errorLog); - }); + childProcess.on('exit', () => { + // This goes to the log file, but not shown on the console + logger.debug(errorLog); }); - return diagramSrc; + return fileName; } module.exports = { @@ -101,11 +82,14 @@ module.exports = { // Processes all tags const $ = cheerio.load(content, { xmlMode: true }); $('puml').each((i, tag) => { - // eslint-disable-next-line no-param-reassign tag.name = 'pic'; - const { src, cwf } = tag.attribs; - // eslint-disable-next-line no-param-reassign - tag.attribs.src = generateDiagram(src, cwf, config); + const { cwf } = tag.attribs; + const pumlContent = pluginUtil.getPluginContent($, tag, cwf); + + const filePath = pluginUtil.getFilePathForPlugin(tag.attribs, pumlContent); + + tag.attribs.src = generateDiagram(filePath, pumlContent, config); + tag.children = []; }); return $.html(); @@ -113,4 +97,6 @@ module.exports = { getSources: () => ({ tagMap: [['puml', 'src']], }), + + getSpecialTags: () => ['puml'], }; diff --git a/src/util/pluginUtil.js b/src/util/pluginUtil.js new file mode 100644 index 0000000000..15003674d0 --- /dev/null +++ b/src/util/pluginUtil.js @@ -0,0 +1,51 @@ +const cryptoJS = require('crypto-js'); +const fs = require('fs'); +const path = require('path'); +const fsUtil = require('./fsUtil'); +const logger = require('./logger'); + +const { + ERR_READING, +} = require('../constants'); + +const pluginUtil = { + /** + * Returns the file path for the plugin tag. + * Return based on given name if provided, or it will be based on src. + * If both are not provided, a md5 generated hash is provided for consistency. + */ + getFilePathForPlugin: (tagAttribs, content) => { + if (tagAttribs.name !== undefined) { + return `${fsUtil.removeExtension(tagAttribs.name)}.png`; + } + + if (tagAttribs.src !== undefined) { + const filePath = fsUtil.removeExtension(tagAttribs.src); + return `${filePath}.png`; + } + + const hashedContent = cryptoJS.MD5(content).toString(); + return `${hashedContent}.png`; + }, + + /** + * Returns the string content of the plugin. + */ + getPluginContent: ($, element, cwf) => { + if (element.attribs.src !== undefined) { + // Path of the plugin content + const rawPath = path.resolve(path.dirname(cwf), element.attribs.src); + try { + return fs.readFileSync(rawPath, 'utf8'); + } catch (err) { + logger.debug(err); + logger.error(`${ERR_READING} ${rawPath}`); + return ''; + } + } + + return $(element).text(); + }, +}; + +module.exports = pluginUtil; diff --git a/test/functional/test_site/expected/9c9e77fc0a983cb6b592e65733787bec.png b/test/functional/test_site/expected/9c9e77fc0a983cb6b592e65733787bec.png new file mode 100644 index 0000000000..ff4fd1ece9 Binary files /dev/null and b/test/functional/test_site/expected/9c9e77fc0a983cb6b592e65733787bec.png differ diff --git a/test/functional/test_site/expected/index.html b/test/functional/test_site/expected/index.html index f8b5285162..355c44413e 100644 --- a/test/functional/test_site/expected/index.html +++ b/test/functional/test_site/expected/index.html @@ -497,6 +497,12 @@

Markbind Plugin Pre-render +

+

+ +

Sequence Diagram

diff --git a/test/functional/test_site/expected/inline-output.png b/test/functional/test_site/expected/inline-output.png new file mode 100644 index 0000000000..e0834af42c Binary files /dev/null and b/test/functional/test_site/expected/inline-output.png differ diff --git a/test/functional/test_site/expected/testPlantUML.html b/test/functional/test_site/expected/testPlantUML.html index e359c22e9b..8c273c7987 100644 --- a/test/functional/test_site/expected/testPlantUML.html +++ b/test/functional/test_site/expected/testPlantUML.html @@ -26,6 +26,12 @@

PlantUML Test

+

+ +

+

+ +

Sequence Diagram

diff --git a/test/functional/test_site/testPlantUML.md b/test/functional/test_site/testPlantUML.md index a407b745a9..ac8ca67531 100644 --- a/test/functional/test_site/testPlantUML.md +++ b/test/functional/test_site/testPlantUML.md @@ -1,5 +1,41 @@ **PlantUML Test** + + +@startuml +alice -> bob ++ : hello +bob -> bob ++ : self call +bob -> bib ++ #005500 : hello +bob -> george ** : create +return done +return rc +bob -> george !! : delete +return success +@enduml + + + + +@startuml +object Object01 +object Object02 +object Object03 +object Object04 +object Object05 +object Object06 +object Object07 +object Object08 +object user { + name = "Dummy" + id = 123 +} +Object01 <|-- Object02 +Object03 *-- Object04 +Object05 o-- "4" Object06 +Object07 .. Object08 : some labels +@enduml + + **Sequence Diagram**