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**