Add commandline tool for generating SVG from code [#32]

This commit is contained in:
David Evans 2018-04-21 20:24:54 +01:00
parent 6d4a620800
commit edc71934dc
14 changed files with 10375 additions and 13 deletions

View File

@ -16,6 +16,9 @@
<FileRef <FileRef
location = "container:web"> location = "container:web">
</FileRef> </FileRef>
<FileRef
location = "container:bin">
</FileRef>
<FileRef <FileRef
location = "container:.gitignore"> location = "container:.gitignore">
</FileRef> </FileRef>

46
bin/sequence-diagram-svg.js Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env node
const {
headlessTextSizerFactory,
SequenceDiagram,
VirtualDocument,
} = require('../lib/sequence-diagram');
function render(code) {
const sd = new SequenceDiagram({
code,
document: new VirtualDocument(),
namespace: '',
textSizerFactory: headlessTextSizerFactory,
});
return sd.dom().outerHTML;
}
function read(pipe) {
return new Promise((resolve) => {
let all = '';
pipe.on('readable', () => {
const chunk = pipe.read();
if(chunk !== null) {
all += chunk;
}
});
pipe.on('end', () => {
resolve(all);
});
});
}
function getCodeArg() {
if(process.argv.length > 2 && process.argv[2] !== '-') {
return Promise.resolve(process.argv[2]);
} else {
process.stdin.setEncoding('utf8');
return read(process.stdin);
}
}
getCodeArg().then((code) => {
process.stdout.write(render(code) + '\n');
});

View File

@ -32,7 +32,7 @@
<link rel="stylesheet" href="styles/editor.css"> <link rel="stylesheet" href="styles/editor.css">
<script src="lib/sequence-diagram.min.js"></script> <script src="lib/sequence-diagram-web.min.js"></script>
<script <script
src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.min.js" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.min.js"

9969
lib/sequence-diagram-web.js Normal file

File diff suppressed because it is too large Load Diff

1
lib/sequence-diagram-web.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -9950,9 +9950,285 @@
themes, themes,
}); });
const def = window.define; function encodeChar(c) {
if(def && def.amd) { return '&#' + c.charCodeAt(0).toString(10) + ';';
def(() => SequenceDiagram); }
function escapeHTML(text) {
return text.replace(/[^\r\n\t -%'-;=?-~]/g, encodeChar);
}
function escapeQuoted(text) {
return text.replace(/[^\r\n\t !#$%(-;=?-~]/g, encodeChar);
}
class TextNode {
constructor(content) {
this.parentNode = null;
this.nodeValue = content;
}
contains() {
return false;
}
get textContent() {
return this.nodeValue;
}
set textContent(value) {
this.nodeValue = value;
}
get isConnected() {
if(this.parentNode !== null) {
return this.parentNode.isConnected;
}
return false;
}
get innerHTML() {
return escapeHTML(this.nodeValue);
}
get outerHTML() {
return this.innerHTML;
}
}
class ElementNode {
constructor(ownerDocument, tag, namespace) {
this.ownerDocument = ownerDocument;
this.tagName = tag;
this.namespaceURI = namespace;
this.parentNode = null;
this.childNodes = [];
this.attributes = new Map();
this.listeners = new Map();
}
setAttribute(key, value) {
let v = null;
if(typeof value === 'number') {
v = value.toString(10);
} else if(typeof value === 'string') {
v = value;
} else {
throw new Error('Bad value ' + value + ' for attribute ' + key);
}
this.attributes.set(key, v);
}
getAttribute(key) {
return this.attributes.get(key);
}
addEventListener(event, fn) {
let list = this.listeners.get(event);
if(!list) {
list = [];
this.listeners.set(event, list);
}
list.push(fn);
}
removeEventListener(event, fn) {
const list = this.listeners.get(event) || [];
const index = list.indexOf(fn);
if(index !== -1) {
list.splice(index, 1);
}
}
dispatchEvent(e) {
const list = this.listeners.get(e.type) || [];
list.forEach((fn) => fn(e));
}
contains(descendant) {
let check = descendant;
while(check) {
if(check === this) {
return true;
}
check = check.parentNode;
}
return false;
}
getElementsByTagName(tag) {
const result = [];
this.traverseDescendants((o) => {
if(o.tagName === tag) {
result.push(o);
}
});
return result;
}
getElementsByClassName(className) {
const result = [];
const check = ' ' + className + ' ';
this.traverseDescendants((o) => {
const cls = ' ' + (o.getAttribute('class') || '') + ' ';
if(cls.indexOf(check) !== -1) {
result.push(o);
}
});
return result;
}
traverseDescendants(fn) {
if(fn(this) === false) {
return;
}
for(const child of this.childNodes) {
if(child.traverseDescendants) {
child.traverseDescendants(fn);
}
}
}
get firstChild() {
return this.childNodes[0] || null;
}
get lastChild() {
return this.childNodes[this.childNodes.length - 1] || null;
}
indexOf(child) {
const index = this.childNodes.indexOf(child);
if(index === -1) {
throw new Error(child + ' is not a child of ' + this);
}
return index;
}
insertBefore(child, existingChild) {
if(child.contains(this)) {
throw new Error('Cyclic node structures are not permitted');
}
if(child.parentNode !== null) {
child.parentNode.removeChild(child);
}
if(existingChild === null) {
this.childNodes.push(child);
} else {
this.childNodes.splice(this.indexOf(existingChild), 0, child);
}
child.parentNode = this;
return child;
}
appendChild(child) {
return this.insertBefore(child, null);
}
removeChild(child) {
this.childNodes.splice(this.indexOf(child), 1);
child.parentNode = null;
return child;
}
replaceChild(newChild, oldChild) {
if(newChild === oldChild) {
return oldChild;
}
this.insertBefore(newChild, oldChild);
return this.removeChild(oldChild);
}
get isConnected() {
return true;
}
get textContent() {
let text = '';
for(const child of this.childNodes) {
text += child.textContent;
}
return text;
}
set textContent(value) {
for(const child of this.childNodes) {
child.parentNode = null;
}
this.childNodes.length = 0;
this.appendChild(new TextNode(value));
}
get innerHTML() {
let html = '';
for(const child of this.childNodes) {
html += child.outerHTML;
}
return html;
}
get outerHTML() {
let attrs = '';
for(const [key, value] of this.attributes) {
attrs += ' ' + key + '="' + escapeQuoted(value) + '"';
}
return (
'<' + this.tagName + attrs + '>' +
this.innerHTML +
'</' + this.tagName + '>'
);
}
}
class VirtualDocument {
createElement(tag) {
return new ElementNode(this, tag, '');
}
createElementNS(ns, tag) {
return new ElementNode(this, tag, ns || '');
}
createTextNode(content) {
return new TextNode(content);
}
}
class UnitaryTextSizer {
// Simplified text sizer, which assumes all characters render as
// 1x1 px squares for repeatable renders in all browsers
baseline() {
return 1;
}
measureHeight({formatted}) {
return formatted.length;
}
prepMeasurement(attrs, formatted) {
return formatted;
}
prepComplete() {
// No-op
}
performMeasurement(data) {
return data.reduce((total, part) => total + part.text.length, 0);
}
teardown() {
// No-op
}
}
if(typeof exports !== 'undefined') {
exports.SequenceDiagram = SequenceDiagram;
exports.VirtualDocument = VirtualDocument;
exports.headlessTextSizerFactory = () => new UnitaryTextSizer();
} else if(window.define && window.define.amd) {
window.define(() => SequenceDiagram);
} else { } else {
window.document.addEventListener('DOMContentLoaded', () => { window.document.addEventListener('DOMContentLoaded', () => {
SequenceDiagram.convertAll(); SequenceDiagram.convertAll();

File diff suppressed because one or more lines are too long

View File

@ -57,7 +57,7 @@
crossorigin="anonymous" crossorigin="anonymous"
></script> ></script>
<script src="lib/sequence-diagram.min.js"></script> <script src="lib/sequence-diagram-web.min.js"></script>
<script>document.addEventListener('DOMContentLoaded', () => { <script>document.addEventListener('DOMContentLoaded', () => {
@ -125,13 +125,13 @@ This library renders sequence diagrams from code. It is
(LGPL-3.0), and including it in a website is as simple as adding the script:</p> (LGPL-3.0), and including it in a website is as simple as adding the script:</p>
<pre data-lang="text/html"> <pre data-lang="text/html">
&lt;script src="lib/sequence-diagram.min.js"&gt;&lt;/script&gt; &lt;script src="lib/sequence-diagram-web.min.js"&gt;&lt;/script&gt;
</pre> </pre>
<p>Or if you are using requirejs:</p> <p>Or if you are using requirejs:</p>
<pre data-lang="javascript"> <pre data-lang="javascript">
requirejs(['lib/sequence-diagram.min'], (SequenceDiagram) => { requirejs(['lib/sequence-diagram-web.min'], (SequenceDiagram) => {
SequenceDiagram.convertAll(); SequenceDiagram.convertAll();
}); });
</pre> </pre>

View File

@ -12,15 +12,22 @@
}, },
"license": "LGPL-3.0", "license": "LGPL-3.0",
"files": [ "files": [
"lib/sequence-diagram.js" "lib/sequence-diagram-web.js",
"lib/sequence-diagram.js",
"scripts",
"bin"
], ],
"main": "lib/sequence-diagram", "main": "lib/sequence-diagram",
"module": "scripts/standalone", "module": "scripts/standalone",
"bin": {
"sequence-diagram-svg": "./bin/sequence-diagram-svg.js"
},
"scripts": { "scripts": {
"lint": "eslint . --config spec/support/eslintrc.js --ignore-path spec/support/eslintignore --ext .js --ext .mjs", "lint": "eslint . --config spec/support/eslintrc.js --ignore-path spec/support/eslintignore --ext .js --ext .mjs",
"minify-lib": "rollup --config scripts/rollup.config.js && uglifyjs --compress --mangle --warn --output lib/sequence-diagram.min.js -- lib/sequence-diagram.js", "minify-lib": "rollup --config scripts/rollup.config.js && uglifyjs --compress --mangle --warn --output lib/sequence-diagram-web.min.js -- lib/sequence-diagram-web.js && uglifyjs --compress --mangle --warn --output lib/sequence-diagram.min.js -- lib/sequence-diagram.js",
"minify-web": "rollup --config web/rollup.config.js && uglifyjs --compress --mangle --warn --output weblib/editor.min.js -- weblib/editor.js", "minify-web": "rollup --config web/rollup.config.js && uglifyjs --compress --mangle --warn --output weblib/editor.min.js -- weblib/editor.js",
"minify": "npm run minify-lib && npm run minify-web", "minify": "npm run minify-lib && npm run minify-web",
"prepublishOnly": "npm run minify-lib",
"start": "http-server", "start": "http-server",
"test": "npm run unit-test && npm run web-test && npm run lint && echo 'PASSED :)'", "test": "npm run unit-test && npm run web-test && npm run lint && echo 'PASSED :)'",
"unit-test": "rollup --config spec/support/rollup.config.js && node -r source-map-support/register node_modules/.bin/jasmine --config=spec/support/jasmine.json", "unit-test": "rollup --config spec/support/rollup.config.js && node -r source-map-support/register node_modules/.bin/jasmine --config=spec/support/jasmine.json",

View File

@ -1,4 +1,12 @@
export default [ export default [
{
input: 'scripts/standalone-web.mjs',
output: {
file: 'lib/sequence-diagram-web.js',
format: 'iife',
name: 'SequenceDiagram',
},
},
{ {
input: 'scripts/standalone.mjs', input: 'scripts/standalone.mjs',
output: { output: {

View File

@ -0,0 +1,17 @@
import SequenceDiagram from './sequence/SequenceDiagram.mjs';
if(typeof exports !== 'undefined') {
exports.SequenceDiagram = SequenceDiagram;
} else if(window.define && window.define.amd) {
window.define(() => SequenceDiagram);
} else {
window.document.addEventListener('DOMContentLoaded', () => {
SequenceDiagram.convertAll();
}, {once: true});
if(window.CodeMirror) {
SequenceDiagram.registerCodeMirrorMode(window.CodeMirror);
}
window.SequenceDiagram = SequenceDiagram;
}

View File

@ -1,8 +1,41 @@
import SequenceDiagram from './sequence/SequenceDiagram.mjs'; import SequenceDiagram from './sequence/SequenceDiagram.mjs';
import {VirtualDocument} from './core/documents/VirtualDocument.mjs';
const def = window.define; class UnitaryTextSizer {
if(def && def.amd) { // Simplified text sizer, which assumes all characters render as
def(() => SequenceDiagram); // 1x1 px squares for repeatable renders in all browsers
baseline() {
return 1;
}
measureHeight({formatted}) {
return formatted.length;
}
prepMeasurement(attrs, formatted) {
return formatted;
}
prepComplete() {
// No-op
}
performMeasurement(data) {
return data.reduce((total, part) => total + part.text.length, 0);
}
teardown() {
// No-op
}
}
if(typeof exports !== 'undefined') {
exports.SequenceDiagram = SequenceDiagram;
exports.VirtualDocument = VirtualDocument;
exports.headlessTextSizerFactory = () => new UnitaryTextSizer();
} else if(window.define && window.define.amd) {
window.define(() => SequenceDiagram);
} else { } else {
window.document.addEventListener('DOMContentLoaded', () => { window.document.addEventListener('DOMContentLoaded', () => {
SequenceDiagram.convertAll(); SequenceDiagram.convertAll();

View File

@ -40,6 +40,7 @@ module.exports = (config) => {
{included: false, pattern: 'scripts/**/*'}, {included: false, pattern: 'scripts/**/*'},
{included: false, pattern: 'spec/**/*'}, {included: false, pattern: 'spec/**/*'},
{included: false, pattern: 'web/**/*'}, {included: false, pattern: 'web/**/*'},
{included: false, pattern: 'bin/**/*'},
{included: false, pattern: 'README.md'}, {included: false, pattern: 'README.md'},
], ],
frameworks: ['detectBrowsers', 'jasmine'], frameworks: ['detectBrowsers', 'jasmine'],

View File

@ -7,6 +7,7 @@ export default [
'scripts/**/*_spec.mjs', 'scripts/**/*_spec.mjs',
'spec/**/*_spec.mjs', 'spec/**/*_spec.mjs',
'web/**/*_spec.mjs', 'web/**/*_spec.mjs',
'bin/**/*_spec.mjs',
], ],
output: { output: {
file: 'ephemeral/spec_bundle.js', file: 'ephemeral/spec_bundle.js',