Skip to content

Commit

Permalink
Initial version of the plugin including unit tests and basic document…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
Pierre Awaragi committed Jun 27, 2021
1 parent 452ed57 commit 2a99ba4
Show file tree
Hide file tree
Showing 10 changed files with 1,933 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ dist

# TernJS port file
.tern-port

# Project specific exclusions
yarn.lock
/.idea/
7 changes: 7 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
test/
.gitignore
.npmignore
jest.config.js
yarn.lock
yarn-error.log
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# eleventy-plugin-plantuml
# eleventy-plugin-plantuml

> Eleventy Plantuml plugin. Uses sync request to connect to a plantuml server to convert markddown code block of type
> plantuml to an inline dataurl png ```<img>```
## Dependencies
* [eleventy](https://www.npmjs.com/package/@11ty/eleventy) A simpler static site generator for which this plugin is make.
* [sync-request](https://www.npmjs.com/package/sync-request) (for making blocking synchronous http request - not to be used in production)
* [jest](https://www.npmjs.com/package/jest) (for executing unit tests)

## Installation

Add plugin to .eleventy.js configuration file and optionally provide a private Plantuml server
```javascript
eleventyConfig.addPlugin(plantuml, {
hostname: "localhost",
port: 8888,
prefix: ""
});
```

if options are omited, the plugin defaults to <http://plnntuml.com/plantuml> server for conversion.

## Using in templates
Simply create a markdown code block of type plantuml and it will be replaced by an img with inline png src (dataurl).

>In the following example, there is an extra space between the ticks ` for escaping
```
`` `plantuml
@startuml
!include https://raw.githubusercontent.com/bschwarz/puml-themes/master/themes/bluegray/puml-theme-bluegray.puml
participant "Makrdown Highlighter" as MDH
participant "eleventy-plugin-plantumt" as plugin
participant "Plantuml Server" as plantuml
MDH -> plugin : highlight
plugin -> plugin: compress url
plugin -> plantuml: GET /png/{url}
plantuml -> plugin: image/png
plugin -> plugin: base64
plugin -> MDH: img src="dataurl"
@enduml
`` `
```

## Contribute
* create a fork of this repo
* make your changes
* test your code by running ```yarn test``` or ```npm test```
* create a Pull Request


## Testing
Execute ```yarn test``` or ```npm test``` to execute integration tests against <plantuml.com/plantuml> live server
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./lib/eleventy-plugin-plantuml');
7 changes: 7 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
"moduleFileExtensions": ["js", "json"],
"rootDir": "./",
"testMatch": ["<rootDir>/test/**/*.{spec,test}.js"],
"testPathIgnorePatterns": ["<rootDir>/node_modules"],
"testEnvironment": "node"
}
69 changes: 69 additions & 0 deletions lib/eleventy-plugin-plantuml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const request = require('sync-request');
const plantumlEncode = require('./plantumlEncode');

const defaultOptions = {
protocol: 'http',
hostname: 'www.plantuml.com',
port: "80",
prefix: "/plantuml",
outputType: "png",
imgClass: "plantuml"
};

function generatePlantumlUrl(encoded, options) {
return `${options.protocol}://${options.hostname}${options.port === 80 ? "" : options.port === 443 ? "" : (":" + options.port)}${options.prefix}/${options.outputType}/${encoded}`;
}

const generateImageAsBase64String = (url, options) => {
// request url
const response = request('GET', url);
// convert from Uint8Array to buffer to base64 string
return Buffer.from(response.getBody()).toString('base64');
}

const generateImgTag = (imageBase64, options) => {
return `<img class="${options.imgClass}" src="data:image/png;base64,${imageBase64}" alt="Plantuml Diagram" />`;
};

const highlight = (diagram, options) => {
// compute compressed version of str
const encoded = plantumlEncode(diagram);
// URL to send to plantuml for conversion
const url = generatePlantumlUrl(encoded, options);
// Call plantuml server to generate image
const imageBase64 = generateImageAsBase64String(url, options);
// Finally convert image to dataurl
return generateImgTag(imageBase64, options);
};

const plugin = (eleventyConfig, pluginOptions = {}) => {
// default options
const options = Object.assign({}, defaultOptions, pluginOptions);

// preserve chain of highlighter
const highlighter = eleventyConfig.markdownHighlighter;

// add highlighter
eleventyConfig.addMarkdownHighlighter((diagram, language) => {
if (language === "plantuml") {
return highlight(diagram, options);
}
// just in case highlighter is not enabled in which case I am not sure why we are doing
if (highlighter) {
return highlighter(diagram, language)
}
// default highlighter just in case
return `<pre class="${language}">${diagram}</a>`;
});
return {}
};

module.exports = {
defaultOptions,
plugin,
highlight,

generatePlantumlUrl,
generateImageAsBase64String,
generateImgTag
};
58 changes: 58 additions & 0 deletions lib/plantumlEncode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Original:
* https://github.com/johan/js-deflate
*/
const { deflate } = require('./rawdeflate');

const encode64 = data => {
let r = "";
for (let i=0; i<data.length; i+=3) {
if (i+2===data.length) {
r +=append3bytes(data.charCodeAt(i), data.charCodeAt(i+1), 0);
} else if (i+1===data.length) {
r += append3bytes(data.charCodeAt(i), 0, 0);
} else {
r += append3bytes(data.charCodeAt(i), data.charCodeAt(i+1),
data.charCodeAt(i+2));
}
}
return r;
}

const append3bytes = (b1, b2, b3) => {
let c1 = b1 >> 2;
let c2 = ((b1 & 0x3) << 4) | (b2 >> 4);
let c3 = ((b2 & 0xF) << 2) | (b3 >> 6);
let c4 = b3 & 0x3F;
let r = "";
r += encode6bit(c1 & 0x3F);
r += encode6bit(c2 & 0x3F);
r += encode6bit(c3 & 0x3F);
r += encode6bit(c4 & 0x3F);
return r;
}

const encode6bit = b => {
if (b < 10) {
return String.fromCharCode(48 + b);
}
b -= 10;
if (b < 26) {
return String.fromCharCode(65 + b);
}
b -= 26;
if (b < 26) {
return String.fromCharCode(97 + b);
}
b -= 26;
if (b === 0) {
return '-';
}
if (b === 1) {
return '_';
}
return '?';
}
const geturl = (s) => encode64(deflate(unescape(encodeURIComponent(s)), 9));

module.exports = geturl;
Loading

0 comments on commit 2a99ba4

Please sign in to comment.